|
1 """special management views to manage repository content (initialization and |
|
2 restoration). |
|
3 |
|
4 :organization: Logilab |
|
5 :copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
7 """ |
|
8 __docformat__ = "restructuredtext en" |
|
9 |
|
10 from os.path import exists, join, abspath |
|
11 from pickle import loads, dumps |
|
12 |
|
13 from logilab.common.decorators import cached |
|
14 from logilab.mtconverter import html_escape |
|
15 |
|
16 from cubicweb.common.view import StartupView |
|
17 from cubicweb.web import Redirect |
|
18 from cubicweb.goa.dbinit import fix_entities, init_persistent_schema, insert_versions |
|
19 |
|
20 from google.appengine.api.datastore import Entity, Key, Get, Put, Delete |
|
21 from google.appengine.api.datastore_types import Blob |
|
22 from google.appengine.api.datastore_errors import EntityNotFoundError |
|
23 |
|
24 |
|
25 def _get_status(name, create=True): |
|
26 key = Key.from_path('EApplicationStatus', name) |
|
27 try: |
|
28 status = Get(key) |
|
29 except EntityNotFoundError: |
|
30 if create: |
|
31 status = Entity('EApplicationStatus', name=name) |
|
32 else: |
|
33 status = None |
|
34 return status |
|
35 |
|
36 |
|
37 class AuthInfo(StartupView): |
|
38 """special management view to get cookie values to give to laxctl commands |
|
39 which are doing datastore administration requests |
|
40 """ |
|
41 id = 'authinfo' |
|
42 require_groups = ('managers',) |
|
43 |
|
44 def call(self): |
|
45 cookie = self.req.get_cookie() |
|
46 values = [] |
|
47 if self.config['use-google-auth']: |
|
48 for param in ('ACSID', 'dev_appserver_login'): |
|
49 morsel = cookie.get(param) |
|
50 if morsel: |
|
51 values.append('%s=%s' % (param, morsel.value)) |
|
52 break |
|
53 values.append('__session=%s' % cookie['__session'].value) |
|
54 self.w(u"<p>pass this flag to the client: --cookie='%s'</p>" |
|
55 % html_escape('; '.join(values))) |
|
56 |
|
57 |
|
58 |
|
59 class ContentInit(StartupView): |
|
60 """special management view to initialize content of a repository, |
|
61 step by step to avoid depassing quotas |
|
62 """ |
|
63 id = 'contentinit' |
|
64 require_groups = ('managers',) |
|
65 |
|
66 def server_session(self): |
|
67 ssession = self.config.repo_session(self.req.cnx.sessionid) |
|
68 ssession.set_pool() |
|
69 return ssession |
|
70 |
|
71 def end_core_step(self, msg, status, stepid): |
|
72 status['cpath'] = '' |
|
73 status['stepid'] = stepid |
|
74 Put(status) |
|
75 self.msg(msg) |
|
76 |
|
77 def call(self): |
|
78 status = _get_status('creation') |
|
79 if status.get('finished'): |
|
80 self.redirect('process already completed') |
|
81 config = self.config |
|
82 # execute cubicweb's post<event> script |
|
83 #mhandler.exec_event_script('post%s' % event) |
|
84 # execute cubes'post<event> script if any |
|
85 paths = [p for p in config.cubes_path() + [config.apphome] |
|
86 if exists(join(p, 'migration'))] |
|
87 paths = [abspath(p) for p in (reversed(paths))] |
|
88 cpath = status.get('cpath') |
|
89 if cpath is None and status.get('stepid') is None: |
|
90 init_persistent_schema(self.server_session(), self.schema) |
|
91 self.end_core_step(u'inserted schema entities', status, 0) |
|
92 return |
|
93 if cpath == '' and status.get('stepid') == 0: |
|
94 fix_entities(self.schema) |
|
95 self.end_core_step(u'fixed bootstrap groups and users', status, 1) |
|
96 return |
|
97 if cpath == '' and status.get('stepid') == 1: |
|
98 insert_versions(self.server_session(), self.config) |
|
99 self.end_core_step(u'inserted software versions', status, None) |
|
100 return |
|
101 for i, path in enumerate(paths): |
|
102 if not cpath or cpath == path: |
|
103 self.info('running %s', path) |
|
104 stepid = status.get('stepid') |
|
105 context = status.get('context') |
|
106 if context is not None: |
|
107 context = loads(context) |
|
108 else: |
|
109 context = {} |
|
110 stepid = self._migrhandler.exec_event_script( |
|
111 'postcreate', path, 'stepable_postcreate', stepid, context) |
|
112 if stepid is None: # finished for this script |
|
113 # reset script state |
|
114 context = stepid = None |
|
115 # next time, go to the next script |
|
116 self.msg(u'finished postcreate for %s' % path) |
|
117 try: |
|
118 path = paths[i+1] |
|
119 self.continue_link() |
|
120 except IndexError: |
|
121 status['finished'] = True |
|
122 path = None |
|
123 self.redirect('process completed') |
|
124 else: |
|
125 if context.get('stepidx'): |
|
126 self.msg(u'created %s entities for step %s of %s' % ( |
|
127 context['stepidx'], stepid, path)) |
|
128 else: |
|
129 self.msg(u'finished postcreate step %s for %s' % ( |
|
130 stepid, path)) |
|
131 context = Blob(dumps(context)) |
|
132 self.continue_link() |
|
133 status['context'] = context |
|
134 status['stepid'] = stepid |
|
135 status['cpath'] = path |
|
136 break |
|
137 else: |
|
138 if not cpath: |
|
139 # nothing to be done |
|
140 status['finished'] = True |
|
141 self.redirect('process completed') |
|
142 else: |
|
143 # Note the error: is expected by the laxctl command line tool, |
|
144 # deal with this if internationalization is introduced |
|
145 self.msg(u'error: strange creation state, can\'t find %s' |
|
146 % cpath) |
|
147 self.w(u'<div>click <a href="%s?vid=contentclear">here</a> to ' |
|
148 '<b>delete all datastore content</b> so process can be ' |
|
149 'reinitialized</div>' % html_escape(self.req.base_url())) |
|
150 Put(status) |
|
151 |
|
152 @property |
|
153 @cached |
|
154 def _migrhandler(self): |
|
155 return self.config.migration_handler(self.schema, interactive=False, |
|
156 cnx=self.req.cnx, |
|
157 repo=self.config.repository()) |
|
158 |
|
159 def msg(self, msg): |
|
160 self.w(u'<div class="message">%s</div>' % html_escape(msg)) |
|
161 def redirect(self, msg): |
|
162 raise Redirect(self.req.build_url('', msg)) |
|
163 def continue_link(self): |
|
164 self.w(u'<a href="%s">continue</a><br/>' % html_escape(self.req.url())) |
|
165 |
|
166 |
|
167 class ContentClear(StartupView): |
|
168 id = 'contentclear' |
|
169 require_groups = ('managers',) |
|
170 skip_etypes = ('EGroup', 'EUser') |
|
171 |
|
172 def call(self): |
|
173 # XXX should use unsafe_execute with all hooks deactivated |
|
174 # XXX step by catching datastore errors? |
|
175 for eschema in self.schema.entities(): |
|
176 if eschema.is_final() or eschema in self.skip_etypes: |
|
177 continue |
|
178 self.req.execute('DELETE %s X' % eschema) |
|
179 self.w(u'deleted all %s entities<br/>' % eschema) |
|
180 status = _get_status('creation', create=False) |
|
181 if status: |
|
182 Delete(status) |
|
183 self.w(u'done<br/>') |
|
184 self.w(u'click <a href="%s?vid=contentinit">here</a> to start the data ' |
|
185 'initialization process<br/>' % self.req.base_url()) |