Package: django-express
Easy Restful APIs with the Django web framework.InstallDownload throughpip(virtualenv -p python3 .venv)pip install django-expressAdd it to yourINSTALLED_APPSinsettings.pyINSTALLED_APPS = [
  # ...
  'django.contrib.staticfiles',
  'express',
]SetupMount the auto-discovered services to any entry point (url) you want inurlpatterns# proj/proj/urls.py

from django.conf.urls import url, include
from express import services

urlpatterns = [
    url(r'^api/v1/', include(services.urls)) # mount everything
    url(r'^app-name/api/v1/', include(services.url('app-name', ...))) # mount only those from specific app(s)
]Pleasedouble checkif yoururl()call here has the path
argumentending with a trailing slash(e.gfoo/bar/). This is
required by the Django framework. You do not need to have this in your@url()decorator paths though.Start serving apisYou can just start Django like normal, your apis will be automatically
discovered and mounted../manage.py runserver 0.0.0.0:8000Note that for other developers to use your apis, you need to bind on
wildcard or public WAN/LAN accessable IP address specifically afterrunserverinstead of leaving the param out to use the default127.0.0.1localhost IP. If you are developing inside a VM (e.g
through ourVagrantweb dev vm) it is very important that you specify
the VM’s IP or the wildcard IP afterrunserverso that you can use
your host machine’s browser for accessing the apis through vm to host
forwarded ports.Also, userunserverwithDEBUG=trueinsettings.pywill
automatically serve all thestatic/sub-folders (and those added bySTATICFILES_DIRS) from your apps. They are served like they were
merged under the same uri set bySTATIC_URLin yoursettings.py,
so if you do not want files from different apps to override each other,
put all your static assets (e.g *.js/html/css/png) in a sub-folder with
the same name as your app inside eachstatic/. ThoughSTATIC_URLdoesn’t have a default value, after runningdjango-adminstartprojectyoursettings.pywill set a default
value/static/to it so you could access files from thestatic/sub-folders underhttp://domain:8000/static/<filepath>with zero
setup time.If you are not using therunservercommand for serving static assets
and service apis during development, make sure you call./manage.py collectstaticsand serve folderSTATIC_ROOTonSTATIC_URL, so that{% load static %}then{% static "images/hi.jpg" %}can work properly in your templates.Adding RESTful servicesCreate apps in your Django projectnormally, this is to sub-divide
your services by app name for better maintainability. Optional though../manage.py startapp app_example
./manage.py startapp another_app_with_servicesFunction as service apiAdd aservices.pyfile in each app folder containing the service
functionsfn(req, res, *args, **kwargs)decorated with@service# proj/app_example/services.py
from express.decorators import service, methods, url


# /api/v1/absolute/url
# /api/v1/app_example/relative/abcd

@methods('GET', 'POST')
@url('/absolute/url')
@url('relative/abcd')
@service
def abc(req, res, *args, **kwargs):
    res.json({'json': req.json, 'link:': reverse('express:testa.abc')})


# /api/v1/app_example/efg

@service
def efg(req, res, *args, **kwargs):
    res.json({
        'params': dict(req.params.lists()), # use {**req.params} in python 3.5+
        'form': dict(req.form.lists()), # use {**req.form} in python 3.5+
        'json': req.json,
        'mime': req['CONTENT_TYPE'],
        })


# /api/v1/app_example/hij

@service
def hij(req, res, *args, **kwargs):
    res.file('db.sqlite3')


# /api/v1/app_example/x

@service
def x(req, res, *args, **kwargs):
    #res.text('Nothing but a test from {}'.format(__name__))
    res.text('<p>Agent: {}</p>'.format(req['HTTP_USER_AGENT']))
    res.html('<p>IP: {}</p>'.format(req['REMOTE_ADDR']))
    res.text('<p>Method: {}</p>'.format(req['REQUEST_METHOD']))


# /api/v1/app_example/relative/url/y-service/articles/2017/01/

@url('relative/url/y-service/articles/([0-9]{4})/([0-9]{2})/')
@service
def y1(req, res, y, m, *args, **kwargs):
    res.json({
        'data': 'Nothing but a test from {}.{}'.format(__name__, 'y1 - positional capture'),
        'text': 123,
        'year': y,
        'month': m,
    })
    res.header('Hello~', 'World!') # header
    res.status(201) # status


# /api/v1/app_example/z

@service
def z(req, res, *args, **kwargs):
    res.download('db.sqlite3')As you can see, you can still use regex captures in@url('..path..')if prefered. The captured group/named group will be passed normally to
your service function as positional args and keyword args. However,You can NOT use both positioned and namged group captures in the same
url!! Due to django implementation.Important NotePut@serviceas the inner-most decorator, other decorators don’t
have this hard requirement on ordering here. You can still use all the
decorators from the Django web framework like@permission_requiredor@login_requiredbut make sure they are all above@service.Argument APIsThe most important arguments to your service function would be the first
two, namelyreqfor request andresfor response. Here are the
available methods on these two objects.req (ExpressRequest)req.params[‘key’]req.jsonreq.formreq.files[‘name’]req.cookies[‘name’]req[‘HTTP-HEADER’]/req.header(‘key’)res (ExpressResponse)res.redirect(‘url’)res.render(req, ‘template’, context={})res.html(‘str’)/text(‘str’)res.json(dict)res.file(‘path’)res.attach(‘path’)/download(‘path’)res.status(int)res[‘HTTP_HEADER’]/res.header(‘key’, val)Caveat:res.status()andres['HTTP_HEADER']/res.header()must be called after.render()/html()/text()/json()/file()/attach()/download()in your
service function for new headers and status to be applied to the
response.Model generated service apisWithin themodels.pyfile, you can decorate any of your Model class
directly for it to generate the apis around its CRUD database
operations.# proj/app_example/models.py

