Owner: Alice Breton
- Have a django project installed with an app created see this.
- In this example we will use an already existing model
city
in alocations
app, to show how to integrate other models with foreign keys.
- 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.
- 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 casePublications
) 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
- 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
- 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
- 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.
- 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.
- 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
- 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
- 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)
- 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
- 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
}
]