(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.)
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)
Precautions and explanations on how to use.
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)
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.pyor
django app, the argument of ʻapp.setup ()
changes. Since this article introduces how to try with one file, you can fix it with __name__
.
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.
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__
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.
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 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("```")
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