Link to live Pokébox front-end
Link to Pokébox front-end repo
- Introduction
- Project Planning
- GitHub Project
- Database Schema
- Serializers
- Views
- Permissions
- Project Settings
- Bugs and Fixes
- Testing
- Deployment
- Technologies
- Credits
This is the back-end API for Pokébox. Created using Django and the Rest Framework. Visit my front-end repo for more information.
The GitHub project board feature was used to keep track of what I was working on and what still needed to be done. I created a user story for each feature, or an issue for each to do and bug, then moved them when necessary throughout the development of both front and back-end.
Project Board:
The models required for this project are:
- Post - for user diary entries.
- Comment - for replies to diary entries.
- Like - for user likes on diary entries.
- Profile - for a user's profile information and Pokémon collection.
- Announcement - for admin user's to post website announcements.
- News - for admin user's to news items.
Entity-Relationship Diagram:
Name | Type | Details | Notes |
---|---|---|---|
id | Primary Key | unique | |
owner | Foreign Key | to User | The author of the post |
created | DateTimeField | auto_now_add=True | Date of post, automatically added on creation |
body | TextField | max_length=400 | The text content of the post |
image | ResizedImageField | blank=True, upload_to="images/", size=[600, None], force_format="WEBP" | Optional image for post. Cropped to a width of 600px and converted to WEBP |
Name | Type | Details | Notes |
---|---|---|---|
id | Primary Key | ||
owner | Foreign Key | to User | |
created | DateTimeField | auto_now_add=True | Date of comment, automatically added on creation |
body | TextField | max_length=400 | The text content of the comment |
Name | Type | Details | Notes |
---|---|---|---|
id | Primary Key | ||
owner | Foreign Key | to User | |
post | Foreign Key | to Post |
Name | Type | Details | Notes |
---|---|---|---|
id | Primary Key | ||
owner | Foreign Key | to User | |
created | DateTimeField | auto_now_add=True | Date of creation, automatically added |
about | TextField | max_length=400, blank=True, default="..." | Text field for a user's description. Has the default text "Hello! I am a new trainer just starting my Pokémon adventure." |
favorite | CharField | max_length=30, blank=True | Character field that will hold a users favorite Pokémon, assigned from a list of possibilities on the front end |
pokemon | ArrayField | Array of IntegerFields, default=list | The array will hold pokemon collected by the user, each pokemon represented by their ID (an integer) |
avatar | ResizedImageField | upload_to="avatars/", size=[300, 300], crop=["middle", "center"], force_format="WEBP", default="..." | For the user's avatar, resized and cropped to a suitable size, has a default image |
The profile object is automatically created for each new user using a post_save
signal.
Name | Type | Details | Notes |
---|---|---|---|
id | Primary Key | ||
created | DateTimeField | auto_now_add=True | Date of creation, automatically added |
body | CharField | max_length=1000 | The body of the announcement. |
Name | Type | Details | Notes |
---|---|---|---|
id | Primary Key | ||
created | DateTimeField | auto_now_add=True | Date of creation, automatically added |
title | TextField | max_length=100 | The title of the news item |
body | CharField | max_length=1000 | The body of the news item |
image | ResizedImageField | blank=True, upload_to="news/", size=[666, None], force_format="WEBP" | Optional image for the news item, resized and the format is changed |
category | CharField | max_length=5, choices = (Anime, TCG, Games, Other) | Category of the news item, with a selectable list of options |
The django Rest Framework serializer component is used as an intermediary between the database models and the API views (see below). They provide fields in addition to those from the parent model, can define which should be read only and a method for creation. Each model has it's own serializer:
The post serializer provides the following to the API view:
- Owner - This field uses the owner's username taken from the user model.
- Created - Provided by the function
get_created()
which returns a formatted date string. - Like ID - Provided by the function
get_like_id()
which returns the ID of the Like object for the Post if the user has liked it. Used to show like status on the front end. - Profile ID - Displays the post owner's profile ID. Used on the front end to link to the owner's profile when viewing the post.
- Profile Avatar - Displays the post owner's avatar. Used to represent the post owner on the front end.
The comment serializer provides the following to the API view:
- Owner - This field uses the owner's username taken from the user model.
- Created - Provided by the function
get_created()
which returns a formatted date string. - Profile ID - Displays the comment owner's profile ID. Used on the front end to link to the owner's profile when viewing the comment.
- Profile Avatar - Displays the comment owner's avatar. Used to represent the comment owner on the front end.
The like serializer provides the following to the API view:
- Owner - This field uses the owner's username taken from the user model.
create()
- This function handles the integrity error if a duplicate like object is found. This is to stop the same user liking a post more than once.
The profile serializer provides the following to the API view:
- Owner - This field uses the owner's username taken from the user model.
- Created - Provided by the function
get_created()
which returns a formatted date string. - Pokemon - Defines this field as a list that can be empty.
- col_size - Collection Size, defined as a read only field.
The announcement serializer provides the following to the API view:
- Created - Provided by the function
get_created()
which returns a formatted date string.
The news serializer provides the following to the API view:
- Created - Provided by the function
get_created()
which returns a formatted date string.
Defined by REST_AUTH_SERIALIZERS
variable in settings. The current user serializer extends DJ Rest Auth's UserDetailsSerializer to provide this additional information:
- Profile ID - Provides the current user's profile ID, used on the front-end to provide a link to their profile.
- Profile Avatar - Provides the current user's profile avatar, used on the front-end to display the logged in user's avatar.
- Is Staff - A boolean value that allows the front-end website to conditionally render specific elements if the logged in user is an admin.
Each view provides a response to the front-end based on the request. They are responsible for generating the appropriate JSON output using the defined serializer and assigning correct permissions.
PostList()
andPostDetail()
- Inherits from the
generics.ListCreateAPIView
andgenerics.RetrieveUpdateDestroyAPIView
class respectively. It specifies the serializer class asPostSerializer
and the permission classes aspermissions.IsAuthenticatedOrReadOnly
. - The
queryset
attribute is set to retrieve a list of Post objects from the database. The queryset is annotated with the counts of distinct likes and comments for each post and ordered by the creation date in descending order. - Specifies three filter backends:
filters.SearchFilter
,filters.OrderingFilter
andDjangoFilterBackend
. - The
ordering_fields
attribute defines the fields that can be used for ordering the posts, which include like_count, comment_count, and created. - The
filterset_class
attribute is set to PostFilter (see below). - The
search_fields
are set tobody
andowner__username
so a user can search for a post by either the owner of the post or by words in the body text. - The
perform_create()
method is overridden to set the owner of a newly created post to the authenticated user making the request.
- Inherits from the
PostFilter()
- Inherits from
rest_framework.FilterSet
. It is used to define filters for the Post model. - Defines the
has_image
filter as arest_framework.BooleanFilter
. This filter is associated with the image field of the Post model. - Returns a filtered queryset that excludes posts with an empty image field. If the value is False or None, it returns the original queryset without applying any filtering.
- Inherits from
CommentList()
andCommentDetail()
- Like the Post views, these two views define the relevant serializer and permissions for the comment model and overrides comment creation to provide the logged in user as owner.
- The
get_queryset()
method is overridden to provide a custom queryset for retrieving comments. It retrieves all comments usingComment.objects.all()
and then checks if a post parameter is present in the request's query parameters. If the post parameter exists, it uses theget_object_or_404()
function to retrieve the corresponding Post object. It then filters the comments queryset to only include comments associated with that particular post.
LikeList()
andLikeDetail()
- As above these views define the relevant serializer and permissions for the Like model and overrides like creation to provide the logged in user as owner.
NewsFilter()
- Inherits from
rest_framework.FilterSet
. It is used to define filters for the News model. - Defines
category
as a multiple choice filter. This filter is assciated with the category field and uses the same variable for choices. - Returns a filtered queryset that will only show news items that match the category defined in the url parameters.
- Also allows the use of a news item's ID field as the filter, so a specific item can be loaded and displayed.
- Inherits from
NewsList()
,NewsDetail()
,AnnouncementList()
andAnnouncementDetail()
- As is the norm, these two views define the relevant serializer for the news and announcement model.
- There is no perform create function this time as owner is not a required field for these models.
IsAdminOrReadOnly
is defined as the permission class in order to allow only admin users full access.
ProfileList()
andProfileDetail()
- Again defines the relevant serializer and permissions for the Profile model.
- The
queryset
attribute is set to retrieve a list of Profile objects from the database. The queryset is annotated with anExpressionWrapper()
which uses a built in Postgresql function calledarray_length
to count the length of thepokemon
array field and return it as a new field. - Specifies three filter backends:
filters.SearchFilter
,filters.OrderingFilter
andDjangoFilterBackend
. - The
ordering_fields
attribute defines the fields that can be used for ordering the profile, which includeowner__username
,col_size
, andcreated
. - The
filterset_fields
is set toowner__username
so a specific user's profile can easily be found. - The
search_fields
is set toowner__username
so a user can search for a trainer by their username.
root_route()
- Overrides the home root of the API, displays a message.
logout_route()
- This view sends a response that sets the two cookies used for authentication as empty. This effectively logs out the user. This is a fix for the DJ Rest Auth logout view present in the version being used.
This project uses two custom permissions IsOwnerOrReadOnly
and IsAdminOrReadOnly
. The first is used in the PostDetail, CommentDetail, ProfileDetail and LikeDetail views. This permission grants full CRUD access to the owner of the object and read-only access to others. The second is used on views relating to the News and Announcement models. Granting full access to admin users, and read-only access to a normal user.
The following are the non-default variables defined in settings.py essential for this project:
CLOUDINARY_STORAGE
: Contains the Cloudinary URL.DEFAULT_FILE_STORAGE
: Specifies the default file storage as Cloudinary.MEDIA_URL
: Defines the URL path for media files.
DJANGORESIZED_DEFAULT_QUALITY
: Sets the default quality for resized images.DJANGORESIZED_DEFAULT_KEEP_META
: Specifies whether to keep metadata for resized images.
SITE_ID
: Identifies the Django site.OLD_PASSWORD_FIELD_ENABLED
: Enables the use of the old password field when attempting to change password.
DEFAULT_AUTHENTICATION_CLASSES
: Defines the authentication classes for the API. Uses session authentication in development (DEV
) and JWT cookie authentication in other environments.DEFAULT_PAGINATION_CLASS
andPAGE_SIZE
: Set pagination settings.
JWT_AUTH_SECURE
: Specifies whether JWT authentication should use a secure connection.JWT_AUTH_COOKIE
: Defines the cookie name for JWT authentication.JWT_AUTH_REFRESH_COOKIE
: Sets the cookie name for JWT token refresh.JWT_AUTH_SAMESITE
: Specifies the SameSite attribute for JWT cookies.
CORS_ALLOW_CREDENTIALS
: Enables sending credentials (e.g., cookies) in cross-origin requests.CORS_ALLOWED_ORIGINS
: Lists the allowed origins for cross-origin requests.
There are no known bugs in the project at the time of project submission. I made it a priority to quickly address and resolve any issues that came up during development. To see the fixes I implemented, you can check my commit history for commits labeled with the "Fix" prefix. The project is in a solid and dependable state for evaluation and the end-user.
Testing information can be found here.
Deployment steps can be found here.
- Django - A high-level Python web framework.
- Django Rest Framework - A powerful and flexible toolkit for building Web APIs.
- dj-database-url - Enables the use of database URLs in Django. ElephantSQL provides an easy to use database URL which makes using this library essential.
- django-cloudinary-storage - A Django package that provides Cloudinary storage for both media and static files as well as management commands for removing unnecessary files. Images added to posts, user avatars and default avatars along with other static files are stored in Cloudinary.
- django-resized - Used to resize images uploaded by the user. This keeps the file size manageable, and crops avatars to a square so they display correctly on my front-end.
- gunicorn - Gunicorn ‘Green Unicorn’ is a Python WSGI HTTP Server for UNIX.
- Pillow - A Python Imaging Library that adds image processing capabilities to the Python interpreter.
- psycopg2 - Psycopg is the most popular PostgreSQL database adapter for the Python programming language. Using a Postgres database was essential for this project as my profile model contains an ArrayField which is incompatible with the default sqlite database used by django.
- Coverage - To check for 100% automated test coverage.
- dj-rest-auth - Provides a set of API endpoints that handle user registration and authentication.
- django-allauth - An integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication.
- django-cors-headers - Adds Cross-Origin Resource Sharing (CORS) headers to responses. This allows in-browser requests to the Django application from other origins.
- django-filter - Allows users to filter down a queryset based on a model’s fields, displayed as a form.
- djangorestframework-simplejwt - A JSON Web Token authentication plugin for the Django REST Framework.
- ElephantSQL - Hosting of the PostgreSQL database used by squigl.
- GitHub - Repository hosting, commit history and project management with user stories.
- Heroku - Pokébox back-end API is deployed to Heroku.
- Cloudinary - Hosting of images and other static files.
- CI Python Linter - Used to validate my Python code.
- This project was loosely based on Moments by Code Institute, a project designed to teach Django Rest Framework and React. There are some code similarities, in particular:
- Logout Route View - This code acts as a fix for the DJ Rest Auth logout view present in the version being used.
- Get Like ID Function - This returns the like object ID for the requested post if a user has liked it.
- The
IsOwnerOrReadOnly
permission class used for detail views is an example of a custom permission from the Rest Framework documentation. It can be found here.