├── 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 | -------------------------------------------------------------------------------- /djangox/deploy/uwsgi.conf: -------------------------------------------------------------------------------- 1 | # Emperor uWSGI script 2 | 3 | description "uWSGI Emperor" 4 | start on runlevel [2345] 5 | stop on runlevel [06] 6 | 7 | exec sudo -uubuntu /home/ubuntu/venv/bin/uwsgi --die-on-term --emperor /etc/uwsgi -------------------------------------------------------------------------------- /djangox/deploy/bin/backup-postgresql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PGPASSWORD=%(db_password)s 3 | pg_dump -U %(db_user)s %(db_name)s | gzip > %(backup_dir)s/%(project_name)s-`date '+%%Y%%m%%d'`.sql.gz 4 | /usr/local/bin/aws s3 mv %(backup_dir)s/%(project_name)s-`date '+%%Y%%m%%d'`.sql.gz s3://%(backup_bucket)s -------------------------------------------------------------------------------- /djangox/apps/unilogin/static/unilogin.css: -------------------------------------------------------------------------------- 1 | .loginbox {text-align: center; } 2 | .loginbox ul {text-align: center; list-style: none;} 3 | .loginbox li {margin: 10px;} 4 | .loginbox form { margin: 0 20px;} 5 | .container {padding: 10px;} 6 | 7 | .social-buttons { overflow: hidden;} 8 | .social-buttons li { float: left} -------------------------------------------------------------------------------- /djangox/apps/unilogin/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from djangox.route import discover_controllers 3 | 4 | 5 | urlpatterns = patterns('', 6 | url('', include('social.apps.django_app.urls', namespace='social')), 7 | (r'', discover_controllers('unilogin.controllers')), 8 | ) -------------------------------------------------------------------------------- /djangox/apps/tools/management/commands/setupdeploy.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from djangox.apps import import_app 3 | 4 | 5 | class Command(BaseCommand): 6 | help = 'setup deploy environment' 7 | 8 | def handle(self, *args, **options): 9 | import_app('djangox.deploy', edit_settings=False) -------------------------------------------------------------------------------- /djangox/apps/tools/management/commands/importapp.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from djangox.apps import import_app 3 | 4 | 5 | class Command(BaseCommand): 6 | args = 'package name of app' 7 | help = 'copy specified app files into this project.' 8 | 9 | def handle(self, *args, **options): 10 | import_app(args[0]) -------------------------------------------------------------------------------- /djangox/deploy/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | thread=3 3 | master=1 4 | env = DJANGO_SETTINGS_MODULE=%(project_name)s.production 5 | env = LC_ALL=en_US.UTF-8 6 | file = /home/ubuntu/%(project_name)s/%(project_name)s/wsgi.py 7 | chdir = %(project_path)s 8 | virtualenv = %(home)s/venv 9 | socket = 127.0.0.1:3031 10 | logto = /home/ubuntu/logs/uwsgi-%(project_name)s.log 11 | buffer-size=32768 -------------------------------------------------------------------------------- /djangox/deploy/nginx-site: -------------------------------------------------------------------------------- 1 | server { 2 | listen %(port)d; 3 | server_name %(server_name)s %(server_alias)s; 4 | client_max_body_size 200M; 5 | access_log /var/log/nginx/%(server_name)s.log; 6 | location /static { 7 | alias %(static_path)s; 8 | } 9 | location / { 10 | uwsgi_pass 127.0.0.1:3031; 11 | include uwsgi_params; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /djangox/deploy/README.md: -------------------------------------------------------------------------------- 1 | ## Prerequisite 2 | Install fabric (fabric runs on python 2) 3 | 4 | pip install -r requirements 5 | 6 | customize config 7 | 8 | cp deployconfig_sample.py deployconfig.py 9 | vi deployconfig.py 10 | 11 | ## Deploy 12 | Prepare database server 13 | 14 | fab setup_db 15 | 16 | Prepare web server 17 | 18 | fab setup_web 19 | 20 | Deploy 21 | 22 | fab deploy 23 | -------------------------------------------------------------------------------- /djangox/deploy/deployconfig_sample.py: -------------------------------------------------------------------------------- 1 | from fabric.state import env 2 | 3 | env.roledefs.update({ 4 | 'web': [ 5 | 'webserver1.com', 6 | 'webserver2.com' 7 | ], 8 | 'db': ['dbserver1.com'] 9 | }) 10 | 11 | env.server_name = 'mydomain.com' 12 | env.project_name = 'myproject' 13 | env.project_repo = 'git@github.com:me/myproject.git' 14 | 15 | env.key_filename = 'mykey.pem' 16 | env.backup_bucket = 'myproject-backup' 17 | 18 | env.db_host = 'dbserver1.com' 19 | env.db_name = 'mydbname' 20 | env.db_user = 'mydbuser' 21 | env.db_password = 'mydbpassword' 22 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/dialog.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/nav.html: -------------------------------------------------------------------------------- 1 | <%def name="nav_tab(label, href, icon, badge=None, extra_class='', attrs={})"> 2 |
  • 7 | % if badge: 8 | ${badge} 9 | % endif 10 | 11 | % if icon: 12 | 13 | % endif 14 | ${label} 15 | 16 |
  • 17 | 18 | 19 | -------------------------------------------------------------------------------- /djangox/mako/staticfiles.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | import os 4 | from urllib.parse import urljoin 5 | 6 | from django.core.cache import cache 7 | from django.templatetags.static import StaticNode, PrefixNode 8 | from django.contrib.staticfiles import finders 9 | 10 | 11 | def static(path): 12 | prefixed_path = urljoin(PrefixNode.handle_simple("STATIC_URL"), path) 13 | 14 | if not cache.get('statichash.' + path): 15 | fs_path = finders.find(path) 16 | if not fs_path or not os.path.isfile(fs_path): 17 | return prefixed_path 18 | 19 | hash = hashlib.md5(open(fs_path, 'rb').read()).hexdigest() 20 | cache.set('statichash.' + path, hash) 21 | 22 | return prefixed_path + '?' + cache.get('statichash.' + path) -------------------------------------------------------------------------------- /djangox/deploy/production.py.template: -------------------------------------------------------------------------------- 1 | from %(project_name)s.settings import * 2 | 3 | STATIC_ROOT = '%(static_path)s' 4 | 5 | DATABASES = { 6 | 'default': { 7 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 8 | 'NAME': '%(db_name)s', # Or path to database file if using sqlite3. 9 | # The following settings are not used with sqlite3: 10 | 'USER': '%(db_user)s', 11 | 'PASSWORD': '%(db_password)s', 12 | 'HOST': '%(db_host)s', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. 13 | 'PORT': '', # Set to empty string for default. 14 | } 15 | } 16 | 17 | DEBUG = False 18 | ALLOWED_HOSTS = ['*'] 19 | 20 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/server_error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Server Error 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    Unknown Error
    16 |
    17 | 이용에 불편을 드려 죄송합니다. 18 |
    19 |
    20 | 첫 페이지로 21 | 뒤로 돌아가기 22 |
    23 |
    24 |
    25 |
    26 | 27 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/not_found.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    페이지를 찾을 수 없습니다.
    16 |
    17 | 요청하신 주소의 페이지를 찾을 수 없습니다. 이용에 불편을 드려 죄송합니다. 18 |
    19 |
    20 | 첫 페이지로 21 | 뒤로 돌아가기 22 |
    23 |
    24 |
    25 |
    26 | 27 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/permission_denied.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Permission denied 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    권한이 없습니다.
    16 |
    17 | 요청하신 기능을 실행할 권한이 없습니다. 관리자에게 문의해보시기 바랍니다. 18 |
    19 |
    20 | 첫 페이지로 21 | 뒤로 돌아가기 22 |
    23 |
    24 |
    25 |
    26 | 27 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/static/bs4tl.css: -------------------------------------------------------------------------------- 1 | body, a { 2 | /*color: #43B6B1;*/ 3 | } 4 | 5 | nav { 6 | overflow: hidden; 7 | box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | nav a:hover { 11 | text-decoration: none; 12 | } 13 | 14 | nav .brand { 15 | float: left; 16 | } 17 | 18 | nav .brand a { 19 | display: block; 20 | padding: 26px 10px 0 10px; 21 | height: 88px; 22 | font-size: 2em; 23 | font-family: Savoye LET; 24 | font-weight: bold; 25 | } 26 | 27 | nav ul { margin: 0; padding: 0; float: left} 28 | 29 | nav li { 30 | float: left; 31 | list-style: none; 32 | } 33 | 34 | nav li a { 35 | color: #777; 36 | display: block; 37 | height: 88px; 38 | vertical-align: middle; 39 | transition: all 0.2s ease-out 0s; 40 | line-height: 20px; 41 | padding: 0 15px; 42 | } 43 | 44 | nav li a:active, nav li a:hover, nav li.active a { 45 | background: #43B6B1 none repeat scroll 0% 0%; 46 | color: #FFF; 47 | } 48 | 49 | nav li a i.fa { 50 | display: block; 51 | text-align: center; 52 | padding: 20px 0 10px; 53 | font-size: 2em; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | 5 | def find_package_data(path): 6 | return [os.path.join(root[len(path) + 1:], name) for root, dirs, files in os.walk(path) for name in files if not name.endswith('.py')] 7 | 8 | 9 | setup(name='djangox', 10 | description='Another way of using django. Convention over configuration for urls, mako template language support,' 11 | 'json response, REST style API, convenient scripts, ...', 12 | author='Youngrok Pak', 13 | author_email='pak.youngrok@gmail.com', 14 | keywords= 'rest route autodiscover django djangox mako', 15 | url='https://github.com/youngrok/djangox', 16 | version='0.1.18', 17 | packages=find_packages(), 18 | package_data={ 19 | 'djangox.apps.bs4tl': find_package_data('djangox/apps/bs4tl'), 20 | 'djangox.apps.tools': find_package_data('djangox/apps/tools') 21 | }, 22 | 23 | classifiers = [ 24 | 'Development Status :: 3 - Alpha', 25 | 'Topic :: Software Development :: Libraries', 26 | 'License :: OSI Approved :: BSD License'] 27 | ) 28 | -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/paginator.html: -------------------------------------------------------------------------------- 1 | <%page args="page_obj, page_number, adjacent_size=4"/> 2 | <% 3 | page_number = int(page_number) if page_number else int(request.GET.get('page', 1)) 4 | qd = request.GET.copy() 5 | if 'page' in qd: 6 | del qd['page'] 7 | querystring = qd.urlencode() 8 | %> 9 | -------------------------------------------------------------------------------- /djangox/apps/tools/management/commands/setupstatic.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.conf import settings 3 | from django.core.management.base import BaseCommand 4 | from mako.template import Template 5 | from djangox.apps import import_app, tools 6 | 7 | 8 | class Command(BaseCommand): 9 | args = 'app name' 10 | help = '''Setup static folder for specified app. 11 | - create static folder 12 | - ready to use bower 13 | - generate .bowerrc and bower.json 14 | - bower packages will be installed in static/lib 15 | - bootstrap, font-awesome, jquery will be included in bower.json by default. 16 | ''' 17 | 18 | def handle(self, *args, **options): 19 | app_name = args[0] 20 | os.makedirs(app_name + '/' + 'static/lib', exist_ok=True) 21 | 22 | template_path = filename=tools.__path__[0] + '/setupstatic/' 23 | with open(app_name + '/../.bowerrc', 'w') as f: 24 | f.write(Template(filename=template_path + '.bowerrc').render(app_name=app_name)) 25 | 26 | with open(app_name + '/../bower.json', 'w') as f: 27 | f.write(Template(filename=template_path + 'bower.json').render(app_name=app_name)) 28 | 29 | print('Now you can use bower command. Try `bower install`.') 30 | 31 | -------------------------------------------------------------------------------- /djangox/apps/unilogin/templates/unilogin/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Social Login 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |

    Login

    17 | % if messages: 18 | % for message in messages: 19 |
    20 | ${message} 21 |
    22 | % endfor 23 | % endif 24 |
    25 |
    You can login with these social login providers. Select one of buttons below.
    26 | 38 |
    39 |
    40 | 41 | 42 | -------------------------------------------------------------------------------- /djangox/route/__init__.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import pkgutil 3 | import sys 4 | import types 5 | 6 | from django.conf.urls import include 7 | from django.urls import re_path 8 | 9 | 10 | def discover_controllers(package, method_first=False): 11 | ''' 12 | Discover controller functions within give package. 13 | ''' 14 | if isinstance(package, types.ModuleType): 15 | package = package.__name__ 16 | 17 | urls = [] 18 | __import__(package) 19 | 20 | if hasattr(sys.modules[package], 'index'): 21 | urls.append(re_path('^$', getattr(sys.modules[package], 'index'))) 22 | 23 | for _, name, _ in pkgutil.iter_modules([sys.modules[package].__path__[0]]): 24 | __import__(package + '.' + name) 25 | 26 | controller = sys.modules[package + '.' + name] 27 | 28 | for member in dir(controller): 29 | func = getattr(controller, member) 30 | if not inspect.isfunction(func): continue 31 | args = inspect.getfullargspec(func).args 32 | 33 | # TODO filter request functions 34 | # if len(args) == 0 or args[0] != 'request': continue 35 | 36 | if method_first: 37 | urls.append(re_path('^' + name + '/' + member + '/(?P[^/\?\&]+)/?$', func)) 38 | else: 39 | urls.append(re_path('^' + name + '/(?P[^/\?\&]+)/' + member + '/?$', func)) 40 | 41 | urls.append(re_path('^' + name + '/' + member + '/?$', func)) 42 | 43 | if 'show' in dir(controller): 44 | urls.append(re_path('^' + name + '/(?P[^/\?\&]+)/?$', getattr(controller, 'show'))) 45 | if 'index' in dir(controller): 46 | urls.append(re_path('^' + name + '$', getattr(controller, 'index'))) 47 | 48 | return include(urls) 49 | 50 | 51 | class SubdomainMiddleware(object): 52 | def process_request(self, request): 53 | hostname = request.get_host().split(':')[0] 54 | 55 | name_parts = hostname.split('.') 56 | 57 | if len(name_parts) < 2: 58 | request.subdomain = None 59 | else: 60 | request.subdomain = name_parts[0] 61 | 62 | return None 63 | -------------------------------------------------------------------------------- /djangox/route/rest.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | import inspect 3 | import pkgutil 4 | import re 5 | import sys 6 | import types 7 | from django.utils.decorators import classonlymethod 8 | from django.views.decorators.csrf import csrf_exempt 9 | from django.views.generic.base import View 10 | 11 | 12 | class RESTController(object): 13 | 14 | @classonlymethod 15 | def as_view(cls): 16 | 17 | @csrf_exempt 18 | def dispatch(request, resource_id=None, action=None): 19 | self = cls() 20 | method = request.method.lower() 21 | 22 | if action: 23 | return getattr(self, action)(request, resource_id) 24 | 25 | elif method == 'get': 26 | if resource_id: 27 | return self.show(request, resource_id) 28 | else: 29 | return self.index(request) 30 | elif method == 'post': 31 | return self.create(request) 32 | elif method == 'put': 33 | return self.update(request, resource_id) 34 | elif method == 'delete': 35 | return self.delete(request, resource_id) 36 | elif method == 'head': 37 | return self.info(request) 38 | else: 39 | raise Exception('No route found: %s %s' % (method, resource_id)) 40 | 41 | return dispatch 42 | 43 | 44 | def discover(package, method_first=False): 45 | ''' 46 | Discover controller functions within give package. 47 | ''' 48 | if isinstance(package, types.ModuleType): 49 | package = package.__name__ 50 | 51 | urls = [] 52 | __import__(package) 53 | for module_loader, name, ispkg in pkgutil.walk_packages(sys.modules[package].__path__): 54 | module = module_loader.find_module(name).load_module(name) 55 | 56 | for member in dir(module): 57 | controller_class = getattr(module, member) 58 | if inspect.isclass(controller_class) and issubclass(controller_class, RESTController): 59 | resource_name = controller_class.__name__.replace('Controller', '').lower() 60 | urls.append(url(resource_name + '/(?P[^/\?\&]+)?', 61 | controller_class.as_view())) 62 | 63 | return include(urls) 64 | -------------------------------------------------------------------------------- /djangox/apps/unilogin/controllers/accounts.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | from django.conf import settings 3 | from django.contrib import auth 4 | from django.contrib.auth import logout as auth_logout 5 | from django.contrib.auth.models import User 6 | from django.core.context_processors import csrf 7 | from django.core.urlresolvers import reverse 8 | from django.http import HttpResponseRedirect 9 | from django.shortcuts import render 10 | from django.template.context import RequestContext 11 | from django.template.defaulttags import csrf_token, CsrfTokenNode 12 | from djangox.mako import render_to_response 13 | 14 | 15 | def login(request): 16 | request.session['login_error_url'] = request.build_absolute_uri() 17 | 18 | next = request.GET.get('next', '/') 19 | 20 | social_auth_providers = settings.SOCIAL_AUTH_PROIVDERS 21 | print('rc', RequestContext(request)) 22 | return render(request, 'unilogin/login.html', locals()) 23 | 24 | 25 | def provider_authorize(request): 26 | request.session['state'] = request.GET['state'] 27 | request.session['host'] = request.GET['host'] 28 | 29 | next = reverse(provider_complete) 30 | 31 | if request.user.is_authenticated() and not request.GET['connect_more']: 32 | return HttpResponseRedirect(next) 33 | 34 | social_auth_providers = settings.SOCIAL_AUTH_PROIVDERS 35 | return render_to_response('accounts/login.html', locals(), RequestContext(request)) 36 | 37 | 38 | def provider_complete(request): 39 | if not request.user.is_authenticated(): 40 | raise Exception('authentication failed') 41 | 42 | host = request.session['host'] 43 | state = request.session['state'] 44 | 45 | del request.session['host'] 46 | del request.session['state'] 47 | 48 | return HttpResponseRedirect('http://%s%s?%s' % ( 49 | host, 50 | reverse(consumer_complete), 51 | urllib.parse.urlencode({ 52 | 'user': request.user.id, 53 | 'state': state 54 | })) 55 | ) 56 | 57 | 58 | def consumer_complete(request): 59 | user = User.objects.get(id=request.GET['user']) 60 | next = request.session['next'] 61 | state = request.session['state'] 62 | 63 | request.session.clear() 64 | 65 | if state != request.GET['state']: 66 | raise Exception('state token mismatch: %s != %s' % (state, request.GET['state'])) 67 | 68 | user.backend = 'django.contrib.auth.backends.ModelBackend' 69 | auth.login(request, user) 70 | return HttpResponseRedirect(next) 71 | 72 | 73 | def logout(request): 74 | """Logs out user""" 75 | auth_logout(request) 76 | 77 | return HttpResponseRedirect(request.GET.get('next', '/')) 78 | -------------------------------------------------------------------------------- /djangox/apps/__init__.py: -------------------------------------------------------------------------------- 1 | import distutils 2 | from distutils.dir_util import copy_tree 3 | from django.conf import settings 4 | import importlib 5 | import os 6 | import re 7 | from django.core.management.base import BaseCommand 8 | 9 | 10 | class Command(BaseCommand): 11 | help = "Install app specified by module name, and copy it's files into this project" 12 | 13 | def add_arguments(self, parser): 14 | parser.add_argument('app_name') 15 | 16 | def handle(self, app_name, *args, **options): 17 | import_app(app_name) 18 | 19 | 20 | def import_app(app_name, edit_settings=True): 21 | print(app_name) 22 | try: 23 | app_module = importlib.import_module(app_name) 24 | except: 25 | print('cannot import %s' % app_name) 26 | return 27 | 28 | module_name_only = app_module.__name__.split('.')[-1] 29 | if edit_settings: 30 | insert_app_to_settings(app_name) 31 | 32 | copy_tree(app_module.__path__[0], settings.BASE_DIR + '/' + module_name_only) 33 | print('copied %s into %s' % (app_module.__path__[0], settings.BASE_DIR + '/' + module_name_only)) 34 | 35 | 36 | def insert_app_to_settings(app_name): 37 | project_path = os.path.dirname(os.path.normpath(os.sys.modules[settings.SETTINGS_MODULE].__file__)) 38 | settings_edit = CodeEditor(project_path + os.sep + 'settings.py') 39 | settings_edit.insert_line(" '%s'," % app_name.split('.')[-1], after='INSTALLED_APPS') 40 | settings_edit.commit() 41 | print("edited " + project_path + os.sep + 'settings.py') 42 | 43 | 44 | class CodeEditor(object): 45 | 46 | def __init__(self, filename): 47 | self.filename = filename 48 | source = open(filename, 'r').read() 49 | self.lines = source.splitlines() 50 | self.cursor = 0 51 | 52 | def insert_tuple_element(self, name, value): 53 | for index, line in enumerate(self.lines): 54 | if line.strip().startswith(name): 55 | insert_index = index + 1 56 | break 57 | 58 | self.lines.insert(insert_index, ' ' + repr(value) + ',') 59 | 60 | def go_line(self, expr): 61 | for index, l in enumerate(self.lines[self.cursor:], self.cursor): 62 | if expr in l: 63 | self.cursor = index 64 | break 65 | 66 | def replace_all(self, expr, replacement): 67 | for index, l in enumerate(self.lines, self.cursor): 68 | if expr in l: 69 | self.lines[index] = l.replace(expr, replacement) 70 | 71 | def replace_line(self, expr, replacement): 72 | for index, l in enumerate(self.lines[self.cursor:], self.cursor): 73 | if expr in l: 74 | self.cursor = index 75 | break 76 | 77 | self.lines[self.cursor] = self.lines[self.cursor].replace(expr, replacement) 78 | 79 | def insert_line(self, line, after): 80 | if line in self.lines: return 81 | 82 | insert_index = 0 83 | for index, l in enumerate(self.lines[self.cursor:], self.cursor): 84 | if re.match(after, l): 85 | insert_index = index + 1 86 | break; 87 | self.lines.insert(insert_index, line) 88 | 89 | def append_line(self, line): 90 | self.lines.append(line) 91 | 92 | def to_source(self): 93 | return '\n'.join(self.lines) 94 | 95 | def commit(self): 96 | open(self.filename, 'w').write(self.to_source()) -------------------------------------------------------------------------------- /djangox/apps/bs4tl/templates/bs4tl/forms.html: -------------------------------------------------------------------------------- 1 | <%def name="form(form, action, submit_label, enctype='application/x-www-form-urlencoded', prefix='')"> 2 |
    3 | 4 | % for field in form.hidden_fields(): 5 | ${hidden_field(field, prefix)} 6 | % endfor 7 | % for field in form.visible_fields(): 8 | ${visible_field(field, prefix)} 9 | % endfor 10 | 11 |
    12 | 13 | <%def name="visible_field(field, prefix='')"> 14 |
    15 | 16 | % if hasattr(self, field.field.widget.__class__.__name__): 17 | ${getattr(self, field.field.widget.__class__.__name__)(field, prefix)} 18 | % else: 19 | ${TextInput(field, prefix)} 20 | %endif 21 | % if field.help_text: 22 |

    ${_(field.help_text)}

    23 | % endif 24 | % for error in field.errors: 25 |

    ${ _(error) }

    26 | % endfor 27 |
    28 | 29 | 30 | <%def name="TextInput(field, prefix='')">\ 31 | \ 32 | 33 | 34 | <%def name="PasswordInput(field, prefix='')">\ 35 | \ 36 | 37 | 38 | <%def name="EmailInput(field, prefix='')">\ 39 | \ 40 | 41 | 42 | <%def name="ClearableFileInput(field, prefix='')">\ 43 | \ 44 | 45 | 46 | <%def name="URLInput(field, prefix='')">\ 47 | \ 48 | 49 | 50 | <%def name="NumberInput(field, prefix='')">\ 51 | \ 52 | 53 | 54 | <%def name="Textarea(field, prefix='')">\ 55 | \ 56 | 57 | 58 | <%def name="Select(field, prefix='')"> 59 | 65 | 66 | 67 | <%def name="SelectMultiple(field, prefix='')"> 68 | 74 | 75 | 76 | <%def name="DateTimeInput(field, prefix='')">\ 77 | \ 78 | 79 | 80 | <%def name="DateInput(field, prefix='')">\ 81 | \ 82 | 83 | 84 | <%def name="MonthInput(field, prefix='')">\ 85 | \ 86 | 87 | 88 | <%def name="hidden_field(field, prefix='')">\ 89 | \ 90 | % for error in field.errors: 91 | 92 |

    ${ error }

    93 | % endfor 94 | 95 | 96 | <%def name="CheckboxInput(field, prefix='')">\ 97 | \ 98 | 99 | 100 | <%def name="csrf_token_input()"> 101 | 102 | -------------------------------------------------------------------------------- /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 |
    224 | 225 | % for field in form.hidden_fields(): 226 | ${hidden_field(field)} 227 | % endfor 228 | % for field in form.visible_fields(): 229 | ${visible_field(field)} 230 | % endfor 231 | 232 |
    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 | --------------------------------------------------------------------------------