This repository contains the back-end API for the Stitch Space website. The front-end repository can be found here. The back-end API is built using Django, Django REST Framework, Python, and PostgreSQL, and is hosted on Heroku.
This README focuses exclusively on the back-end components and functionality within this repository. For information on the front end, including competitor analysis, development management with GitHub Projects, and Agile methodology, please refer to the README in the front-end repository.
To visit the deployed Stitch Space site click here
The goal of this API is to provide the required data to the front-end application to power Stitch Space: the dedicated space for fibre artists to showcase their portfolios.
I constructed this Entity Relationship Diagram prior to starting my project, to ensure the key entities and relationships were defined in Stitch Space.
Not shown in the diagram is that I will set two unique constraints to ensure:
- A user can only follow another user once
- A user can only rate a piece once
Data validation rules ensure the accuracy and reliability of information stored in the system, ensuring all entries adhere to expected formats. Below, I have detailed the requirements either defined in my models, required by my forms or as they stand in the Abstract User model inherited from Django:
users
- user_id: must be a unique integer (auto-assigned by Django)
- first_name: must be a non-empty string (max-length 150 characters)
- last_name: must be a non-empty string (max-length 150 characters)
- email: must be a valid email format and unique within the system
- password: Must meet validity requirements set out by Django's built-in password validators, quoted below from the source.
- UserAttributeSimilarityValidator, which checks the similarity between the password and a set of attributes of the user.
- MinimumLengthValidator, which checks whether the password meets a minimum length. This validator is configured with a custom option: it now requires the minimum length to be nine characters, instead of the default eight.
- CommonPasswordValidator, which checks whether the password occurs in a list of common passwords. By default, it compares to an included list of 20,000 common passwords.
- NumericPasswordValidator, which checks whether the password isn’t entirely numeric.
- image: must be a valid URL (max-length 1024 characters)
- biography: an optional text field
- art_type: must be one of the predefined types (knitting, crochet, embroidery, weaving, dyeing, other)
- last_visited_notifications: Datetime that may be empty (until first value is added)§
- created_at: Datetime assigned on creation of user instance
- updated_at: Datetime that may be empty (until first update is carried out)
pieces
- piece_id: must be a unique integer (auto-assigned by Django)
- title: must be a non-empty string (max-length 150 characters)
- image: must be a valid URL (max-length 1024 characters)
- user_id: must be an existing user_id
- art_type: must be one of the predefined types (knitting, crochet, embroidery, weaving, dyeing, other)
- created_at: Datetime assigned on creation of piece instance
- updated_at: Datetime that may be empty (until first update is carried out)
comments
- comment_id: must be a unique integer (auto-assigned by Django)
- content: a mandatory text field
- piece_id: must be an existing piece_id
- user_id: must be an existing user_id
- created_at: Datetime assigned on creation of comment instance
- updated_at: Datetime that may be empty (until first update is carried out)
ratings
- rating_id: must be a unique integer (auto-assigned by Django)
- user_id: must be an existing user_id
- piece_id: must be an existing piece_id
- score: must be an integer between 1 and 5
- created_at: Datetime assigned on creation of rating instance
- updated_at: Datetime that may be empty (until first update is carried out)
followers
- follower_id: must be a unique integer (auto-assigned by Django)
- user_id: must be an existing user_id
- follower_id: must be an existing follower_id
- created_at: Datetime assigned on creation of follower instance
- updated_at: Datetime that may be empty (until first update is carried out)
notifications
- notification_id: must be a unique integer (auto-assigned by Django)
- piece_id: optional - used only for comments and ratings
- actor_id: must be an existing user_id
- recipient_id: must be an existing user_id
- interaction_type: must be one of the predefined types (comment, rating, follow)
- created_at: Datetime assigned on creation of notification instance
During implementation, I made some tweaks to my planned models to ensure maximum code readability and to follow convention I'd not previously known, where relevant.
- It is not best practice to use '_id' suffixes on foreign keys, so these were removed.
- It is clearer to call the user that has been followed 'followed_user' rather than just 'user' so I changed this in the Followers table.
- As Django had an in-built
Users
model already which was being used bydj_rest_auth
I decided to rename my "Users" model to "Profiles" during development.
I also needed to add/remove some fields:
- Users:
- I found that 'username' is a required field when extending Django's AbstractUser model. I didn't want to use the AbstractBaseUser so decided to implement a username after all but use the
email
field to populate it. - The 'art_type' field wasn't actually powering any functionality so was not implemented because it was redundant.
- I found that 'username' is a required field when extending Django's AbstractUser model. I didn't want to use the AbstractBaseUser so decided to implement a username after all but use the
- Pieces: I added 'featured' to this model to enable me to mark certain pieces as featured to go on the home page of the site.
- Comments: I didn't need the 'updated_at' field in this model because I don't allow users to edit their comments.
- Followers: Again, the 'updated_at' field wasn't needed in this model because a user either followed a user or they didn't. Therefore either the row in the table existed or was deleted. There was nothing to update.
This section outlines the key API endpoints for the application, detailing the HTTP methods, authentication and authorisation requirements, as well as descriptions for each operation. These endpoints allow interaction with core features, such as user management, profiles, pieces, comments, and ratings. All endpoints are designed to follow RESTful principles, making them intuitive and easy to work with in client applications.
The table below provides an overview of the available endpoints, the supported HTTP methods, and the associated authentication or authorisation requirements:
Endpoint | Allowed Methods | Authentication/Authorisation | Description |
---|---|---|---|
dj-rest-auth/login |
POST | No authentication required | User login |
dj-rest-auth/logout |
POST | No authentication required | User logout |
dj-rest-auth/registration/ |
POST | No authentication required | User registration |
profiles/ |
GET | No authentication required | List all profiles |
profile/<int:id>/ |
GET, PATCH, PUT, DELETE | Requires authentication | Retrieve, update, or delete a profile by ID |
profile/<int:id>/followers/ |
GET | No authentication required | List followers for a profile |
profile/<int:id>/followers/add/ |
POST | Users cannot follow themselves | Add a follower to a profile |
profile/<int:id>/followers/remove/ |
DELETE | Users can delete their own follows | Remove a follower from a profile |
profile/<int:id>/following/ |
GET | No authentication required | List profiles the user is following |
profile/<int:id>/notifications/ |
GET | No authentication required | List notifications for a profile |
pieces/ |
GET | No authentication required | List all pieces |
pieces/create/ |
POST | Requires authentication | Create a new piece |
pieces/feed/ |
GET | No authentication required | List pieces of profiles the logged in user is following |
pieces/<int:id>/ |
GET, PATCH, PUT, DELETE | Requires authentication | Retrieve, update, or delete a piece by ID |
pieces/<int:id>/comments/ |
GET, POST | Requires authentication, read access allowed for all users | List or create comments on a piece |
ratings/ |
GET | No authentication required | List all ratings |
ratings/<int:id>/ |
GET, PATCH, PUT, DELETE | Requires authentication, read access allowed for all users | Retrieve, update, or delete a rating by ID |
pieces/<int:id>/ratings/ |
GET, POST | Requires authentication, read access allowed for all users | List or create ratings for a piece |
To enhance the flexibility of the API, several endpoints provide options for filtering and sorting results. This allows the frontend to tailor its requests to specific needs, retrieving only the most relevant data. The table below outlines the filtering and search capabilities of the key endpoints:
Endpoint | Filtering Options | Search Options |
---|---|---|
dj-rest-auth/login |
None | None |
dj-rest-auth/logout |
None | None |
dj-rest-auth/registration/ |
None | None |
profiles/ |
Sort profiles by any field, defaults to sorting by ID | None |
profile/<int:id>/ |
None | None |
profile/<int:id>/followers/ |
Filter by follower's profile ID, sort by any field | None |
profile/<int:id>/followers/add/ |
None | None |
profile/<int:id>/followers/remove/ |
None | None |
profile/<int:id>/following/ |
Sort by any field, defaults to sorting by ID | None |
profile/<int:id>/notifications/ |
None | None |
pieces/ |
Filter by type of art, owner’s profile ID, or featured | Search by title, owner's first name or last name |
pieces/create/ |
None | None |
pieces/feed/ |
None | None |
pieces/<int:id>/ |
None | None |
pieces/<int:id>/comments/ |
Sort comments by creation date | None |
ratings/ |
Filter by piece or profile | None |
ratings/<int:id>/ |
None | None |
pieces/<int:id>/ratings/ |
Filter by profile owner ID | None |
To handle larger datasets and ensure good performance, all list-based endpoints utilise pagination. This structure helps limit the number of results returned in a single response. The following structure describes the pagination response format:
{
"count": 64, // Total number of objects in the database
"nextPage": 2, // Page number of the previous page of results
"previousPage": 4, // Page number of the previous page of results
"results": [{}] // The objects on this page (e.g page 3)
}
Manual testing was performed throughout the development process to ensure that all API endpoints, models, and interactions between the backend and the frontend worked as expected. Below is an overview of key areas tested, along with specific tests performed and edge cases covered:
- User Registration and Authentication
Goal: To ensure that user registration, login, and logout processes are functioning as intended.
Tests:
- Successfully registered a new user and verified their data (first name, last name, email) was stored correctly in the database.
- Logged in using valid credentials and checked that the appropriate response, including authentication tokens, was returned.
- Logged out and confirmed that the user's session was invalidated.
Edge Cases:
- Attempted to register with invalid data (e.g., mismatched passwords, missing fields) to ensure proper validation messages.
- Tried logging in with incorrect credentials to verify that error messages were displayed, and no tokens were issued.
- Profile and User Data Management
Goal: To validate that profiles can be retrieved, updated, and deleted, and that users can manage their own profiles.
Tests:
- Created new profiles, updated personal details (e.g., biography, profile image URL), and verified changes were reflected in the database.
- Ensured that profiles could be retrieved by their ID and displayed correctly on the front end.
- Deleted a profile and verified that related data (e.g., art pieces) were handled appropriately in the database.
Edge Case:
- Attempted to update another user's profile without proper authorisation to ensure access was restricted.
- Art Piece Management
Goal: To verify that users can create, update, and delete art pieces, and that related data (e.g., comments, ratings) is stored correctly.
Tests:
- Created new pieces with valid data and confirmed they appeared in the database.
- Edited the title, image, and art type and verified the updates persisted.
- Deleted a piece and confirmed it was removed from the user's portfolio and that related comments or ratings were handled correctly.
Edge Cases:
- Submitted incomplete forms (e.g., missing required fields) to test that validation prevented improper submissions.
- Ensured users could not edit or delete pieces they did not own.
- Comments and Ratings
Goal: To ensure that comments and ratings can be added to art pieces and that data integrity is maintained across the system.
Tests:
- Created comments on various art pieces and verified they were stored correctly and displayed on the front end.
- Rated pieces and ensured that the rating was averaged correctly for the piece.
- Deleted comments and ratings and ensured that the correct entries were removed from the database.
Edge Cases:
- Tried posting empty comments to test that validation prevented submission.
- Tested the system to ensure users could not rate their own pieces.
- Followers and Notifications
Goal: To verify that users can follow other profiles and receive notifications for relevant interactions.
Tests:
- Followed and unfollowed users, checking that the follower relationships were stored and deleted correctly.
- Created notifications via actions new comments, ratings, or follows, and ensured that they were delivered to the correct users.
The code I wrote was also passed through validators/linters at the end to ensure adherence to coding standards and best practices, ultimately aiming for robust and maintainable code.
Language | Validation Method | Outcome |
---|---|---|
Python | CI Python Linter | Whitespace, line length and number of blank line errors. All errors resolved |
The majority of the bugs found were on the front end repository, and ten of them have been detailed there. I have included an example of a bug found here in the back-end repository below:
Issue: Registering a new user doesn't assign their first name and last name correctly to the database, thus their Stitch Space is not populated correctly on the front end.
Fix: I found the solution here. The first_name and last_name fields were not being properly assigned during user registration so I needed to add the registration serializer and customise it to include these fields and ensure they were correctly saved to the database upon user creation.
-
Set Environment Variables Defined and set the necessary environment variables in my project to configure the backend with external services and security settings:
- CLIENT_ORIGIN: set this to the URL of the frontend app that will be making requests to the backend.
- DATABASE_URL: specified the PostgreSQL database connection string.
- DISABLE_COLLECTSTATIC: set this to '1' to skip static file collection during deployment, used for Heroku deployments.
- SECRET_KEY: defined a secret key for Django's security features.
-
Installed Libraries for Database Connection Installed the necessary libraries to handle the database connection and configuration:
- psycopg2: PostgreSQL adapter for Python to allow Django to interact with a PostgreSQL database.
- dj-database-url: simplifies the database configuration by allowing the use of a single DATABASE_URL environment variable.
-
Configured dj-rest-auth for JWT Authentication Set up dj-rest-auth to handle JSON Web Token (JWT) authentication for the API. This involved updating the Django settings file to use dj-rest-auth as the authentication system.
- Updated INSTALLED_APPS with dj_rest_auth and rest_framework.
- Set JWT-specific settings in the settings.py file for token handling.
-
Set Allowed Hosts Configured ALLOWED_HOSTS in the settings.py file to ensure that only trusted domains can access the backend. I added the domain of the deployed app and any relevant subdomains.
-
Configured CORS Set up Cross-Origin Resource Sharing (CORS) to control which origins are permitted to interact with the API. Installed and configured the django-cors-headers library.
-
Set Default Renderer to JSON Configured the default renderer in the Django REST framework to JSON to ensure API responses are sent in the correct format.
-
Added a Procfile for Heroku Deployment Created a Procfile in the root directory of the project to instruct Heroku on how to run the application. This includes commands for running the web server and managing database migrations.
-
Ignored env.py For security purposes, I created an env.py file to store environment variables locally and added it to .gitignore to ensure it is not tracked by version control.
-
Generated requirements.txt Created a requirements.txt file via pip, which lists all the Python dependencies needed to run the project.
- Install Python and PostgreSQL
- Set up a virtual environment and install the dependencies with
pip install -r requirements.txt
- Apply DB Migrations with
python manage.py migrate
- Run the development server with
python manage.py runserver
This project was deployed to Heroku: a hosting platform.
I referred to the Code Institute material on Django Rest Framework and related concepts.
- I built my flowcharts using Mermaid in my readme.
- I used ChatGPT to explain error messages and research the best way to go about my implementation.
- I found my Django settings configuration for JWT cookies in this article
I also used the documentation of all the elements included in this project:
As ever, I want to thank the open source community for the great resources that teach me so much and also remind me of what I learnt in my Code Institute lessons.
I believe I have credited where I used specific items in the previous section but this is a general credit to the reference resources I looked through to teach me new elements as well as reminding me how things I'd already come across worked as I went along.
Every effort has been made to credit everything used, but if I find anything else specific later on that needs crediting, that I missed, I will be sure to add it.