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:
or adapt the previous commands, if you run your project with docker.
What you can check!
python3 manage.py showmigrations
python3 manage.py dbshell
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'
)
What you can check!
Serialize the News you get from the database (~5 min)
# 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
# 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.
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
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
}
]
.
Note: Here is an article that goes into more detail
In the shell display the table (in case of psql \d) and check the existence of your model. More info
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 ). list_display allows you to add several columns to the admin interface.
Without list_display:
With list_display:
Note: you can learn how to add other configuration to your admin by reading the
Serializers translates Django models into other formats(json, xml). In our project want to get a JSON response. Check out the for more info.
Note: Check out the Django doc for more info
Note: You can also use automatically generated filters ()
Note:Here is the if you want to learn more about Django routers