source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/applications/admin/controllers/wizard.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 23.2 KB
Line 
1# -*- coding: utf-8 -*-
2
3import os
4import uuid
5import re
6import pickle
7import urllib
8import glob
9from gluon.admin import app_create, plugin_install
10from gluon.fileutils import abspath, read_file, write_file, open_file
11
12
13def reset(session):
14    session.app = {
15        'name': '',
16        'params': [('title', 'My New App'),
17                   ('subtitle', 'powered by web2py'),
18                   ('author', 'you'),
19                   ('author_email', 'you@example.com'),
20                   ('keywords', ''),
21                   ('description', ''),
22                   ('layout_theme', 'Default'),
23                   ('database_uri', 'sqlite://storage.sqlite'),
24                   ('security_key', str(uuid.uuid4())),
25                   ('email_server', 'localhost'),
26                   ('email_sender', 'you@example.com'),
27                   ('email_login', ''),
28                   ('login_method', 'local'),
29                   ('login_config', ''),
30                   ('plugins', [])],
31        'tables': ['auth_user'],
32        'table_auth_user': ['username', 'first_name',
33                            'last_name', 'email', 'password'],
34        'pages': ['index', 'error'],
35        'page_index': '# Welcome to my new app',
36        'page_error': '# Error: the document does not exist',
37    }
38
39if not session.app:
40    reset(session)
41
42
43def listify(x):
44    if not isinstance(x, (list, tuple)):
45        return x and [x] or []
46    return x
47
48
49def clean(name):
50    return re.sub('\W+', '_', name.strip().lower())
51
52
53def index():
54    response.view = 'wizard/step.html'
55    reset(session)
56    apps = os.listdir(os.path.join(request.folder, '..'))
57    form = SQLFORM.factory(Field('name', requires=[IS_NOT_EMPTY(),
58                                                   IS_ALPHANUMERIC()]), _class='span5 well well-small')
59    if form.accepts(request.vars):
60        app = form.vars.name
61        session.app['name'] = app
62        if MULTI_USER_MODE and db(db.app.name == app)(db.app.owner != auth.user.id).count():
63            session.flash = 'App belongs already to other user'
64        elif app in apps:
65            meta = os.path.normpath(
66                os.path.join(os.path.normpath(request.folder),
67                             '..', app, 'wizard.metadata'))
68            if os.path.exists(meta):
69                try:
70                    metafile = open_file(meta, 'rb')
71                    try:
72                        session.app = pickle.load(metafile)
73                    finally:
74                        metafile.close()
75                    session.flash = T("The app exists, was created by wizard, continue to overwrite!")
76                except:
77                    session.flash = T("The app exists, was NOT created by wizard, continue to overwrite!")
78        redirect(URL('step1'))
79    return dict(step='Start', form=form)
80
81
82def step1():
83    from json import loads
84    import urllib
85    if not session.themes:
86        #url = LAYOUTS_APP + '/default/layouts.json'
87        #try:
88        #    data = urllib.urlopen(url).read()
89        #    session.themes = ['Default'] + loads(data)['layouts']
90        #except:
91        session.themes = ['Default']
92    themes = session.themes
93    if not session.plugins:
94        #url = PLUGINS_APP + '/default/plugins.json'
95        #try:
96        #    data = urllib.urlopen(url).read()
97        #    session.plugins = loads(data)['plugins']
98        #except:
99        session.plugins = []
100    plugins = [x.split('.')[2] for x in session.plugins]
101    response.view = 'wizard/step.html'
102    params = dict(session.app['params'])
103    form = SQLFORM.factory(
104        Field('title', default=params.get('title', None),
105              requires=IS_NOT_EMPTY()),
106        Field('subtitle', default=params.get('subtitle', None)),
107        Field('author', default=params.get('author', None)),
108        Field(
109            'author_email', default=params.get('author_email', None)),
110        Field('keywords', default=params.get('keywords', None)),
111        Field('description', 'text',
112              default=params.get('description', None)),
113        Field('layout_theme', requires=IS_IN_SET(themes),
114              default=params.get('layout_theme', themes[0])),
115        Field(
116            'database_uri', default=params.get('database_uri', None)),
117        Field(
118            'security_key', default=params.get('security_key', None)),
119        Field(
120            'email_server', default=params.get('email_server', None)),
121        Field(
122            'email_sender', default=params.get('email_sender', None)),
123        Field('email_login', default=params.get('email_login', None)),
124        Field('login_method', requires=IS_IN_SET(('local', 'janrain')),
125              default=params.get('login_method', 'local')),
126        Field(
127            'login_config', default=params.get('login_config', None)),
128        Field('plugins', 'list:string', requires=IS_IN_SET(plugins, multiple=True)),
129        _class='span7 well well-small')
130
131    if form.accepts(request.vars):
132        session.app['params'] = [(key, form.vars.get(key, None))
133                                 for key, value in session.app['params']]
134        redirect(URL('step2')  + '/#xwizard_form')
135    return dict(step='1: Setting Parameters', form=form)
136
137
138def step2():
139    response.view = 'wizard/step.html'
140    form = SQLFORM.factory(Field('table_names', 'list:string',
141                                 default=session.app['tables']), _class="span7 well well-small")
142    if form.accepts(request.vars):
143        table_names = [clean(t) for t in listify(form.vars.table_names)
144                       if t.strip()]
145        if [t for t in table_names if t.startswith('auth_') and
146                not t == 'auth_user']:
147            form.error.table_names = \
148                T('invalid table names (auth_* tables already defined)')
149        else:
150            session.app['tables'] = table_names
151            for table in session.app['tables']:
152                if not 'table_' + table in session.app:
153                    session.app['table_' + table] = ['name']
154                if not table == 'auth_user':
155                    name = table + '_manage'
156                    if not name in session.app['pages']:
157                        session.app['pages'].append(name)
158                        session.app['page_' + name] = \
159                            '## Manage %s\n\n{{=form}}' % (table)
160            if session.app['tables']:
161                redirect(URL('step3', args=0) + '/#xwizard_form')
162            else:
163                redirect(URL('step4') + '/#xwizard_form')
164    return dict(step='2: Tables', form=form)
165
166
167def step3():
168    response.view = 'wizard/step.html'
169    n = int(request.args(-1) or 0)
170    m = len(session.app['tables'])
171    if n >= m:
172        redirect(URL('step2'))
173    table = session.app['tables'][n]
174    form = SQLFORM.factory(Field('field_names', 'list:string',
175                                 default=session.app.get('table_' + table, [])), _class="span7 well well-small")
176    if form.accepts(request.vars) and form.vars.field_names:
177        fields = listify(form.vars.field_names)
178        if table == 'auth_user':
179            for field in ['first_name', 'last_name', 'username', 'email', 'password']:
180                if not field in fields:
181                    fields.append(field)
182        session.app['table_' + table] = [t.strip().lower()
183                                         for t in listify(form.vars.field_names)
184                                         if t.strip()]
185        try:
186            tables = sort_tables(session.app['tables'])
187        except RuntimeError:
188            response.flash = T('invalid circular reference')
189        else:
190            if n < m - 1:
191                redirect(URL('step3', args=n + 1) + '/#xwizard_form')
192            else:
193                redirect(URL('step4') + '/#xwizard_form')
194    return dict(step='3: Fields for table "%s" (%s of %s)'
195                % (table, n + 1, m), table=table, form=form)
196
197
198def step4():
199    response.view = 'wizard/step.html'
200    form = SQLFORM.factory(Field('pages', 'list:string',
201                                 default=session.app['pages']), _class="span7 well well-small")
202    if form.accepts(request.vars):
203        session.app['pages'] = [clean(t)
204                                for t in listify(form.vars.pages)
205                                if t.strip()]
206        if session.app['pages']:
207            redirect(URL('step5', args=0) + '/#xwizard_form')
208        else:
209            redirect(URL('step6') + '/#xwizard_form')
210    return dict(step='4: Pages', form=form)
211
212
213def step5():
214    response.view = 'wizard/step.html'
215    n = int(request.args(-1) or 0)
216    m = len(session.app['pages'])
217    if n >= m:
218        redirect(URL('step4'))
219    page = session.app['pages'][n]
220    markmin_url = 'http://web2py.com/examples/static/markmin.html'
221    form = SQLFORM.factory(Field('content', 'text',
222                                 default=session.app.get('page_' + page, []),
223                                 comment=A('use markmin',
224                                           _href=markmin_url, _target='_blank')),
225                           formstyle='table2cols', _class="span7 well well-small")
226    if form.accepts(request.vars):
227        session.app['page_' + page] = form.vars.content
228        if n < m - 1:
229            redirect(URL('step5', args=n + 1) + '/#xwizard_form')
230        else:
231            redirect(URL('step6') + '/#xwizard_form')
232    return dict(step='5: View for page "%s" (%s of %s)' % (page, n + 1, m), form=form)
233
234
235def step6():
236    response.view = 'wizard/step.html'
237    params = dict(session.app['params'])
238    app = session.app['name']
239    form = SQLFORM.factory(
240        Field('generate_model', 'boolean', default=True),
241        Field('generate_controller', 'boolean', default=True),
242        Field('generate_views', 'boolean', default=True),
243        Field('generate_menu', 'boolean', default=True),
244        Field('apply_layout', 'boolean', default=True),
245        Field('erase_database', 'boolean', default=True),
246        Field('populate_database', 'boolean', default=True),
247        _id="generate_form", _class="form-horizontal span7 well well-small")
248    if form.accepts(request.vars):
249        if DEMO_MODE:
250            session.flash = T('Application cannot be generated in demo mode')
251            redirect(URL('index'))
252        create(form.vars)
253        session.flash = 'Application %s created' % app
254        redirect(URL('generated'))
255    return dict(step='6: Generate app "%s"' % app, form=form)
256
257
258def generated():
259    return dict(app=session.app['name'])
260
261
262def sort_tables(tables):
263    import re
264    regex = re.compile('(%s)' % '|'.join(tables))
265    is_auth_user = 'auth_user' in tables
266    d = {}
267    for table in tables:
268        d[table] = []
269        for field in session.app['table_%s' % table]:
270            d[table] += regex.findall(field)
271    tables = []
272    if is_auth_user:
273        tables.append('auth_user')
274
275    def append(table, trail=[]):
276        if table in trail:
277            raise RuntimeError
278        for t in d[table]:
279            # if not t==table: (problem, no dropdown for self references)
280            append(t, trail=trail + [table])
281        if not table in tables:
282            tables.append(table)
283    for table in d:
284        append(table)
285    return tables
286
287
288def make_table(table, fields):
289    rawtable = table
290    if table != 'auth_user':
291        table = 't_' + table
292    s = ''
293    s += '\n' + '#' * 40 + '\n'
294    s += "db.define_table('%s',\n" % table
295    first_field = 'id'
296    for field in fields:
297        items = [x.lower() for x in field.split()]
298        has = {}
299        keys = []
300        for key in ['notnull', 'unique', 'integer', 'double', 'boolean', 'float',
301                    'boolean', 'date', 'time', 'datetime', 'text', 'wiki',
302                    'html', 'file', 'upload', 'image', 'true',
303                    'hidden', 'readonly', 'writeonly', 'multiple',
304                    'notempty', 'required']:
305            if key in items[1:]:
306                keys.append(key)
307                has[key] = True
308        tables = session.app['tables']
309        refs = [t for t in tables if t in items]
310        items = items[:1] + [x for x in items[1:]
311                             if not x in keys and not x in tables]
312        barename = name = '_'.join(items)
313        if table[:2] == 't_': name = 'f_' + name
314        if first_field == 'id':
315            first_field = name
316
317        ### determine field type
318        ftype = 'string'
319        deftypes = {'integer': 'integer', 'double': 'double', 'boolean': 'boolean',
320                    'float': 'double', 'bool': 'boolean',
321                    'date': 'date', 'time': 'time', 'datetime': 'datetime',
322                    'text': 'text', 'file': 'upload', 'image': 'upload',
323                    'upload': 'upload', 'wiki': 'text', 'html': 'text'}
324        for key, t in deftypes.items():
325            if key in has:
326                ftype = t
327        if refs:
328            key = refs[0]
329            if not key == 'auth_user':
330                key = 't_' + key
331            if 'multiple' in has:
332                ftype = 'list:reference %s' % key
333            else:
334                ftype = 'reference %s' % key
335        if ftype == 'string' and 'multiple' in has:
336            ftype = 'list:string'
337        elif ftype == 'integer' and 'multiple' in has:
338            ftype = 'list:integer'
339        elif name == 'password':
340            ftype = 'password'
341        s += "    Field('%s', type='%s'" % (name, ftype)
342
343        ### determine field attributes
344        if 'notnull' in has or 'notempty' in has or 'required' in has:
345            s += ', notnull=True'
346        if 'unique' in has:
347            s += ', unique=True'
348        if ftype == 'boolean' and 'true' in has:
349            s += ",\n          default=True"
350
351        ### determine field representation
352        elif 'wiki' in has:
353            s += ",\n          represent=lambda x, row: MARKMIN(x)"
354            s += ",\n          comment='WIKI (markmin)'"
355        elif 'html' in has:
356            s += ",\n          represent=lambda x, row: XML(x,sanitize=True)"
357            s += ",\n          comment='HTML (sanitized)'"
358        ### determine field access
359        if name == 'password' or 'writeonly' in has:
360            s += ",\n          readable=False"
361        elif 'hidden' in has:
362            s += ",\n          writable=False, readable=False"
363        elif 'readonly' in has:
364            s += ",\n          writable=False"
365
366        ### make up a label
367        s += ",\n          label=T('%s')),\n" % \
368            ' '.join(x.capitalize() for x in barename.split('_'))
369    if table == 'auth_user':
370        s += "    Field('created_on','datetime',default=request.now,\n"
371        s += "          label=T('Created On'),writable=False,readable=False),\n"
372        s += "    Field('modified_on','datetime',default=request.now,\n"
373        s += "          label=T('Modified On'),writable=False,readable=False,\n"
374        s += "          update=request.now),\n"
375        s += "    Field('registration_key',default='',\n"
376        s += "          writable=False,readable=False),\n"
377        s += "    Field('reset_password_key',default='',\n"
378        s += "          writable=False,readable=False),\n"
379        s += "    Field('registration_id',default='',\n"
380        s += "          writable=False,readable=False),\n"
381    elif 'auth_user' in session.app['tables']:
382        s += "    auth.signature,\n"
383    s += "    format='%(" + first_field + ")s',\n"
384    s += "    migrate=settings.migrate)\n\n"
385    if table == 'auth_user':
386        s += """
387db.auth_user.first_name.requires = IS_NOT_EMPTY(
388    error_message=auth.messages.is_empty)
389db.auth_user.last_name.requires = IS_NOT_EMPTY(
390    error_message=auth.messages.is_empty)
391db.auth_user.password.requires = CRYPT(
392    key=auth.settings.hmac_key, min_length=4)
393db.auth_user.username.requires = IS_NOT_IN_DB(db, db.auth_user.username)
394db.auth_user.email.requires = (
395    IS_EMAIL(error_message=auth.messages.invalid_email),
396                               IS_NOT_IN_DB(db, db.auth_user.email))
397"""
398    else:
399        s += "db.define_table('%s_archive',db.%s,Field('current_record','reference %s',readable=False,writable=False))\n" % (table, table, table)
400    return s
401
402
403def fix_db(filename):
404    params = dict(session.app['params'])
405    content = read_file(filename, 'r')
406    if 'auth_user' in session.app['tables']:
407        auth_user = make_table('auth_user', session.app['table_auth_user'])
408        content = content.replace('sqlite://storage.sqlite',
409                                  params['database_uri'])
410        content = content.replace('auth.define_tables()',
411                                  auth_user + 'auth.define_tables(migrate = settings.migrate)')
412    content += """
413mail.settings.server = settings.email_server
414mail.settings.sender = settings.email_sender
415mail.settings.login = settings.email_login
416"""
417    if params['login_method'] == 'janrain':
418        content += """
419from gluon.contrib.login_methods.rpx_account import RPXAccount
420auth.settings.actions_disabled=['register','change_password',
421    'request_reset_password']
422auth.settings.login_form = RPXAccount(request,
423    api_key = settings.login_config.split(':')[-1],
424    domain = settings.login_config.split(':')[0],
425    url = "http://%s/%s/default/user/login" % (request.env.http_host,request.application))
426"""
427    write_file(filename, content, 'w')
428
429
430def make_menu(pages):
431    s = ''
432    s += 'response.title = settings.title\n'
433    s += 'response.subtitle = settings.subtitle\n'
434    s += "response.meta.author = '%(author)s <%(author_email)s>' % settings\n"
435    s += 'response.meta.keywords = settings.keywords\n'
436    s += 'response.meta.description = settings.description\n'
437    s += 'response.menu = [\n'
438    for page in pages:
439        if not page.startswith('error'):
440            if page.endswith('_manage'):
441                page_name = page[:-7]
442            else:
443                page_name = page
444            page_name = ' '.join(x.capitalize() for x in page_name.split('_'))
445            s += "(T('%s'),URL('default','%s')==URL(),URL('default','%s'),[]),\n" \
446                % (page_name, page, page)
447    s += ']'
448    return s
449
450
451def make_page(page, contents):
452    if 'auth_user' in session.app['tables'] and not page in ('index', 'error'):
453        s = "@auth.requires_login()\ndef %s():\n" % page
454    else:
455        s = "def %s():\n" % page
456    items = page.rsplit('_', 1)
457    if items[0] in session.app['tables'] and len(items) == 2 and items[1] == 'manage':
458        s += "    form = SQLFORM.smartgrid(db.t_%s,onupdate=auth.archive)\n" % items[0]
459        s += "    return locals()\n\n"
460    else:
461        s += "    return dict()\n\n"
462    return s
463
464
465def make_view(page, contents):
466    s = "{{extend 'layout.html'}}\n\n"
467    s += str(MARKMIN(contents))
468    return s
469
470
471def populate(tables):
472    s = 'from gluon.contrib.populate import populate\n'
473    s += 'if db(db.auth_user).isempty():\n'
474    for table in sort_tables(tables):
475        t = table == 'auth_user' and 'auth_user' or 't_' + table
476        s += "     populate(db.%s,10)\n" % t
477    return s
478
479
480def create(options):
481    if DEMO_MODE:
482        session.flash = T('disabled in demo mode')
483        redirect(URL('step6'))
484    params = dict(session.app['params'])
485    app = session.app['name']
486    if app_create(app, request, force=True, key=params['security_key']):
487        if MULTI_USER_MODE:
488            db.app.insert(name=app, owner=auth.user.id)
489    else:
490        session.flash = 'Failure to create application'
491        redirect(URL('step6'))
492
493    ### save metadata in newapp/wizard.metadata
494    try:
495        meta = os.path.join(request.folder, '..', app, 'wizard.metadata')
496        file = open_file(meta, 'wb')
497        pickle.dump(session.app, file)
498        file.close()
499    except IOError:
500        session.flash = 'Failure to write wizard metadata'
501        redirect(URL('step6'))
502
503    ### apply theme
504    if options.apply_layout and params['layout_theme'] != 'Default':
505        try:
506            fn = 'web2py.plugin.layout_%s.w2p' % params['layout_theme']
507            theme = urllib.urlopen(
508                LAYOUTS_APP + '/static/plugin_layouts/plugins/' + fn)
509            plugin_install(app, theme, request, fn)
510        except:
511            session.flash = T("unable to download layout")
512
513    ### apply plugins
514    for plugin in params['plugins']:
515        try:
516            plugin_name = 'web2py.plugin.' + plugin + '.w2p'
517            stream = urllib.urlopen(PLUGINS_APP + '/static/' + plugin_name)
518            plugin_install(app, stream, request, plugin_name)
519        except Exception as e:
520            session.flash = T("unable to download plugin: %s" % plugin)
521
522    ### write configuration file into newapp/models/0.py
523    model = os.path.join(request.folder, '..', app, 'models', '0.py')
524    file = open_file(model, 'w')
525    try:
526        file.write("from gluon.storage import Storage\n")
527        file.write("settings = Storage()\n\n")
528        file.write("settings.migrate = True\n")
529        for key, value in session.app['params']:
530            file.write("settings.%s = %s\n" % (key, repr(value)))
531    finally:
532        file.close()
533
534    ### write configuration file into newapp/models/menu.py
535    if options.generate_menu:
536        model = os.path.join(request.folder, '..', app, 'models', 'menu.py')
537        file = open_file(model, 'w')
538        try:
539            file.write(make_menu(session.app['pages']))
540        finally:
541            file.close()
542
543    ### customize the auth_user table
544    model = os.path.join(request.folder, '..', app, 'models', 'db.py')
545    fix_db(model)
546
547    ### create newapp/models/db_wizard.py
548    if options.generate_model:
549        model = os.path.join(
550            request.folder, '..', app, 'models', 'db_wizard.py')
551        file = open_file(model, 'w')
552        try:
553            file.write('### we prepend t_ to tablenames and f_ to fieldnames for disambiguity\n\n')
554            tables = sort_tables(session.app['tables'])
555            for table in tables:
556                if table == 'auth_user':
557                    continue
558                file.write(make_table(table, session.app['table_' + table]))
559        finally:
560            file.close()
561
562    model = os.path.join(request.folder, '..', app,
563                         'models', 'db_wizard_populate.py')
564    if os.path.exists(model):
565        os.unlink(model)
566    if options.populate_database and session.app['tables']:
567        file = open_file(model, 'w')
568        try:
569            file.write(populate(session.app['tables']))
570        finally:
571            file.close()
572
573    ### create newapp/controllers/default.py
574    if options.generate_controller:
575        controller = os.path.join(
576            request.folder, '..', app, 'controllers', 'default.py')
577        file = open_file(controller, 'w')
578        try:
579            file.write("""# -*- coding: utf-8 -*-
580### required - do no delete
581def user(): return dict(form=auth())
582def download(): return response.download(request,db)
583def call(): return service()
584### end requires
585""")
586            for page in session.app['pages']:
587                file.write(
588                    make_page(page, session.app.get('page_' + page, '')))
589        finally:
590            file.close()
591
592    ### create newapp/views/default/*.html
593    if options.generate_views:
594        for page in session.app['pages']:
595            view = os.path.join(
596                request.folder, '..', app, 'views', 'default', page + '.html')
597            file = open_file(view, 'w')
598            try:
599                file.write(
600                    make_view(page, session.app.get('page_' + page, '')))
601            finally:
602                file.close()
603
604    if options.erase_database:
605        path = os.path.join(request.folder, '..', app, 'databases', '*')
606        for file in glob.glob(path):
607            os.unlink(file)
Note: See TracBrowser for help on using the repository browser.