[PYTHON] How to get people to try out django rest framework features in one file

(I made a tag called onefile because I sometimes want to try the troublesome framework functions with one file. You can make it without permission. I don't know if it is.)

Introduction

django rest framework is a framework built on top of django. It's convenient when creating a REST API. It is troublesome to check the functions provided. After all, reading the internal source code to understand the behavior is often easier than reading documents. There is no loss even if you create an environment where you can easily try out the functions.

You can try out the features of django restframework with code like this: (You can skip it without reading it carefully)

#The story of making this shortcut module
from shortcut import App  # shorthand
from shortcut import do_get, do_post, do_delete

app = App()
app.setup(apps=[__name__], root_urlconf=__name__)


from rest_framework import routers, serializers, viewsets

# models
from django.contrib.auth.models import User


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'url', 'username', 'email', 'is_staff')


# viewsets
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer


# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = app.setup_urlconf(router)

if __name__ == "__main__":
    app.create_table(User)
    parser = app.create_arg_parser()
    args = parser.parse_args()

    if args.run_server:
        app.run_server(port=8080)
    else:
        app.run_client(main_client)

How to use

Precautions and explanations on how to use.

file organization

Since it depends on shortcut.py created later, it needs to have the following structure. (Of course, you can make the package seriously)

.
├── shortcut.py
└── view-sample1.py  #File you want to try

Adding view-sample1.py and - is inappropriate as a python module name. (I personally give an inappropriate file name because I want an import error when trying to import from another person, but it doesn't make much sense)

Import order

Due to the bad design of django, you need to be careful about the order of imports. The order and position of the following codes must be observed.

from shortcut import App  # shorthand
from shortcut import do_get, do_post, do_delete

#Various settings are required before importing the rest framework
app = App()
app.setup(apps=[__name__], root_urlconf=__name__)

#Import model and restframework modules
from django.contrib.auth.models import User
from rest_framework import routers, serializers, viewsets

The moment you import the django rest framework module, you may get an error saying that django needs to be set, so you need to make various settings before that. If you want to separate ʻurls.pyordjango app, the argument of ʻapp.setup () changes. Since this article introduces how to try with one file, you can fix it with __name__.

Roughly define model, serializer, router

This is a feature of the django rest framework, so I won't explain it in detail. See documentation etc.

from django.contrib.auth.models import User
# models
from rest_framework import routers, serializers, viewsets

# serializers
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'url', 'username', 'email', 'is_staff')


# viewsets
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = app.setup_urlconf(router)

Basically, serialier provides the output representation and variation of the model, viewset handles a series of views using serialiezer, and if you register the viewset in the router, you can use various REST APIs.

Precautions when defining a model

By the way, I forgot. If you try with one file, you need to set __meta__.app_label to determine the django app to which the model you want to define belongs. For example, it may be defined as follows.

class Skill(models.Model):
    name = models.CharField(max_length=255, default="", null=False)
    user = models.ForeignKey(User, null=False, related_name="skills")

    class Meta:
        app_label = __name__

Use as a server or display an execution example

If you start it with the --run-server option, it will actually work as an app. Since the browsable api is also enabled, you can access it with a browser, enter some value in the form, and try GET / POST / PUT / DELETE. Internally it just calls runserver from django.

$ python view-sample1.py --run-server
Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

July 18, 2016 - 23:32:11
Django version 1.9.6, using settings None
Starting development server at http://127.0.0.1:8080/
Quit the server with CONTROL-C.

Model utilization (persistence)

Since the model itself to be used is set to be registered in the in-memory DB of sqlite, it is necessary to generate a table every time. It is done below.

app.create_table(User)

Display of execution example when executed with no arguments

Display an execution example by django.test.client.Client when executed with no arguments. In the above example, it is assumed that main_client () is called, but it is not written. For example, write as follows.

def main_client(client):
    """call view via Client"""
    # success request
    msg = "listing (empty)"
    do_get(client, msg, "/users/")

    msg = "create user (name=foo)"
    do_post(client, msg, "/users/", {"username": "foo"})
    msg = "create user (name=bar)"
    do_post(client, msg, "/users/", {"username": "bar"})

    msg = "listing"
    do_get(client, msg, "/users/")

    msg = "show information for user(id=1)"
    do_get(client, msg, "/users/1/")

    msg = "delete user(id=1)"
    do_delete(client, msg, "/users/1")

    msg = "listing"
    do_get(client, msg, "/users/")

The execution result is as follows. (python view-sample1.py)

listing (empty)

request: GET /users/
status code: 200
response: []

create user (name=foo)

request: POST /users/
status code: 201
response: {
  "id": 1,
  "url": "http://testserver/users/1/",
  "username": "foo",
  "email": "",
  "is_staff": false
}

