create-model-and-api.mo

Prerequisites (~45 mins)

  • Have a django project installed with an app created

    see this.

  • 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.

    Here is the Django doc for more information.

  • 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!

python3 manage.py showmigrations
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!

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.

What you can check!

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!

[
  {
    "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
  }
]
[
  {
    "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
  }
]

Last updated