Links

create-model-and-api.mo

Owner: Alice Breton

Prerequisites (~45 mins)

  • Have a django project installed with an app created
  • In this example we will use an already existing model city in a locations app, to show how to integrate other
    models with foreign keys.

Steps

  • In this tutorial we will create a News model, add it to the Django admin back-office and create a GET API route to
    retrieve all the news that have been created.

Create a News model (~10 min)

  • In this example we will create a new model called News that has different attributes as you will see bellow. In the
    models.py file of your app (in our case Publications) create a new model.
# File: "our_django_project/publications/models.py"
from django.db import models
class News(models.Model):
title = models.CharField(max_length=50)
description = models.TextField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
city = models.ForeignKey('locations.city')
class Meta:
verbose_name_plural = "News"
def __str__(self):
return self.title
  • CharField will return a single line form in the admin; max_length speaks for itself.
  • TextField will return a scrollable paragraph in the admin.
  • auto_now_add generates automatically a timestamp when a news is created.
  • auto_now updates the timestamp every time you change the news.
  • Concerning the city attribute which is a many to one relationship: every piece of news is linked to one city, two pieces of news can have the same city.
  • It's standard that the name of a model (not class) is singular, and django automatically puts it to plural for external use. However here, News is a singular/plural english name, so we need to make sure Django does not add a second 's' (Newss) by overriding verbose_name_plural
  • Finally, make your migrations and migrate. To do this run the following commands in your shell:
python3 manage.py makemigrations
python3 manage.py migrate
or adapt the previous commands, if you run your project with docker.
Note: Here is an article that goes into more detail How to Create Django Data Migrations

What you can check!

  • You can now check that the migrations are run and that the model exists in the database:
python3 manage.py showmigrations
  • You should see the last migration in the list.
  • You could also go in the database and check that the table has been created:
python3 manage.py dbshell
  • In the shell display the table (in case of psql \d) and check the existence of your model. More info here

Add permissions to a group (~10 min)

  • Users that are in groups need permission to access the Model. If you don't have a group yet, you can create one by
    clicking on group on the admin web site. We can now add permissions to them that will be automatically added when
    you deploy.
# File: "our_django_project/users/apps.py"
from django.apps import AppConfig
from django.contrib.admin import site
from django.db.models.signals import post_migrate
# This function takes a group and a model and adds all the permissions (read, update, delete) of this model to the group
def add_model_permissions(group, model, ContentType, Permission):
content_type = ContentType.objects.get_for_model(model)
permissions = Permission.objects.filter(content_type=content_type)
for permission in permissions:
group.permissions.add(permission)
  • You can than create a migration to add the groups permissions when the migrations are run
  • Create an empty migration:
python manage.py makemigrations --empty users
  • Then fill in your migration like so:
from django.db import migrations
# This function takes the model News and adds the permissions to the group Mayor Admin
def add_group_permissions(sender, using, apps, **kwargs):
Group = apps.get_model("auth", "Group")
Permission = apps.get_model("auth", "Permission")
ContentType = apps.get_model("contenttypes", "ContentType")
News = apps.get_model("publications", "News") #Add this line
#If the group Mayor Admin doesn't exist, this piece of code creates it.
if Group.objects.using(using).filter(name='Mayor Admin').exists():
group = Group.objects.get(name='Mayor Admin')
else:
group = Group.objects.using(using).create(name='Mayor Admin')
add_model_permissions(group, News, ContentType, Permission) #Add this second line
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.RunPython(add_group_permissions),
]
  • Then run
python manage.py migrate

What you can check!

  • Create a new user that will be part of the mayor admin group.
  • Connect yourself as the mayor and check that you can create pieces of news.

Add the News model to the admin (~5 min)

  • We now need to add the model to the admin back-office.