create user (name=bar)

request: POST /users/
status code: 201
response: {
  "id": 2,
  "url": "http://testserver/users/2/",
  "username": "bar",
  "email": "",
  "is_staff": false
}

listing

request: GET /users/
status code: 200
response: [
  {
    "id": 1,
    "url": "http://testserver/users/1/",
    "username": "foo",
    "email": "",
    "is_staff": false
  },
  {
    "id": 2,
    "url": "http://testserver/users/2/",
    "username": "bar",
    "email": "",
    "is_staff": false
  }
]

show information for user(id=1)

request: GET /users/1/
status code: 200
response: {
  "id": 1,
  "url": "http://testserver/users/1/",
  "username": "foo",
  "email": "",
  "is_staff": false
}

delete user(id=1)

request: DELETE /users/1/
status code: 204

listing

request: GET /users/
status code: 200
response: [
  {
    "id": 2,
    "url": "http://testserver/users/2/",
    "username": "bar",
    "email": "",
    "is_staff": false
  }
]

shortcut.py

shortcut.py looks like this: This explanation is troublesome, so I won't do it.

import os.path
import json
import copy
import importlib
import argparse
from django.db import connections
from django.test.client import Client


default_settings = dict(
    DEBUG=True,
    ALLOWED_HOSTS=['*'],
    INSTALLED_APPS=[
        "django.contrib.staticfiles",
        "django.contrib.contenttypes",
        "django.contrib.auth",
        "rest_framework",
    ],
    STATIC_URL='/static/',
    MIDDLEWARE_CLASSES=(
        'django.middleware.common.CommonMiddleware',
    ),
    REST_FRAMEWORK={
        "DEFAULT_PERMISSION_CLASS": [
            "rest_framework.permissions.AllowAny"
        ]
    },
    DATABASES={"default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": ":memory:"
    }},
    CACHES={
        'default': {
            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        }
    },
    TEMPLATES=[
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [],
            'APP_DIRS': True
        },
    ]
)


def create_table(model, dbalias="default"):
    connection = connections[dbalias]
    with connection.schema_editor() as schema_editor:
        schema_editor.create_model(model)


def maybe_list(x):
    if isinstance(x, (list, tuple)):
        return x
    else:
        return [x]


class SettingsHandler(object):
    defaults = {
        "settings": default_settings,
        "STATIC_ROOT": None,
        "dbalias": "default"
    }

    def get_settings_options(self, root_urlconf):
        options = copy.copy(self.defaults["settings"])
        options.update(
            STATIC_ROOT=self.defaults["STATIC_ROOT"] or self.get_static_root(),
            ROOT_URLCONF=root_urlconf
        )
        return options

    def get_static_root(self):
        import rest_framework
        return os.path.abspath(os.path.join(rest_framework.__path__[0], 'static'))


class App(object):
    def __init__(self, settings_handler=SettingsHandler()):
        self.settings_handler = settings_handler

    def setup(self, apps, root_urlconf, extra_settings=None):
        import django
        from django.conf import settings
        apps = maybe_list(apps)
        options = self.settings_handler.get_settings_options(root_urlconf)
        options["INSTALLED_APPS"].extend(apps)
        if extra_settings:
            options.update(extra_settings)
        settings.configure(**options)
        django.setup()

    def setup_urlconf(self, router):
        # url
        from django.conf.urls import url, include
        from django.contrib.staticfiles.urls import staticfiles_urlpatterns

        urlpatterns = [
            url(r'^', include(router.urls))
        ]
        urlpatterns += staticfiles_urlpatterns()
        return urlpatterns

    def load_module(self, module_name):
        return importlib.import_module(module_name)

    def run(self, main_client):
        parser = self.create_arg_parser()
        args = parser.parse_args()

        if args.run_server:
            self.run_server(port=8080)
        else:
            self.run_client(main_client)

    def run_server(self, port=8000):
        from django.core.management.commands.runserver import Command
        return Command().execute(addrport=str(port))

    def run_client(self, callback):
        client = Client()
        return callback(client)

    def create_arg_parser(self):
        parser = argparse.ArgumentParser()
        parser.add_argument("--run-server", dest="run_server", action="store_true", default=False)
        return parser

    def create_table(self, *models):
        for model in models:
            create_table(model, dbalias=self.settings_handler.defaults["dbalias"])


def do_get(client, msg, path):
    print(msg)
    print("```")
    print("request: GET {}".format(path))
    response = client.get(path)
    print("status code: {response.status_code}".format(response=response))
    print("response: {content}".format(content=json.dumps(response.data, indent=2)))
    print("```")


