File: create_app.py

package info (click to toggle)
python-django-extensions 1.7.4-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,016 kB
  • ctags: 1,342
  • sloc: python: 8,873; makefile: 117
file content (156 lines) | stat: -rw-r--r-- 6,933 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# -*- coding: utf-8 -*-
import os
import re
import sys
import warnings

import django
from django.conf import settings
from django.core.management.base import CommandError, BaseCommand
from django.db import connection
from django.template import Context, Template

import django_extensions
from django_extensions.management.utils import _make_writeable, signalcommand
from django_extensions.settings import REPLACEMENTS
from django_extensions.utils.dia2django import dia2django


class Command(BaseCommand):
    help = "Creates an application directory structure for the specified application name."

    requires_system_checks = False
    can_import_settings = True

    def add_arguments(self, parser):
        parser.add_argument('app_name')
        parser.add_argument(
            '--template', '-t', action='store', dest='app_template',
            help='The path to the app template')
        parser.add_argument(
            '--parent_path', '-p', action='store', dest='parent_path',
            help='The parent path of the application to be created')
        parser.add_argument(
            '-d', action='store_true', dest='dia_parse',
            help='Generate model.py and admin.py from [APP_NAME].dia file')
        parser.add_argument(
            '--diagram', action='store', dest='dia_path',
            help='The diagram path of the app to be created. -d is implied')

    @signalcommand
    def handle(self, *args, **options):
        from django_extensions.utils.deprecation import MarkedForDeprecationWarning
        warnings.warn(
            "Deprecated: "
            "\"create_app\" is marked for depreciaton and will most likely "
            "be removed in future releases. Use \"startapp --template\" instead.",
            MarkedForDeprecationWarning
        )
        if django.VERSION[:2] >= (1, 10):
            raise CommandError("This command is deprecated. Please use \"startapp --template\" instead.")

        project_dir = os.getcwd()
        project_name = os.path.split(project_dir)[-1]
        app_name = options['app_name']
        app_template = options.get('app_template') or os.path.join(django_extensions.__path__[0], 'conf', 'app_template')
        app_dir = os.path.join(options.get('parent_path') or project_dir, app_name)
        dia_path = options.get('dia_path') or os.path.join(project_dir, '%s.dia' % app_name)

        if not os.path.exists(app_template):
            raise CommandError("The template path, %r, does not exist." % app_template)

        if not re.search(r'^\w+$', app_name):
            raise CommandError("%r is not a valid application name. Please use only numbers, letters and underscores." % app_name)

        dia_parse = options.get('dia_path') or options.get('dia_parse')
        if dia_parse:
            if not os.path.exists(dia_path):
                raise CommandError("The diagram path, %r, does not exist." % dia_path)
            if app_name in settings.INSTALLED_APPS:
                raise CommandError("The application %s should not be defined in the settings file. Please remove %s now, and add it after using this command." % (app_name, app_name))
            tables = [name for name in connection.introspection.table_names() if name.startswith('%s_' % app_name)]
            if tables:
                raise CommandError("%r application has tables in the database. Please delete them." % app_name)

        try:
            os.makedirs(app_dir)
        except OSError as e:
            raise CommandError(e)

        copy_template(app_template, app_dir, project_name, app_name)

        if dia_parse:
            generate_models_and_admin(dia_path, app_dir, project_name, app_name)

        self.stdout.write("Application %r created.\n" % app_name)
        self.stdout.write("Please add now %r and any other dependent application in settings.INSTALLED_APPS, and run 'manage syncdb'\n" % app_name)


def copy_template(app_template, copy_to, project_name, app_name):
    """copies the specified template directory to the copy_to location"""
    import shutil

    app_template = os.path.normpath(app_template)
    # walks the template structure and copies it
    for d, subdirs, files in os.walk(app_template):
        relative_dir = d[len(app_template) + 1:]
        d_new = os.path.join(copy_to, relative_dir).replace('app_name', app_name)
        if relative_dir and not os.path.exists(d_new):
            os.mkdir(d_new)
        for i, subdir in enumerate(subdirs):
            if subdir.startswith('.'):
                del subdirs[i]
        replacements = {'app_name': app_name, 'project_name': project_name}
        replacements.update(REPLACEMENTS)
        for f in files:
            if f.endswith('.pyc') or f.startswith('.DS_Store'):
                continue
            path_old = os.path.join(d, f)
            path_new = os.path.join(d_new, f.replace('app_name', app_name))
            if os.path.exists(path_new):
                path_new = os.path.join(d_new, f)
                if os.path.exists(path_new):
                    continue
            if path_new.endswith('.tmpl'):
                path_new = path_new[:-5]
            fp_old = open(path_old, 'r')
            fp_new = open(path_new, 'w')
            fp_new.write(Template(fp_old.read()).render(Context(replacements)))
            fp_old.close()
            fp_new.close()
            try:
                shutil.copymode(path_old, path_new)
                _make_writeable(path_new)
            except OSError:
                sys.stderr.write("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new)


def generate_models_and_admin(dia_path, app_dir, project_name, app_name):
    """Generates the models.py and admin.py files"""

    def format_text(string, indent=False):
        """format string in lines of 80 or less characters"""
        retval = ''
        while string:
            line = string[:77]
            last_space = line.rfind(' ')
            if last_space != -1 and len(string) > 77:
                retval += "%s \\\n" % string[:last_space]
                string = string[last_space + 1:]
            else:
                retval += "%s\n" % string
                string = ''
            if string and indent:
                string = '    %s' % string
        return retval

    model_path = os.path.join(app_dir, 'models.py')
    admin_path = os.path.join(app_dir, 'admin.py')

    models_txt = 'from django.db import models\n' + dia2django(dia_path)
    open(model_path, 'w').write(models_txt)

    classes = re.findall('class (\w+)', models_txt)
    admin_txt = 'from django.contrib.admin import site, ModelAdmin\n' + format_text('from %s.%s.models import %s' % (project_name, app_name, ', '.join(classes)), indent=True)
    admin_txt += format_text('\n\n%s' % '\n'.join(map((lambda t: 'site.register(%s)' % t), classes)))
    open(admin_path, 'w').write(admin_txt)