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-musicCreate a virtual environment to isolate our package dependencies locally, and activate the virtual environment
python3 -m venv venv && source env/bin/activateInstall Django into the virtualenv
pip3 install DjangoSet 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_artDjango 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 baseDjango Rest Framework
At this stage we can proceed with installing Django Rest Framework.
pip3 install djangorestframeworkCross 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-headersDjango File (and Image) Uploads
pip3 install pillowEdit 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_APPSINSTALLED_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 thisSTATIC_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 servedCORS_ORIGIN_WHITELIST = ( 'http://localhost:3000',)
you can now close the file and make this directory
mkdir news_art/staticModels
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.pyfrom django.db import modelsfrom django.conf import settingsfrom django.utils import timezonefrom datetime import dateclass Genre(models.Model):name = models.CharField(max_length=20)def __str__(self):return self.nameclass 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.titleclass 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 makemigrationspython3 manage.py migrateserializers
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 serializersfrom base.models import Artists, Albumsclass ArtistsSerializer(serializers.ModelSerializer):class Meta:model = Artistsfields = ['id', 'name', 'cover', 'bio', 'albums']depth = 2class AlbumsSerializer(serializers.ModelSerializer):class Meta:model = Albumsfields = ('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/artistscreate – create a new element, serves Artists to /base/artistsretrieve – retrieves one element, serves GET to /base/artists/1update and partial_update – updates single element, handles PUT/PATCH to /base/artists/1destroy – deletes single element, handles DELETE to /base/artists/1nano base/api.pyfrom base.models import Artistsfrom rest_framework import viewsetsfrom .serializers import ArtistsSerializerclass ArtistsViewSet(viewsets.ModelViewSet):queryset = Artists.objects.all()serializer_class = ArtistsSerializerModelViewSet 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_classRouters
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.pyfrom rest_framework import routersfrom .api import ArtistsViewSetrouter = routers.DefaultRouter()router.register('artists', ArtistsViewSet, 'artist')urlpatterns = router.urlsurls.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 adminfrom django.urls import path, include from django.conf import settingsfrom django.conf.urls.static import staticurlpatterns = [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.pyfrom django.contrib import adminfrom base.models import Artists, Genre, Albums class ArtistsAdmin(admin.ModelAdmin):passclass GenreAdmin(admin.ModelAdmin):passclass 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 createsuperuserpython3 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.😉


