On the previous article I demonstrated how we can use the generic views along with ModelSerializer classes to rapidly develop our REST APIs. Knowledge that you will need in your career as full stack / backend developer, however think of this article as an extension to the previous one, equipped with what we already know about REST API we will step our game up and discuss about ViewSet, ModelViewset we will dig deep into the concepts of Routers which allow us to manage our api routes in a simple and sophisticated manner as well as helping to speed up building APIs even further. There for on part II of this article i'll work you through on how React application can consume this RESTful API. There for at the end of the day we will have a full stack web app, in short we strat our development at the backend then later on we move at the frontend... so are you excited and ready to take the challange? lets do this then..... you can get source code for the bakend on github
Preparation
To kick things off create the project directory and cd into itmkdir django-music && cd django-music
Create a virtual environment to isolate our package dependencies locally, and activate the virtual environment
python3 -m venv venv && source env/bin/activate
Install Django into the virtualenv
pip3 install Django
Set up a new project kindly note give your project a name that make sense to you and those who will be reading you code for the sake of this demo mine I'll call
news_artdjango-admin startproject news_art
Django by default will create nested directory personally I don't like this behavior so I need to do a bit of housekeeping by shuffling things around, by the way this is optional.
mv news_art/manage.py ./
mv news_art/news_art/* news_art/
rm -r news_art/news_art/
Define a single application for this project this is going to be our api app, you can name it anything..., but I recommend that you use a name that make sense to you and those who will read your code for the sake of this article mine i'll call it base.
Note: All of our API information will be routed through here. Even if we had multiple apps in our project, we’d still need a single api app to control what the API does.
python manage.py startapp base
Django Rest Framework
At this stage we can proceed with installing Django Rest Framework.
pip3 install djangorestframework
Cross Origin Resource Sharing or CORS
It allows client applications to interface with APIs hosted on different domains by enabling modern web browsers to bypass the Same origin Policy which is enforced by default.
CORS enables us to add a set of headers that tell the web browser if it's allowed to send/receive requests from domains other than the one serving the page.
We can enable CORS in Django REST framework by using a custom middleware or better yet using the django-cors-headers package. For the sake of this article we will install django-cors-headers package
CORS enables us to add a set of headers that tell the web browser if it's allowed to send/receive requests from domains other than the one serving the page.
We can enable CORS in Django REST framework by using a custom middleware or better yet using the django-cors-headers package. For the sake of this article we will install django-cors-headers package
pip3 install django-cors-headers
Django File (and Image) Uploads
pip3 install pillow
Edit Settings.py
nano news_art/settings.py
By default Django will listen to traffic on the localhost adapter, I'll open it up to listen to all network adapters by doing the following
ALLOWED_HOSTS = ['*']
Since we have a new app and middleware we need to update our
INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',------- add this
'rest_framework', ------ add this
'base', -------- add this
]
corsheaders.middleware.CorsMiddleware
middleware to the middleware classes.
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
.....
.....
]
It's also good practise to set up your time zone accordingly, change this to match yours
TIME_ZONE = 'Africa/Dar_es_Salaam'
set up your static files, make sure you create this directory
news_art/static
STATIC_ROOT = os.path.join(BASE_DIR, 'static') --- add this
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'news_art/static') add this
]
Add the following Pillow setting's
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
I'll only enable CORS for specific domains:
CORS_ORIGIN_ALLOW_ALL = True
# we whitelist localhost:3000 because that's where frontend will be served
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
)
you can now close the file and make this directory
mkdir news_art/static
Models
In Django, the model is the object that is mapped to the database. When you create a model, Django executes SQL to create a corresponding table in the database, without you having to write a single line of SQL. Django prefixes the table name with the name of your Django application.The model also links related information in the database.
"Bad programmers worry about the code. Good programmers worry about data structures and their relationships."
— Linus Torvalds
Relational fields are used to represent model relationships. They can be applied to
— Linus Torvalds
ForeignKey
, ManyToManyField
and OneToOneField
relationships, as well as to reverse relationships, and custom relationships such as GenericForeignKey
for more insight about fields in models
In order to examine the
ManyToManyField
relational fields, I'll use a couple of simple models for my demonstration purpose. My models will be for music artists, and their albums plus the genre each album represent.nano base/models.py
from django.db import models
from django.conf import settings
from django.utils import timezone
from datetime import date
class Genre(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Albums(models.Model):
title = models.CharField(max_length=20)
albumId = models.CharField(max_length=10)
year = models.DateField()
cover = models.ImageField(upload_to='albums/')
genre = models.ManyToManyField('Genre', related_name='genres')
def __str__(self):
return self.title
class Artists(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=20)
cover = models.ImageField(upload_to='artists/')
bio = models.TextField()
albums = models.ManyToManyField('Albums', related_name='record')
def __str__(self):
return self.name
Now i'll create a dedicated migration file and migrate my changes to the project database.
python3 manage.py makemigrations
python3 manage.py migrate
serializers
WTF?? ok don't sweat the technique, a normal webpage requires HTML, CSS, and JavaScript (usually). But my API is only sending data in the JSON format. No HTML. No CSS. Just data. The serializer translates my Django models into JSON and then the client app translates JSON into a full-blown webpage. The reverse, deserialization, also occurs when my API accepts a user input–for example submitting a new artist–which is translated from HTML into JSON then converted into my Django model.
So to repeat one last time: urls control access, views control logic, and serializers transform data into something we can send over the internet.
nano base/serializers.py
Much of the heavy lifting comes from the serializers class within DRF which I’ll import at the top. We need to import our desired model and specify which fields we want exposed (usually you don’t want to expose everything in your model to the public).
from rest_framework import serializers
from base.models import Artists, Albums
class ArtistsSerializer(serializers.ModelSerializer):
class Meta:
model = Artists
fields = ['id', 'name', 'cover', 'bio', 'albums']
depth = 2
class AlbumsSerializer(serializers.ModelSerializer):
class Meta:
model = Albums
fields = ('albumId', 'title', 'year', 'cover', 'genre')
ViewSets
"After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output."
.list()
and .create().
/base/artists
and /base/artists/1
can respond to GET
requests and should produce different types of response. So we can no longer work with the get, post, put etc methods. We need to think more along the actions we can take on the entity (Artists) as a whole. A ViewSet works with these methods instead:list – list
all elements, serves GET
to /base/artists
create – create
a new element, serves Artists
to /base/
artists
retrieve – retrieves
one element, serves GET
to /base/artists/1
update and partial_update – updates
single element, handles PUT/PATCH
to /base/artists/1
destroy – deletes
single element, handles DELETE
to /base/artists/1
nano base/api.py
from base.models import Artists
from rest_framework import viewsets
from .serializers import ArtistsSerializer
class ArtistsViewSet(viewsets.ModelViewSet):
queryset = Artists.objects.all()
serializer_class = ArtistsSerializer
ModelViewSet
would only ask for the serializer class
and the queryset
. And then it will provide all the functionality of the different ViewSet
methods. My ArtistsViewSet
now extends the ModelViewSet
and I provided the queryset
and the serializer_class
Routers
ArtistsViewSet
to a router.
I'm going to open the
I’ll need a second
Now i'm going to update the base/urls.py
file and create the router there and register the viewset like so.nano base/api.py
from rest_framework import routers
from .api import ArtistsViewSet
router = routers.DefaultRouter()
router.register('artists', ArtistsViewSet, 'artist')
urlpatterns = router.urls
urls.py
at the project-level news_art/urls.py
files I need to add imports for settings
, include
, and static
. Then define a route for the base app. Note I also need to add the MEDIA_URL
if settings are in DEBUG
mode, otherwise I won’t be able to view uploaded images locally.nano news_art/urls.py
I'm using the empty string
''
as my route path because I want to put all the artists at the homepage. There are a number of different styles for how you can include these URLs. but i'll keep it simple.from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('', include('base.urls')),
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
Admin
base/admin.py
file so I can see my Artists app in the Django admin.nano base/admin.py
from django.contrib import admin
from base.models import Artists, Genre, Albums
class ArtistsAdmin(admin.ModelAdmin):
pass
class GenreAdmin(admin.ModelAdmin):
pass
class AlbumsAdmin(admin.ModelAdmin):
pass
admin.site.register(Artists, ArtistsAdmin)
admin.site.register(Genre, GenreAdmin)
admin.site.register(Albums, AlbumsAdmin)
superuser
account to access the admin and then execute runserver to spin up the local web server for the first time.python3 manage.py createsuperuser
python3 manage.py runserver 192.168.0.250:7000
If you go to
and when you click on thet link http://192.168.0.250:7000/admin
you’ll be able to log in to the Django admin site. Once your there create artists albums and there respective genres, so at the end of thewhen you vist 192.168.0.250:7000
you will see somthing similar to this"artists": "http://192.168.0.250:7000/artists/"
you should see somthing similar to this provided you have already populated you entries from the admin interface:
We have crafted a nice, functional REST API. The next stop would be how to consume-restful-api-with react in my next article.
Securing it, Authentication and Permissions it's somthing I handle it over to you as a homework.😉