root/traclegosscript/anyrelease/traclegos/web.py

Revision 4631, 12.7 kB (checked in by k0s, 2 months ago)

* infrastructure for db types
* preliminary selection of db information TTW

Line 
1 """
2 TTW view for project creation and serving trac
3 """
4
5 import cgi
6 import os
7 import string
8 import sys
9 import tempfile
10
11 from genshi.core import Markup
12 from genshi.template import TemplateLoader
13 from trac.web.main import dispatch_request
14 from traclegos.config import ConfigMunger
15 from traclegos.db import available_databases
16 from traclegos.legos import site_configuration
17 from traclegos.legos import traclegos_factory
18 from traclegos.legos import TracLegos
19 from traclegos.pastescript.string import PasteScriptStringTemplate
20 from traclegos.pastescript.var import vars2dict, dict2vars
21 from traclegos.project import project_dict
22 from traclegos.repository import available_repositories
23 from webob import Request, Response, exc
24
25 # TODO: better handling of errors (not very friendly, currently)
26
27 template_directory = os.path.join(os.path.dirname(__file__), 'templates')
28
29 class View(object):
30     """WebOb view which wraps trac and allows TTW project creations"""
31
32     def __init__(self, **kw):
33
34         # trac project creator
35         argspec = traclegos_factory(kw.get('conf', ()),
36                                     kw,
37                                     kw.get('variables', {}))
38         self.legos = TracLegos(**argspec)
39         self.legos.interactive = False
40         self.directory = self.legos.directory # XXX needed?
41
42         # trac projects available
43         self.available_templates = kw.get('available_templates') or project_dict().keys()
44         assert self.available_templates
45            
46         # genshi template loader
47         self.loader = TemplateLoader(template_directory, auto_reload=True)
48
49         # storage of intermittent projects
50         self.projects = {}
51
52         # URL to redirect to after project creation
53         self.done = '/%(project)s'
54
55         # steps of project creation
56         self.steps = [ 'create-project', 'project-details', 'project-variables' ]
57
58         # available SCM repository types
59         self.repositories = available_repositories()
60         self.available_repositories = kw.get('available_repositories')
61         if self.available_repositories is None:
62             self.available_repositories = ['NoRepository'] + [ name for name in self.repositories.keys() if name is not 'NoRepository' ]
63            
64         else:
65             for name in self.repositories.keys():
66                 if name not in self.available_repositories:
67                     del self.repositories[name]
68
69         # available database types
70         self.databases = available_databases()
71         self.available_databases = kw.get('available_databases')
72         if self.available_databases is None:
73             self.available_databases = [ 'SQLite' ] + [ name for name in self.databases.keys() if name is not 'SQLite' ]
74         else:
75             for name in self.databases.keys():
76                 if name not in self.available_databases:
77                     del self.databases[name]
78                    
79         # TODO: pop project-details if this is an empty step
80
81     ### methods dealing with HTTP
82     def __call__(self, environ, start_response):
83
84         req = Request(environ)
85        
86         step = req.path_info.strip('/')
87         if step in self.steps:
88             method = ''.join(index and token.title() or token
89                              for index, token in enumerate(step.split('-')))
90
91             # if POST-ing, validate the request and store needed information
92             errors = None
93             if req.method == 'POST':
94
95                 project = req.POST.get('project')
96                 if not project and step != 'create-project':
97                     res = exc.HTTPSeeOther("No session found", location="create-project")
98                     return res(environ, start_response)
99        
100                 validator = getattr(self, 'validate' + method[0].upper() + method[1:])
101                 errors = validator(req)
102                 if not errors: # success
103                     index = self.steps.index(step)
104                     if index == len(self.steps) - 1:
105                         destination = self.done % self.projects[project]['vars']
106                         self.projects.pop(project) # successful project creation
107                     else:
108                         destination = '%s?project=%s' % (self.steps[index + 1], project)
109                     res = exc.HTTPSeeOther(destination, location=destination)
110                     return res(environ, start_response)
111             else:
112                 if step != 'create-project':
113                     project = req.GET.get('project')
114                     if project in self.projects:
115                         # TODO: put this and the project data into data
116                         pass
117                     else:
118                         res = exc.HTTPSeeOther("No session found", location="create-project")
119                         return res(environ, start_response)
120                        
121            
122             data = getattr(self, method)(req, errors)
123             data['errors'] = errors
124             template = self.loader.load("%s.html" % step)
125             html =  template.generate(**data).render('html', doctype='html')
126             res = self.get_response(html)
127             return res(environ, start_response)
128
129         environ['trac.env_parent_dir'] = self.directory
130
131         # could otherwise take over the index.html serving ourselves
132         environ['trac.env_index_template'] = os.path.join(template_directory, 'index.html')
133
134         if not step:  # site index
135             pass      # XXX needed? not for now
136 #            vars = {}
137 #            environ.setdefault('trac.template_vars', {}).update(vars)
138        
139         return dispatch_request(environ, start_response)
140        
141     def get_response(self, text, content_type='text/html'):
142         """returns a response object for HTML/text input"""
143         res = Response(content_type=content_type, body=text)
144         res.content_length = len(res.body)
145         return res
146
147     ### methods for URIs
148     def createProject(self, req, errors=None):
149         """first project creation step:  initial project data"""
150         data = {}
151         data['URL'] = req.url.rsplit('create-project', 1)[0]
152         data['projects'] = self.available_templates
153         data['next'] = 'Project Details'
154         return data
155
156     def validateCreateProject(self, req):
157
158         # check for errors
159         errors = []       
160         project = req.POST.get('project')
161         if project:
162             if project in self.projects: # TODO check for existing trac projects
163                 errors.append("The project '%s' already exists" % project)
164             else:
165                 self.projects[project] = {} # new project
166         else:
167             errors.append('No project URL specified')
168
169
170         project_type = req.POST.get('project_type')
171         assert project_type in self.available_templates
172
173         # get the project logo
174         logo = req.POST['logo']
175         logo_file = None
176         if logo:
177             if not logo.startswith('http://') or logo.startswith('https://'):
178                 if os.path.exists(logo):
179                     logo_file = file(logo, 'rb')
180                     logo_file_name = os.path.basename(logo)
181                 else:
182                     errors.append("Logo file not found: %s" % logo)
183
184         if errors:
185             return errors
186
187         # process the request and save necessary data
188         project_data = self.projects[project]
189         project_data['type'] = project_type
190         project_data['vars'] = self.legos.vars.copy()
191         project_data['vars'].update({'project': project,
192                                      'description': req.POST.get('project_name').strip() or project,
193                                      'url': req.POST.get('alternate_url')
194                                      })
195
196         project_data['config'] = {}
197         project_data['config']['header_logo'] = {'link': req.POST['alternate_url'] }
198
199         # get an uploaded logo
200         # note that uploaded logos will override logo files/links
201         # (should this be an error instead?)
202         uploaded_logo = req.POST['logo_file']
203         if isinstance(uploaded_logo, cgi.FieldStorage):
204             logo_file_name = uploaded_logo.filename
205             logo_file = uploaded_logo.file
206
207         project_data['logo_file'] = logo_file
208         if logo_file:
209             logo = 'site/%s' % logo_file_name
210
211         project_data['config']['header_logo']['src'] = logo
212         project_data['vars']['logo'] = logo
213        
214         # TODO:  get the favicon from the alternate URL or create one from the logo
215
216     def projectDetails(self, req, errors=None):
217         """second project creation step: project details
218         svn repo, mailing lists (TODO)
219         """
220         project = req.GET['project']
221         data = {'project': project,
222                 'repositories': [ self.repositories[name] for name in self.available_repositories ],
223                 'excluded_fields': dict((key, value.keys()) for key, value in self.legos.repository_fields(project).items()),
224                 'databases': [ self.databases[name] for name in self.available_databases ] }
225
226         # get the database strings
227         data['db_string'] = {}
228         for database in data['databases']:
229             dbstring = database.db_string()
230             dbstring = string.Template(dbstring).safe_substitute(**self.projects[project]['vars'])
231             template = PasteScriptStringTemplate(dbstring)
232             missing = template.missing()
233             if missing:
234                 vars = vars2dict(None, *database.options)
235                 missing = dict([(i,
236                                  '<input type="text" name="%s-%s" value="%s"/>' % (database.name, i, getattr(vars.get(i), 'default', '')))
237                                 for i in missing])
238                 dbstring = string.Template(dbstring).substitute(**missing)
239                 dbstring = Markup(dbstring)
240             data['db_string'][database.name] = dbstring
241         return data
242
243     def validateProjectDetails(self, req):
244
245         # check for errors
246         errors = []
247         project = req.POST.get('project')
248         project_data = self.projects.get(project)
249         if project_data is None:
250             errors.append('Project not found')
251
252         # repository information
253         project_data['repository'] = None
254         repository = req.POST.get('repository')
255         if repository in self.available_repositories:
256             args = dict((arg.split('%s_' % repository, 1)[1], value)
257                         for arg, value in req.POST.items()
258                         if arg.startswith('%s_' % repository))
259             project_data['repository'] = self.repositories[repository]
260             project_data['vars'].update(args)
261             project_data['vars'].update(self.legos.repository_fields(project).get(repository, {}))
262
263         # database information
264         project_data['database'] = None
265         database = req.POST.get('database')
266         if database in self.available_databases:
267             project_data['database'] = self.databases[database]
268
269         return errors
270
271     def projectVariables(self, req, errors=None):
272         """final project creation step:  filling in the project variables"""
273        
274         project = req.GET.get('project')
275         project_data = self.projects[project]
276         templates = [project_data['type'], project_data['config']]
277         repository = project_data['repository']
278         if repository:
279             templates.append(repository.config())
280         templates = self.legos.project_templates(templates)
281         options = templates.options()
282         for var in project_data['vars']:
283             options.pop(var, None)
284         project_data['templates'] = templates
285         data = {'project': project,
286                 'options': options.values()}
287        
288         return data
289
290     def validateProjectVariables(self, req):
291         errors = []
292
293         # XXX to deprecate ?
294         project = req.POST.get('project')
295         if project not in self.projects:
296             errors.append('Project not found')
297
298         if errors:
299             return errors
300
301         project_data = self.projects[project]
302         project_data['vars'].update(req.POST)
303
304         # create the project
305         self.legos.create_project(project,
306                                   project_data['templates'],
307                                   project_data['vars'],
308                                   repository=project_data['repository'])
309
310         # write the logo_file to its new location
311         logo_file = project_data['logo_file']
312         if logo_file:
313             logo_file_name = os.path.basename(project_data['vars']['logo'])
314             filename = os.path.join(self.directory, project, 'htdocs', logo_file_name)
315             logo = file(filename, 'wb')
316             logo.write(logo_file.read())
317             logo.close()
318            
319         # TODO: favicons from logo or alternate url
320
321         # TODO: add authenticated user to TRAC_ADMIN of the new site
322         # (and redirect to the admin panel?)
323
324        
Note: See TracBrowser for help on using the browser.