def do_post(client, msg, path, data):
    print(msg)
    print("```")
    print("request: POST {}".format(path))
    response = client.post(path, data)
    print("status code: {response.status_code}".format(response=response))
    print("response: {content}".format(content=json.dumps(response.data, indent=2)))
    print("```")


def do_delete(client, msg, path):
    print(msg)
    print("```")
    print("request: DELETE {}".format(path))
    response = client.delete(path)
    print("status code: {response.status_code}".format(response=response))
    print("```")

Postscript (try the pagination function)

For example, you can try the pagination function by making the following changes.

--- view-sample1.py	2016-07-18 23:39:33.000000000 +0900
+++ view-sample2.py	2016-07-19 00:02:14.000000000 +0900
@@ -16,10 +20,21 @@
         fields = ('id', 'url', 'username', 'email', 'is_staff')
 
 
+# pagination
+from rest_framework import pagination
+
+
+class MyPagination(pagination.PageNumberPagination):
+    page_size = 5
+    page_size_query_param = 'page_size'
+    max_page_size = 10000
+
+
 # viewsets
 class UserViewSet(viewsets.ModelViewSet):
     queryset = User.objects.all()
     serializer_class = UserSerializer
+    pagination_class = MyPagination
 
 
 # Routers provide an easy way of automatically determining the URL conf.

List display with page_size = 5.

request: GET /users/
status code: 200
response: {
  "count": 10,
  "next": "http://testserver/users/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1,
      "url": "http://testserver/users/1/",
      "username": "foo0",
      "email": "",
      "is_staff": false
    },
    {
      "id": 2,
      "url": "http://testserver/users/2/",
      "username": "foo1",
      "email": "",
      "is_staff": false
    },
    {
      "id": 3,
      "url": "http://testserver/users/3/",
      "username": "foo2",
      "email": "",
      "is_staff": false
    },
    {
      "id": 4,
      "url": "http://testserver/users/4/",
      "username": "foo3",
      "email": "",
      "is_staff": false
    },
    {
      "id": 5,
      "url": "http://testserver/users/5/",
      "username": "foo4",
      "email": "",
      "is_staff": false
    }
  ]
}

Recommended Posts

How to get people to try out django rest framework features in one file
How to write custom validations in the Django REST Framework
How to check ORM behavior in one file with django
How to deal with garbled characters in json of Django REST Framework
How to create a Rest Api in Django
How to get multiple model objects randomly in Django
How to reset password via API using Django rest framework
How to convert 0.5 to 1056964608 in one shot
How to reflect CSS in Django
How to get started with Django
[Django] How to read variables / constants defined in an external file
Logical deletion in Django, DRF (Django REST Framework)
How to delete expired sessions in Django
CRUD GET with Nuxt & Django REST Framework ②
How to get a stacktrace in python
CRUD GET with Nuxt & Django REST Framework ①
How to get a namespaced view name from a URL (path_info) in Django
How to do Server-Sent Events in Django
How to convert DateTimeField format in Django
How to drop Google Docs in one folder in a .txt file with python
How to automatically generate API document with Django REST framework & POST from document screen
How to implement Rails helper-like functionality in Django
How to get results from id in Celery
How to create a JSON file in Python
How to reflect ImageField in Django + Docker (pillow)
How to run some script regularly in Django
[Django] How to get data by specifying SQL.
How to get help in an interactive shell
Django REST framework A little useful to know.
How to get the files in the [Python] folder
Implement JWT login functionality in Django REST framework
How to read a file in a different directory
Sample to put Python Kivy in one file
Implementing authentication in Django REST Framework with djoser
How to get the variable name itself in python
How to get the number of digits in Python
How to define Decorator and Decomaker in one function
How to do zero-padding in one line with OpenCV
How to use bootstrap in Django generic class view
When you want to filter with Django REST framework
List method for nested resources in Django REST framework
How to use template engine in pyramid 1 file application
How to upload files in Django generic class view
[Work efficiency] How to change file names in Python
How to use Decorator in Django and how to make it
How to reference static files in a Django project
Implement hierarchical URLs with drf-nested-routers in Django REST framework
How to get RGB and HSV histograms in OpenCV
[Question] How to get data of textarea data in real time using Python web framework bottle
Django REST framework basics
Django Rest Framework Tips
One liner to get the nth commit hash in Git
How to embed multiple embeds in one message with Discord.py
How to adapt multiple machine learning libraries in one shot
How to get rid of server custom emoji in message.content
[Go language] How to get terminal input in real time
How to use Laravel-like ORM / query builder Orator in Django
How to update user information when logging in to Django RemoteUserMiddleware
How to import a file anywhere you like in Python
[Python] How to write an if statement in one sentence.
[Django] How to give input values in advance with ModelForm