# File: "our_django_project/publications/admin.py"
from django.contrib import admin
from .models import News
@admin.register(News)
class MyNewsAdmin(admin.ModelAdmin):
list_display = (
'title',
'city'
)
If you don't add the list_display property, the default column title in the admin will be the result of the magic method str returned in the model (you can read more here). list_display allows you to add several columns to the admin interface.
Without list_display:
With list_display:

What you can check!

  • You can now check that the titles on the admin interface have changed.
Note: you can learn how to add other configuration to your admin by reading the official documentation

Serialize the News you get from the database (~5 min)

  • Serializers translates Django models into other formats(json, xml). In our project want to get a JSON response. Check out the official Django doc for more info.
# File: "our_django_project/publications/serializers.py"
from .models import News
from rest_framework import serializers
class NewsSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk')
city = serializers.StringRelatedField(many=False, source="city.pk") #We need this to get the primary key of the city that is attached to this news
class Meta:
model = News
fields = ('title', 'description', 'created_at', 'updated_at', 'city', 'id') #All the fields you wish to get

Create a News ViewSet (~5 min)

  • ViewSets will allow you to concentrate on modeling the state and interactions of the API, and leave the URL construction to be handled automatically
Note: Check out the Django doc for more info ViewSets
# File: "our_django_project/publications/viewsets.py"
from .models import News
from rest_framework import viewsets
from .serializers import NewsSerializer
#This is an example of filtering the pieces of news with the city-id like this: /?city-id=5
class FilterByCity(object):
def get_queryset(self): #You need to override the default get_queryset method
queryset = super().get_queryset()
city_id = self.request.query_params.get('city-id')
# This filters the queryset by city (if there is a city)
if city_id is not None:
queryset = queryset.filter(city=city_id)
return queryset
class NewsViewSet(FilterByCity, viewsets.ModelViewSet):
queryset = News.objects.all()
serializer_class = NewsSerializer
  • The NewsViewSet class will now inherit both classes FilterByCity viewsets.ModelViewSet from right to left. Therefore, the FilterByCity get_queryset method will override the ModelViewSet one.
Note: You can also use automatically generated filters (have a look here)

Mount the News ViewSet to an endpoint using a router (~5 min)

  • Django routers will allow you to easily generate different routes from your ViewSet (GET, POST, ...)
# File: "our_django_project/config/router.py"
from rest_framework import routers
from our_django_project.users.viewsets import UserViewSet
from our_django_project.locations.viewsets import CityViewSet
from our_django_project.publications.viewsets import NewsViewSet #Add this line
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'locations/cities', CityViewSet)
router.register(r'publications/news', NewsViewSet) #Add this one too
Note:Here is the Django doc if you want to learn more about Django routers

What you can check!

  • You are good to go! :D
  • If you go on this url: HOST/publications/news/ you should get something like this:
[
{
"title": "Reading the article",
"description": "Hello, I am glad I read this article!",
"created_at": "2017-11-08T15:31:30.597524Z",
"updated_at": "2017-11-08T15:31:30.597555Z",
"city": "66",
"id": 2
},
{
"title": "Tutorial",
"description": "I followed the tutorial to create a new model",
"created_at": "2017-11-08T15:33:28.834694Z",
"updated_at": "2017-11-08T15:33:28.834721Z",
"city": "66",
"id": 3
},
{
"title": "Les Français adorent !",
"description": "J'ai lu cet article, il m'a aidé à faire mon premier modèle, youhouu !",
"created_at": "2017-11-08T15:20:33.457740Z",
"updated_at": "2017-11-08T16:54:19.322703Z",
"city": "5",
"id": 1
}
]
  • If you go on this url: HOST/publications/news/?city-id=5 (that is filtered) you should get something like this:
[
{
"title": "Les Français adorent !",
"description": "Je suis française et j'ai lu cet article, il m'a aidé à faire mon premier modèle, youhouu !",
"created_at": "2017-11-08T15:20:33.457740Z",
"updated_at": "2017-11-08T16:54:19.322703Z",
"city": "5",
"id": 1
}
]