├── djangox ├── __init__.py ├── deploy │ ├── __init__.py │ ├── requirements.txt │ ├── .gitignore │ ├── bin │ │ ├── loadenv │ │ ├── djangorc │ │ └── backup-postgresql.sh │ ├── uwsgi.conf │ ├── uwsgi.ini │ ├── nginx-site │ ├── README.md │ ├── deployconfig_sample.py │ ├── production.py.template │ └── fabfile.py ├── apps │ ├── bs4tl │ │ ├── __init__.py │ │ ├── templates │ │ │ └── bs4tl │ │ │ │ ├── list.html │ │ │ │ ├── dialog.html │ │ │ │ ├── nav.html │ │ │ │ ├── server_error.html │ │ │ │ ├── not_found.html │ │ │ │ ├── permission_denied.html │ │ │ │ ├── paginator.html │ │ │ │ └── forms.html │ │ └── static │ │ │ └── bs4tl.css │ ├── tools │ │ ├── __init__.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ ├── setupdeploy.py │ │ │ │ ├── importapp.py │ │ │ │ └── setupstatic.py │ │ └── setupstatic │ │ │ ├── .bowerrc │ │ │ └── bower.json │ ├── unilogin │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── __init__.py │ │ │ └── unilogin │ │ │ │ ├── __init__.py │ │ │ │ └── login.html │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ └── accounts.py │ │ ├── static │ │ │ └── unilogin.css │ │ └── urls.py │ └── __init__.py ├── mako │ ├── staticfiles.py │ └── __init__.py └── route │ ├── __init__.py │ └── rest.py ├── requirements.txt ├── .gitignore ├── setup.py └── README.md /djangox/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/deploy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/unilogin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | mako -------------------------------------------------------------------------------- /djangox/apps/tools/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/unilogin/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/unilogin/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/tools/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/unilogin/templates/unilogin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/deploy/requirements.txt: -------------------------------------------------------------------------------- 1 | fabric 2 | fabtools -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist 3 | build 4 | *.egg-info 5 | .idea 6 | -------------------------------------------------------------------------------- /djangox/deploy/.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | id_deploy* 3 | deployconfig.py -------------------------------------------------------------------------------- /djangox/deploy/bin/loadenv: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | bash --rcfile ~/bin/djangorc -------------------------------------------------------------------------------- /djangox/apps/tools/setupstatic/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory":"${app_name}/static/lib" 3 | } 4 | -------------------------------------------------------------------------------- /djangox/deploy/bin/djangorc: -------------------------------------------------------------------------------- 1 | . ~/.bashrc 2 | . ~/venv/bin/activate 3 | export DJANGO_SETTINGS_MODULE=%(project_name)s.production 4 | export LC_ALL=en_US.UTF-8 5 | cd ~/%(project_name)s -------------------------------------------------------------------------------- /djangox/apps/tools/setupstatic/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "${app_name}", 3 | "dependencies": { 4 | "bootstrap": "", 5 | "font-awesome": "", 6 | "jquery": "" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/list.html: -------------------------------------------------------------------------------- 1 | <%page args="object_list, path"/> 2 |
${_(field.help_text)}
23 | % endif 24 | % for error in field.errors: 25 |${ _(error) }
26 | % endfor 27 |${ error }
93 | % endfor 94 | %def> 95 | 96 | <%def name="CheckboxInput(field, prefix='')">\ 97 | \ 98 | %def> 99 | 100 | <%def name="csrf_token_input()"> 101 | 102 | %def> -------------------------------------------------------------------------------- /djangox/deploy/fabfile.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from fabric.context_managers import cd, settings 3 | from fabric.operations import run, sudo 4 | from fabric.contrib.files import exists 5 | from fabric.decorators import task, roles 6 | from fabric.state import env 7 | from fabtools import require 8 | import fabtools 9 | from fabtools.python import virtualenv 10 | import deployconfig 11 | 12 | 13 | env.user = 'ubuntu' 14 | env.home = '/home/' + env.user 15 | env.project_path = env.home + '/' + env.project_name 16 | env.static_path = env.home + '/static' 17 | env.deploy_key_path = env.home + '/.ssh/' + 'id_deploy' 18 | env.virtualenv = env.home + '/venv' 19 | env.backup_dir = '/mnt/data' 20 | 21 | 22 | def checkout(url, directory): 23 | if exists(directory): 24 | with cd(directory): 25 | run("ssh-agent bash -c 'ssh-add %s; git pull'" % env.deploy_key_path) 26 | 27 | else: 28 | with cd(env.home): 29 | run("ssh-agent bash -c 'ssh-add %s; git clone %s'" % (env.deploy_key_path, url)) 30 | 31 | 32 | @task 33 | @roles('web') 34 | def setup_web(): 35 | require.deb.packages(['git', 'npm', 'nodejs-legacy', 'python3-dev', 'libxml2-dev', 'libxslt1-dev', 'libpq-dev']) 36 | require.nginx.server() 37 | require.nginx.site(env.server_name, template_source='nginx-site', 38 | port=80, 39 | server_alias='', 40 | static_path=env.static_path) 41 | # require.nginx.disabled('default') 42 | 43 | with settings(warn_only=True): 44 | if run('type bower').return_code: 45 | sudo('npm install -g bower') 46 | 47 | update_source() 48 | 49 | require.directory(env.home + '/logs') 50 | require.python.packages(['uwsgi', 'virtualenv', 'ipython', 'celery'], use_sudo=True) 51 | require.file('/etc/init/uwsgi.conf', source='uwsgi.conf', use_sudo=True) 52 | # require.file('/etc/init/celery.conf', source='celery.conf', use_sudo=True) 53 | require.directory('/etc/uwsgi', use_sudo=True) 54 | require.files.template_file('/etc/uwsgi/%s.ini' % env.project_name, 55 | template_source='uwsgi.ini', 56 | context=env, use_sudo=True) 57 | require.service.started('uwsgi') 58 | require.directory(env.home + '/bin') 59 | require.files.template_file(env.home + '/bin/djangorc', 60 | template_source='bin/djangorc', 61 | context=env) 62 | require.files.template_file(env.home + '/bin/loadenv', 63 | template_source='bin/loadenv', 64 | context=env) 65 | run('chmod +x ' + env.home + '/bin/loadenv') 66 | 67 | # require.service.started('celery') 68 | 69 | 70 | @task 71 | @roles('db') 72 | def setup_db(): 73 | require.deb.packages(['postgresql', 'libpq-dev']) 74 | if not fabtools.postgres.user_exists(env.db_user): 75 | fabtools.postgres.create_user(env.db_user, env.db_password, createdb=True) 76 | 77 | if not fabtools.postgres.database_exists(env.project_name): 78 | fabtools.postgres.create_database(env.project_name, env.project_name) 79 | 80 | # require.python.package('awscli', use_sudo=True) 81 | # require.files.directory(env.home + '/.aws') 82 | # require.files.template_file(env.home + '/.aws', 83 | # template_source='.aws/config', context=env) 84 | # require.files.template_file(env.home + '/.aws', 85 | # template_source='.aws/credentials', context=env) 86 | # run('chmod 0600 ' + env.home + '/.aws/*') 87 | 88 | require.directory(env.backup_dir, use_sudo=True) 89 | sudo('chown ubuntu.ubuntu %s' % env.backup_dir) 90 | # require.files.template_file(env.home + '/bin/backup-postgresql.sh', 91 | # template_source='bin/backup-postgresql.sh', 92 | # context=env) 93 | # run('chmod +x ' + env.home + '/bin/backup-postgresql.sh') 94 | # fabtools.cron.add_task('backup-postgresql', 95 | # '0 0 * * *', 96 | # 'ubuntu', 97 | # '%s/bin/backup-postgresql.sh' % env.home) 98 | 99 | 100 | @task 101 | @roles('web') 102 | def deploy(): 103 | update_source() 104 | require.service.restart('uwsgi') 105 | # require.service.restart('celery') 106 | 107 | 108 | @roles('web') 109 | def update_source(): 110 | require.file(env.deploy_key_path, source='id_deploy') 111 | run('chmod 0600 ' + env.deploy_key_path) 112 | checkout(env.project_repo, env.project_path) 113 | require.files.template_file(env.project_path + '/%s/production.py' % env.project_name, 114 | template_source='production.py.template', context=env) 115 | 116 | require.python.virtualenv(env.virtualenv, venv_python='/usr/bin/python3') 117 | with virtualenv(env.virtualenv): 118 | require.python.requirements(env.project_path + '/requirements.txt') 119 | with cd(env.project_path): 120 | run('bower install') 121 | run('./manage.py collectstatic --settings=%s.production --noinput' % env.project_name) 122 | run('./manage.py migrate --settings=%s.production --noinput' % env.project_name) 123 | -------------------------------------------------------------------------------- /djangox/mako/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import traceback 5 | import types 6 | from importlib import import_module 7 | 8 | from django.conf import settings 9 | from django.core.exceptions import ImproperlyConfigured 10 | from django.http import HttpResponse, HttpResponseServerError 11 | from django.shortcuts import render 12 | from django.template import TemplateDoesNotExist, Origin 13 | from django.template.backends.base import BaseEngine 14 | from django.template.backends.utils import csrf_input_lazy, csrf_token_lazy 15 | from django.template.engine import Engine 16 | from django.templatetags import static 17 | from django.urls import reverse, get_resolver, get_urlconf 18 | from django.utils.translation import gettext 19 | from mako import exceptions 20 | from mako.exceptions import TemplateLookupException 21 | from mako.lookup import TemplateLookup 22 | from mako.template import Template 23 | 24 | from djangox.mako import staticfiles 25 | 26 | 27 | def url(view_name, *args, **kwargs): 28 | try: 29 | return reverse(view_name, args=args, kwargs=kwargs) 30 | except: 31 | resolver = get_resolver(get_urlconf()) 32 | for key in resolver.reverse_dict.keys(): 33 | if isinstance(key, types.FunctionType): 34 | name = key.__module__ + '.' + key.__name__ 35 | else: 36 | name = key 37 | 38 | if name.endswith(view_name): 39 | return reverse(key, args=args, kwargs=kwargs) 40 | 41 | raise 42 | 43 | 44 | default_context = { 45 | 'url' : url, 46 | 'static': staticfiles.static, 47 | '_': gettext, 48 | } 49 | 50 | default_charset = getattr(settings, 'DEFAULT_CHARSET', 'utf8') 51 | 52 | app_template_dirs = [] 53 | fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() 54 | 55 | for app in settings.INSTALLED_APPS: 56 | try: 57 | mod = import_module(app) 58 | except ImportError as e: 59 | raise ImproperlyConfigured('ImportError %s: %s' % (app, e.args[0])) 60 | template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates') 61 | if os.path.isdir(template_dir): 62 | app_template_dirs.append(template_dir) 63 | 64 | 65 | template_lookup = TemplateLookup(directories=app_template_dirs, 66 | input_encoding=default_charset, 67 | output_encoding=default_charset, 68 | ) 69 | 70 | def render_to_response(filename, dictionary, context_instance=None): 71 | ''' 72 | :param filename: 73 | :param dictionary: 74 | :param context_instance: 75 | :return: rendered django HttpResponse 76 | ''' 77 | 78 | dictionary.update(default_context) 79 | 80 | if context_instance: 81 | return render(context_instance.request, filename, dictionary) 82 | 83 | if hasattr(settings, 'MAKO_DEFAULT_CONTEXT'): 84 | dictionary.update(settings.MAKO_DEFAULT_CONTEXT) 85 | 86 | try: 87 | template = template_lookup.get_template(filename) 88 | return HttpResponse(template.render(**dictionary)) 89 | except exceptions.TopLevelLookupException: 90 | raise 91 | except: 92 | traceback.print_exc() 93 | return HttpResponseServerError(exceptions.html_error_template().render()) 94 | 95 | 96 | class MakoTemplateEngine(BaseEngine): 97 | 98 | app_dirname = 'templates' 99 | 100 | def __init__(self, params): 101 | params = params.copy() 102 | options = params.pop('OPTIONS').copy() 103 | options.setdefault('debug', settings.DEBUG) 104 | super().__init__(params) 105 | 106 | self.context_processors = options.pop('context_processors', []) 107 | 108 | self.apps = options.get('apps', None) 109 | self.app_dirs = [] 110 | for app in self.apps: 111 | app_module = import_module(app) 112 | self.app_dirs.append(os.path.abspath(app_module.__path__[0])) 113 | 114 | def from_string(self, template_code): 115 | return MakoTemplateWrapper(Template(text=template_code)) 116 | 117 | def get_template(self, template_name, dirs=None): 118 | try: 119 | mt = template_lookup.get_template(template_name) 120 | except TemplateLookupException as e: 121 | raise TemplateDoesNotExist(str(e)) 122 | 123 | for d in self.app_dirs: 124 | if os.path.abspath(os.path.join(d, 'templates', template_name)) == os.path.abspath(mt.filename): 125 | return MakoTemplateWrapper(mt) 126 | 127 | raise TemplateDoesNotExist("template does not exists in templates directories in specified apps: %s", str(self.apps)) 128 | 129 | 130 | class MakoTemplateWrapper(object): 131 | 132 | def __init__(self, template): 133 | self.template = template 134 | self.backend = Engine.get_default() 135 | self.origin = Origin( 136 | name=template.filename, template_name=template.filename, 137 | ) 138 | 139 | def render(self, context=None, request=None): 140 | if context is None: 141 | context = {} 142 | if request is not None: 143 | context['request'] = request 144 | context['csrf_input'] = csrf_input_lazy(request) 145 | context['csrf_token'] = csrf_token_lazy(request) 146 | for context_processor in self.backend.template_context_processors: 147 | context.update(context_processor(request)) 148 | 149 | context.update(default_context) 150 | if hasattr(settings, 'MAKO_DEFAULT_CONTEXT'): 151 | context.update(settings.MAKO_DEFAULT_CONTEXT) 152 | 153 | try: 154 | return self.template.render(**context) 155 | except exceptions.TopLevelLookupException: 156 | raise 157 | except Exception as e: 158 | logging.error('Template Error\n request: %s\n context: %s', request, context, exc_info=e) 159 | if settings.DEBUG: 160 | return exceptions.html_error_template().render() 161 | else: 162 | raise e 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # djangox 2 | Another way of using django. Convention over configuration for urls, mako template language, json response, REST style API, convenient scripts, ... 3 | 4 | This package was originated from [djangox-mako](http://github.com/youngrok/djangox-mako) and [djangox-route](http://github.com/youngrok/djangox-route). The namespace package of python sucks, so I integrated two packages into one, and planned to add other features. 5 | 6 | ## Install 7 | djangox supports python 3.x and django >= 1.8 only. 8 | 9 | pip install djangox 10 | 11 | ## djangox.route 12 | [Django said tying URLs to Python functions is Bad thing.](https://docs.djangoproject.com/en/dev/misc/design-philosophies/#id8) But registering every view functions in urls.py is not so cool. Even django admin uses autodiscover, what's the reason we don't use autodiscover for normal view functions? 13 | 14 | 15 | ### discover_controllers 16 | Register all controller functions to urlpatterns. 17 | 18 | url(r'', discover_controllers(your_app.controllers)) 19 | 20 | Recommended controller directory structure is like this: 21 | 22 | * your_project 23 | * your_app 24 | * controllers 25 | * \__init__.py 26 | * hello.py 27 | * your_project 28 | * urls.py 29 | 30 | \__init__.py 31 | 32 | def index(request): 33 | ... 34 | 35 | hello.py 36 | 37 | def index(request): 38 | ... 39 | 40 | def world(request): 41 | ... 42 | 43 | def show(request, resource_id): 44 | ... 45 | 46 | def great(request, resource_id): 47 | ... 48 | 49 | Now urls will be dispatched like these: 50 | 51 | * / -> controllers.__init__.index(request) 52 | * /hello -> controllers.hello.index(request) 53 | * /hello/great -> controllers.hello.great(request) 54 | * /hello/5 -> controllers.hello.show(request, 5) 55 | * /hello/5/world -> controllers.hello.world(request, 5) 56 | 57 | You can also use string of package name. 58 | 59 | url(r'', discover_controllers('your_app.controllers')) 60 | 61 | Other features of django url system are available, too. 62 | 63 | url(r'api/', discover_controllers(your_app.controllers)) 64 | 65 | url above will dispatch /api/hello to hello.index(request) 66 | 67 | 68 | ### Notes 69 | * `discover_controllers` doesn't intercept django's url dispatching. It just registers every controllers to urlconf. So every decorators for django views works fine. 70 | 71 | * I don't like the naming, `views`. Every other web frameworks are consist of MVC(model, view, controller), but only django uses the concept of model-view-template. The view of MVC and the view of django are different. It's more like controller in MVC, but not exactly same. Django intend template should be dull, and views should supply all data template needed. View in django are abstract layer for view in MVC. However I didn't find any advantages in django's approach, so I prefer MVC. This is why I name the autodiscover function as `discover_controllers`, not `discover_views`. 72 | 73 | * As I said above, I don't like powerless template engine in django. I think template should have full functionality as programming language. Therefore I recommend mako rather than django template. With [djangox-mako](https://github.com/youngrok/djangox-mako) you can use mako easily. 74 | 75 | 76 | ## djangox.mako 77 | django mako adapter. Now support backend for django template engine (django >= 1.8). Refer to [Support for template engines](https://docs.djangoproject.com/en/1.8/topics/templates/#support-for-template-engines) 78 | 79 | ### template loader 80 | Currently djangox.mako supports only app directories loader. You can put template file in your app directory's subdirectory named 'templates', djangox-mako can access that. 81 | 82 | 83 | ### usage 84 | #### settings.py 85 | see `apps` config. if you don't specify `apps`, MakoTemplateEngine will be also applied to django.contrib.admin or other DjangoTemplates based apps. 86 | 87 | TEMPLATES = [ 88 | { 89 | 'BACKEND': 'djangox.mako.MakoTemplateEngine', 90 | 'DIRS': [], 91 | 'APP_DIRS': True, 92 | 'OPTIONS': { 93 | 'context_processors': [ 94 | 'django.template.context_processors.debug', 95 | 'django.template.context_processors.request', 96 | 'django.contrib.auth.context_processors.auth', 97 | 'django.contrib.messages.context_processors.messages', 98 | 'django.template.context_processors.csrf', 99 | ], 100 | 'apps': [ 101 | 'myapp1', 102 | 'myapp2', 103 | ], 104 | }, 105 | }, 106 | { 107 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 108 | 'DIRS': [], 109 | 'APP_DIRS': True, 110 | 'OPTIONS': { 111 | 'context_processors': [ 112 | 'django.template.context_processors.debug', 113 | 'django.template.context_processors.request', 114 | 'django.contrib.auth.context_processors.auth', 115 | 'django.contrib.messages.context_processors.messages', 116 | ], 117 | }, 118 | }, 119 | ] 120 | 121 | MAKO_DEFAULT_CONTEXT = { 122 | 'humanize': humanize, 123 | 'localize': localize, 124 | '_': ugettext, 125 | } 126 | 127 | #### render 128 | same as using django template. 129 | 130 | def someview(request): 131 | ... 132 | ... 133 | return render(request, 'dir/file.html', locals()) 134 | 135 | #### render_to_response (*deprecated in django 1.8*) 136 | 137 | def someview(request): 138 | ... 139 | ... 140 | return render_to_response('dir/file.html', locals(), RequestContext(request)) 141 | 142 | With RequestContext, you can use context variables related to request, such as csrf_token. 143 | 144 | #### how to use django template tags 145 | ##### url 146 | django template 147 | 148 | {% url 'path.to.some_view' arg1=v1 arg2=v2 %} 149 | 150 | mako 151 | 152 | ${url('path.to.some_view', v1, v2)} 153 | 154 | ##### csrf_token 155 | django template 156 | 157 | {% csrf_token %} 158 | 159 | The code above will be rendered as: 160 | 161 | 162 | 163 | mako 164 | 165 | 166 | 167 | and planning to make the code below possible. Not implemented yet. 168 | 169 | ${csrf_token_tag()} 170 | 171 | ##### static 172 | django template 173 | 174 | {% static 'path/to/style.css' %} 175 | 176 | mako 177 | 178 | ${static('path/to/style.css')} 179 | 180 | #### encoding 181 | Django settings variable DEFAULT_CHARSET will be used for input & output of templates. if None, 'utf8' will be default. 182 | 183 | #### default context 184 | You can inject default context with django settings MAKO_DEFAULT_CONTEXT. It's dict type. It's also possible to use TEMPLATES config in settings.py to provide template context processors. 185 | 186 | ### TODO 187 | * csrf_token_tag() 188 | * settings.TEMPLATE_DEBUG support 189 | * render_to_string 190 | * support filesystem loader (settings.TEMPLATE_DIR) 191 | * any other feature that is supported by django template but not supported by djangox-mako. 192 | 193 | ## bs4tl 194 | common ui template library for web application built on top of bootstrap 4. This templates use mako, not django template. 195 | 196 | ### install 197 | settings.py 198 | 199 | INSTALLED_APPS = ( 200 | ... 201 | ... 202 | 'djangox.apps.tools', 203 | ... 204 | ) 205 | 206 | run import command 207 | 208 | ./manage.py importapp djangox.apps.bs4tl 209 | 210 | ### forms 211 | The code generated by django forms sucks. bs4tl provides better html code with bootstrap 4. 212 | 213 | import using mako namespace 214 | 215 | <%namespace name="forms" file="/bs4tl/forms.html" inheritable="True"/> 216 | 217 | Generate form tag and tags for all fields in one shot. `form` is django form. 218 | 219 | ${forms.form(form, action='/some_url', submit_label='Submit')} 220 | 221 | Use your own form tags. 222 | 223 | 233 | 234 | Just generate one field. label, form widget tag(like input or select), help text, validation status and etc will be generated. Field type will be automatically detected. `field` is field of django form. 235 | 236 | ${visible_field(field)} 237 | 238 | Generate only specific form widget. This feature will be useful when you don't use django form. 239 | 240 | ${forms.text(name='title', label='Book Title', value='Pragmatic Thinking and Learning')} 241 | ${forms.select(name='country', label='Country', choices=country_choices, value='36')} 242 | ${forms.date(name='begin', label='Begin date')} 243 | --------------------------------------------------------------------------------