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