django terminal commands:
python -m venv venv
.\venv\Scripts\activate
pip install django
django-admin startproject djchat
pip install djangorestframework
cd djchat/
pip install python-dotenv
python manage.py runserver
pip install black
pip install flake8
pip freeze > requirements.txt
python manage.py startapp server
python manage.py startapp account
.\venv\Scripts\activate
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser //access admin dashboard
Generate Schema with CLI. (Auto Generate Documentation with DRF-Spectacular)
pip install drf-spectacular
#settings.py
INSTALLED_APPS: {
"drf_spectacular",
...
}
REST_FRAMEWORK = {"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema"}
SPECTACULAR_SETTINGS = {
"TITLE": "Your Project API",
"DESCRIPTION": "Your project description",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False, # whether we want to make the schema available
for a download
}
python ./manage.py spectacular --color --file schema.yml
#drf spectacular will look at our views look for any endpoints and generate a
schema.
#it is going to generate some metadata which is going to be able to record or
describe the endpoints that we have in our system and we can then utilize this
schema and swagger ui to present the data utilizing the gui.
#urls.py
from django.contrib import admin
from django.urls import path
# SpectacularSwaggerView will provide us the GUI.
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
path("admin/", admin.site.urls),
# Allow us to download the schema.
path("api/docs/schema/", SpectacularAPIView.as_view(), name="schema"),
# To access the swaggerUI
path("api/docs/schema/ui/", SpectacularSwaggerView.as_view()),
]
#You will now encounter error when starting up server and visiting root directory
'http://127.0.0.1:8000/'
#Page not found error
#Possible paths defined in urls.py will be listed
#Hence, to access a path without error, you will use specified endpoints i.e
'http://127.0.0.1:8000/api/docs/schema/ui/
Authentication:
#settings.py
REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
# whenever we build an endpoint or view which has or needs authentication we
are just going to call upon djangos session authentication class, and thats
going to help us authenticate the user, should the user be logged-in and should
we need it to authenticate the user before they can access the endpoint. Hence, we
need to make sure the user is logged in and serve them the data related to
them.
"DEFAULT_AUTHENTICATION_CLASSES": [
'rest_framework.authentication.SessionAuthentication',
],
}
Building API Endpoints.
In DJANGO we have a default router with DRF.
It allows us to automatically generate urls for our API Views. This is why we also
use viewsets.
#server/views.py
from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.response import Response
from .models import Server
from .serializer import ServerSerializer
# Create your views here.
# Two Approaches: Function Based Views and Class Based Views
# We will utilize ViewSets which is a class based view.
# ViewSets is a class that will provide CRUD based operations for a model.
# It acts as a bridge between the model and the serializer allowing us to create a
simple and powerful api with little amount of code.
# Creating an API Endpoint for Filtering Servers by Category
# There are two approaches here we can create an endpoint for every single resource
that needs to be accessed.
# i.e we can create an endpoint for category, all servers related to a particular
user, or specific user's servers.
# this can lead us to having many many endpoints and becomes tough to manage.
# the second approach which we will use is to create an endpoint, but to allow it
to pass in multiple parameters, so we can return different resource based upon the
parameters that are passed in.
class ServerListViewSet(viewsets.ViewSet):
# If we dont sent any parameters i.e the category parameter we are going to
return all of the servers.
queryset = Server.objects.all()
# list function acts as a 'get' request
def list(self, request):
# Capture Category ID passed to this end point and retreive category.
category = request.query_params.get("category")
# Return servers related to category id.
if category:
self.queryset = self.queryset.filter(category=category)
# convert data fetched to json via serializer, created serailizer.py in
server.
serializer = ServerSerializer(self.queryset, many=True)
return Response(serializer.data)
#A serializer must be created by you, call it serializer.py inside server
#server/serializer.py:
from rest_framework import serializers
# Sort of data to expect
from .models import Category, Server
# serializers has a ModelSerializer which enables us to utilize the model
information from django to quickly create a serializer.
class ServerSerializer(serializers.ModelSerializer):
# What model we are using and fields we want serialized and sent to frontend
class Meta:
model = Server
fields = "__all__"
# Register router in urls.py, use djangos default router system.
#djchat/urls.py
from django.contrib import admin
from django.urls import path
# SpectacularSwaggerView will provide us the GUI.
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from rest_framework.routers import DefaultRouter
from server.views import ServerListViewSet
router = DefaultRouter()
router.register("api/server/select", ServerListViewSet)
urlpatterns = [
path("admin/", admin.site.urls),
# Allow us to download the schema.
path("api/docs/schema/", SpectacularAPIView.as_view(), name="schema"),
# To access the swaggerUI
path("api/docs/schema/ui/", SpectacularSwaggerView.as_view()),
] + router.urls
You may now test the endpoint by visiting the url:
http://127.0.0.1:8000/api/server/select/
or
http://127.0.0.1:8000/api/server/select/?category=1
Filter via category name instead of category id requires a simple modification in
views.py
#server/views.py
class ServerListViewSet(viewsets.ViewSet):
# If we dont sent any parameters i.e the category parameter we are going to
return all of the servers.
queryset = Server.objects.all()
# list function acts as a 'get' request
def list(self, request):
# Capture Category ID passed to this end point and retreive category.
category = request.query_params.get("category")
# Return servers related to category id.
if category:
self.queryset = self.queryset.filter(category__name=category)
# convert data fetched to json via serializer, created serailizer.py in
server.
serializer = ServerSerializer(self.queryset, many=True)
return Response(serializer.data)
Refer to endpoint:
http://127.0.0.1:8000/api/server/select/?category=Cat1
Extending an endpoint.
Lets filter servers by quantity
Here we may use a parameter like the number 10 to specify how many servers to
return.
#server/views.py
class ServerListViewSet(viewsets.ViewSet):
# If we dont sent any parameters i.e the category parameter we are going to
return all of the servers.
queryset = Server.objects.all()
# list function acts as a 'get' request
def list(self, request):
# Capture Category ID passed to this end point and retreive category.
category = request.query_params.get("category")
qty = request.query_params.get("qty") #quantity
# Return servers related to category id.
if category:
self.queryset = self.queryset.filter(category__name=category)
if qty:
self.queryset = self.queryset[: int(qty)]
# convert data fetched to json via serializer, created serailizer.py in
server.
serializer = ServerSerializer(self.queryset, many=True)
return Response(serializer.data)
You may now test on endpoint:
http://127.0.0.1:8000/api/server/select/?category=Cat1&qty=1
Filtering Servers by User
Lets first make our Users table visible in our admin login.
#account/admin.py:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import Account
# Register your models here.
admin.site.register(Account, UserAdmin)
#server/views.py
class ServerListViewSet(viewsets.ViewSet):
# If we dont sent any parameters i.e the category parameter we are going to
return all of the servers.
queryset = Server.objects.all()
# list function acts as a 'get' request
def list(self, request):
# Capture Category ID passed to this end point and retreive category.
category = request.query_params.get("category")
qty = request.query_params.get("qty")
by_user = request.query_params.get("by_user") == "true"
# Return servers related to category id.
if category:
self.queryset = self.queryset.filter(category__name=category)
if by_user:
user_id = request.user.id
self.queryset = self.queryset.filter(member=user_id)
if qty:
self.queryset = self.queryset[: int(qty)]
# convert data fetched to json via serializer, created serailizer.py in
server.
serializer = ServerSerializer(self.queryset, many=True)
return Response(serializer.data)
Lets also create a new user in terminal
python ./manage.py createsuperuser
Username: admin1
Now lets create server ser4, and associate it with admin1 as its owner and member.
Now we will visit the endpoint:
http://127.0.0.1:8000/api/server/select/?by_user=true
We will not see admin1's server (ser4) but all other servers (ser1, ser2, ser3)
as the current user logged in is admin, not admin 1, we can see this when we visit
the endpoint: http://127.0.0.1:8000/admin/
Emphasis on 'admin'.
Server Associated Channels:
We will assume that if a server is returned than associated channels must be
returned too.
class ChannelSerializer(serializers.ModelSerializer):
class Meta:
model = Channel
fields = "__all__"
class ServerSerializer(serializers.ModelSerializer):
channel_server = ChannelSerializer(many=True)
# What model we are using and fields we want serialized and sent to frontend
class Meta:
model = Server
fields = "__all__"
Filtering Servers and Returning Annotation of the number of members.
We have run a query, in whichwe have the field num_members containing the count of
members in a server: #views.py
'''
if with_num_members:
self.queryset = self.queryset.annotate(num_members=Count("member"))
'''
We are going to pass that data through the serializer. #serializer.py
'''
num_members = serializers.SerializerMethodField()
'''
At the moment django doesnt know that this data we have acquired via the queryset
that is referenced by num_members is related to the new field we have create in
serializer.py above, also num_members.
So, what we need to do is just need to tell django that the data in the queryset
should corelate with num_members field in serializer.py that we want to serialize.
So that data in our queryset should be placed right here.
This will require a function in serializer.py:
class ServerSerializer(serializers.ModelSerializer):
num_members = serializers.SerializerMethodField()
...
def get_num_members(Self, obj):
#this num_members refers to num_members data returned from views.py
queryset.
if hasattr(obj, "num_members"):
return obj.num_members
return None
Now when data is to be serialized and the code hits the line:
num_members = serializers.SerializerMethodField()
django will ask itself, what is num_members, it will fire off the get_num_members
function
and get num_members data from the queryset in views.py.
You may now hit the endpoint:
http://127.0.0.1:8000/api/server/select/?with_num_members=true
One more thing if we do not pass the '?with_num_members=true' parameter into the
url, we dont want to return that field. If we try we get data back something like:
{
{
'id':1,
'num_members: null,
...
}
}
We dont want to return the field as null if not asked for.
What we are going to do is we are going to utilize the 'with_num_members' boolean
true that we are going to pass in and we are going to pass that in to the
serializer via views.py
We are going to pass in the fact that we are trying to utilize this filter into the
serializer. We will pass that in as a context.
#views.py:
serializer = ServerSerializer(self.queryset, many=True, context={"num_members":
with_num_members})
If it exists it will be passed to serializer as true, else false.
Turns out, we can then change an object post serialization as well. For that we
need a built in function: 'to_representation' in serializer.py
def to_representation(self, instance):
data = super().to_representation(instance)
#reference to the key 'with_num_members' boolean value
num_members = self.context.get("num_members")
if not num_members: #if false
data.pop("num_members", None)
return data
Configuring Django for image storage
#settings.py
STATIC_URL = "static/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "media/"
#urls.py
from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)