@url('/absolute/db/device')
@url('db/device')
@serve_unprotected
class Device(models.Model):
    """docstring for Device"""
    sn = models.CharField(max_length=32)This will mount 5 default service functions bound to different HTTP
methods (POST/GET/PUT,PATCH/DELETE/HEAD) to urlapp_example/models/Devicefor its CRUD database operations and one
more metadata operations.DecoratorsFor a function@serviceTurn yourfn(req, res, *args, **kwargs)function into a Restful
service routine. Automatically detected if present inservices.pyin
anyinstalledapp.Default path withservices.urls:/<app>/<fn>Default path withservices.url(app, noprefix=True):/<fn>You can change the mounting path by using the@url()decorator. You
can also usedjango.urls.reverse()to get the mount point by name<namespace>:<app>.services.<fn>if you mount the
``services.url(s)`` with namespaced ``include()`` calls in
``urls.py``.Still,do not forgetto mount everthing collected insideservices.urlsto a root url in the djangourls.py. See theSetupsection above.@methods(m1, m2, …)Allowed HTTP request methods to the service. You can also use@safeto allow onlyGETandHEADrequests. You can use different@methods()on each service function with the same@url()path to
reuse the same url.@url(path)Override basic service auto-path (/<app>/<fn>). No need to user'..path..'here, what you put inpathwill be treated as raw
string automatically. Feel free to put regex group captures.Just
don’t mix named and annonymous capture groups in the url path, they
won’t work together in django.You can use multiple@url()on the same service function.@csrfSetting CSRF token cookie onGET/HEADrequests to the service.
Checks and rejectsPOST/PUT/PATCH/DELETErequests according to their
csrf token + cookie pairs.If you want an Ajax request to be guarded by django CSRF
(django.middleware.csrf.CsrfViewMiddleware) you need toGET/HEADthe@csrfdecorated service first to have your CSRF cookie (namedcsrftoken) set, thenPOST/PUT/PATCH/DELETEto it with real
requests including eitherX-CSRFTokenin header orcsrfmiddlewaretokenin a hidden form<input>field. The header
or hidden field value should match the value given by the cookie.You can change the cookie and header names butNOTthe hidden field
name in the djangosettings.py.For a Model@serveGive a Model default RESTful apis to its CRUD operations.Default path withservices.urls:/<app>/<Model>Default path wihtservices.url(app, noprefix=True):/<Model>You can change the mounting path by using the@url()decorator. You
can also usedjango.urls.reverse()to get the mount point by name<namespace>:<app>.models.<fn>.POST – create – {“payload”: {…data…}}GET – read – ?pk= for single record, omit for allPUT/PATCH – update – {“payload”: {“id”: “…”, …data…}}DELETE – delete – ?pk= for target record, requiredHEAD – meta – model nameX-Django-App-Modeland table countX-DB-Table-Countin reply headersWhen usingGEThttp request on a@serve(-ed) model, you can
also specify params for filtering (by columns and Django ORM filter
operations), sorting (by columns) and paging the returned result.?filter=foo1:op_and_val1&filter=foo2:op_and_val2
?sort=foo, -bar

?size=number
?offset=number
?page=numberWhen usingAnyhttp requests on a@serve(-ed) model, you can
always use?db=...to switch onto the specific database for served
model apis to query and modify. The database names come from yourDATABASESconfigure insettings.py.Still,do not forgetto mount everthing collected insideservices.urlsto a root url in the djangourls.py. See theSetupsection above.@serve_unprotectedSame as @serve but without csrf protection.@methods(m1, m2, …)Same as @methods for a service function.@url(path)Same as @url for a service function.Database Backendsbackends.mongodbThis is a dummy backend engine to use with MongoDB connections without
the involvement of Django ORM. The purpose is to have your MongoDB
settings in thesettings.pyand usedjango.db.connections['<yourmongodbname>']to start using MongoDB
in your Django apps.# settings.py

DATABASES = {
    ...,
    'mongo': {
        'ENGINE': 'express.db.backends.mongodb',
        'HOST': 'mongo.server.com',
        'PORT': 27017,
        'NAME': 'testdb',
        'USER': '...',
        'PASSWORD': '...',
        'OPTIONS': {
            ...pymongo.MongoClient options...
        }
    },
    ...
}Now you will have,django.db.connections[‘testdb’].db - apymongodb object;django.db.connections[‘testdb’].collection(‘collection’=None) - apymongocollection or all available collection names;django.db.connections[‘testdb’].cursor(‘collection’, **kwargs) - a
.find(kwargs)pymongocursor;After getting the above, you will have, *
django.db.connections[‘testdb’].connection - apymongoclient;Use.cursor()for search (GET) apis and.collection()for
modify (POST/PUT/PATCH/DELETE) apis.LimitationThis engine works up to the point of creating the db connection and
collection cursor, taking in DATABASES options from your settings.py;
The ORM layer (migration, schema, transactions, save/delete()) will not
work on database that has settings using this Engine.LicenceCopyright 2017 Tim Lauv. Under theMITLicense.