To demonstrate the ability to create a website where users can intuitively perform CRUD operations. For the purpose of showcasing features, it has pre-made content with posts, comments and users.
The site includes custom models for Posts, Comments as well as a User Profile that enables customisation of their username, profile picture and a bio.
User stories were used to understand the features that would most benefit a user, first having those that would produce an MVP, and then incrementally adding the features believed to offer the most value.
They were labeled according to the MoSCoW prioritisation system according to the following principles:
must-havestories would inform the MVP for the website.should-havestories would add additional core features.could-havestories would improve the user experience.
The MVP for this project was considered to be a basic social media feed wherein any user could browse all the posts ever made. They would be able to register for an account and then be able to create, edit and delete their own posts and comments. Additionally they would be able to create, update and delete an account profile picture.
This MVP would fulfil CRUD operations and be a functional website, however it would lack features enabling users to better navigate the site and engage with other users. To that end should-have stories, that would enable users to view individual posts and follow other users, were seen to be the most crucial. The could-have features would have similar motivations, but also would concern the functionality of the site as the amount of content scaled.
In the end, all must-have stories were completed, but the should-have ability to follow users and the could-have features did not have time to be implemented. An Agile approach to the site development means that, despite missing these desired features, the current site is functional and polished.
The project board can be accessed here: FoodFeed project board
The site name was chosen by considering the types of names other social media apps have. Many of their names are derived from core functionality or act as a call to action. As a social media app centred around sharing and engaging with posts about food, FoodFeed was chosen to reflect that: after all, it is a feed of food. Similarly, the logo styling was designed to emphasise the "Feed" portion of the name and highlight the way content is presented as a feed.
The culture of Facebook food groups such as Roast My Ugly Vegan Food and subreddits like r/shittyfoodporn inspired users stories around allowing users to filter posts to depending on the appearance and taste of the food. A user was envisioned to have the capacity to browse a feed of whatever combination of tags they want. For instance they could specifically seek "ugly looking but tasty" foods which is a popular post type in such communities. Whilst these particular features did not make it to the site, they still informed the must-have and should-have features that would be required to enable them.
Structurally, the site was inspired by a mixture of Facebook, Reddit and Twitter, where they primarily all have a central feed and then supplementary features either on their navigation bar or on the sides of the main content.
Their sites adapt to smaller screen sizes by moving their sidebar content to navigation bar icons or drop‑down menus. For FoodFeed, it was similarly intended for the sidebar filters and follow list to become accessible through a dropdown found via icons that would prompt drop‑down menus.
| Layout | Wireframe |
|---|---|
| Main feed | ![]() |
| A page to submit content | ![]() |
| Profile page | ![]() |
The entity‑relationship diagram shows the relationships between the (default Django) User, User Profile, Post and Comment models.
The Posts and Comment are similar to one another, with both having fields pertaining to an author and their specific content type. Users are able to perform full CRUD operations, being able to view posts and comments through feeds and also able to create, update, and delete their own instances of both.
Users are able to make and update their content through corresponding forms. Where fields are required, blank submissions are prevented from being posted. If this is bypassed and the request reaches the view handler functions, the submission will be rejected at that level.
It is worth noting that the updated_on field is not actually used in the site's current state, but I didn't want to remove it and its data from the model in anticipation of future changes.
Users can also perform full CRUD operations on their User Profile image and bio to customise their profile pages. They can also update their associated username, which changes the username field associated with the (default Django) User model.
Users are able to make and update these profile details via a corresponding form accessed by the Edit Profile button visible on their profile page. Where fields are required, blank submissions are prevented from being posted. If this is bypassed and the request reaches the view handler functions, the submission will be rejected at that level.
I wanted users to have the ability to have profile pages with a username, bio, and image. The Django project settings provide the ability to provide a custom model to handle users, so creating a custom user model to replace the existing one would have worked. However, this felt like an overcomplicated solution to achieve what is effectively just wanting to add two extra fields to the pre-existing User model.
As the ERD diagram indicates, the approach used was to define an additional User Profile model with these additional fields. The User Profile can be considered the true representation of users, with User only being used to handle access control.
Currently whichever model is considered the author by posts and comments makes little difference, however I felt the User Profile would be more maintainable and scalable when considering future features. Features like display names, or profile pictures rendering in posts, all seemed to build on the User Profile model, so it made sense to choose that one in anticipation of future changes.
When users register to the website, the Django project uses the original User model to create a new user. This action (by default) sends a signal that the create_user_profile view function receives and responds to by creating a corresponding User Profile.
The site is hosted on Heroku, which required some configuration to enable it to work. The GitHub repository needed connecting to the Heroku application so that its content could be deployed. Automatic deployment is not configured, so manual deployments must be made to keep the site up to date.
Application configuration required:
- Set Heroku as an allowed host in the project settings.
- Create a Procfile supported by the gunicorn library.
- Handle static files with the WhiteNoise library.
Heroku configuration required:
- Add the
SECRET_KEYenvironment variable to provide authorisation to the Django application. - Add the
DATABASE_URLto access the database. - Add the
CLOUDINARY_URLto access Cloudinary services.
Prepare your Django application:
- Log in to GitHub and create a new repository for your project.
- Set up a basic Django application.
- Secure your environment variables:
- Delete the value of the
SECRET_KEYvariable premade within the project directory'ssettings.py - Create an
env.pyfile and declare a secureSECRET_KEYvariable there. - Create a
.gitignorefile and add yourenv.pyfile to it so that your sensitive information won't get exposed. - In your project's
settings.pyhave yourSECRET_KEYtake its value from yourenv.pyfile. - Add any additional sensitive to your
env.pyfile such as anyDATABASE_URLorCLOUDINARY_URLvariables your application will rely on.
- Delete the value of the
- Add a
Procfilein the root of your project to later enable Heroku to run the site. - Add, commit, and push your code to the GitHub repository so it can later be accessed by Heroku.
Configure your Heroku site:
- Log in to Heroku.
- Create an application:
- From the Heroku dashboard, click New and then Create new app.
- Give the app a unique name.
- Choose an appropriate region for deployment.
- Add your environment variables to Heroku:
- In the Heroku app dashboard, go to Settings and navigate to Config Vars.
- Click to Reveal them and then copy your project's
SECRET_KEYand any other variables defined in yourenv.pyfile.
- Connect Heroku to GitHub:
- In your Heroku app dashboard, go to the Deploy tab.
- Under Deployment method, select Connect to GitHub.
- Authorise Heroku to access your GitHub account when prompted.
- Search for and select the correct GitHub repository.
- Deploy the Django application:
- Scroll down to the Manual Deploy section.
- Select the branch you want to deploy (usually main or master).
- Click Deploy Branch.
- Wait for the build and deployment process to complete.
- Access the live site through the View button shown when it has finsihed.
Visitors to the site are able to browse the main feed, view specific posts and browse a user's profile page. They also have the ability to create their own account to then interact with content.
| Feature | Mobile | Desktop |
|---|---|---|
| View all content in a main feed | ![]() |
![]() |
| Copy content's URL | ![]() |
![]() |
| View specific content | ![]() |
![]() |
| View user's profile page | ![]() |
![]() |
| Register an account | ![]() |
![]() |
Navigation bar buttons indicate login status and enable users to register, log in, or log out. Signed‑in users also have the options to create a post and see their own profile.
Site content provides the ability to visit user profiles by clicking an author's username. The copy button allows a user to get a URL that allows viewing of that specific content. Signed‑in authors get further buttons that allow them to edit or delete the content.
| UI element | Images |
|---|---|
| Navigation bar signed in | ![]() |
| Navigation bar signed out | ![]() |
| Navigation account buttons |
Clicking the user icon, pictured in the table below, will take you to your profile page. From here you can click on the edit profile button to make changes to your profile username, image and bio as showcased later in the User Profile CRUD features section.
| UI element | Images |
|---|---|
| View your profile page | ![]() |
| Edit your profile page | ![]() |
| UI element | Images |
|---|---|
| Create a post | ![]() |
| Create a comment | ![]() |
| Edit your content | ![]() |
| Delete your content | ![]() |
Clicking a content author's username will take you to their profile page. This can be another way to navigate to your own profile if you are the author of the post or comment.
| UI element | Images |
|---|---|
| View a content author's profile page | ![]() |
| Copy content's URL | ![]() |
Once a user has registered an account they have a profile page. From this page users can perform CRUD operations relating to the underlying custom User Profile model, allowing them to customise their username, image and bio through the Edit Profile button in their profile details section.
When a user creates an account they have a blank user profile with only their username. Their profile defaults to displaying the static no-user-image and having no bio section.
Their displayed name is sourced from the Django User model username field and used in site authentication so, whilst they are free to update it, they are prevented from removing it completely. They are completely free to create, edit and delete their profile image and bio.
| Feature | Mobile | Desktop |
|---|---|---|
| Initial profile view | ![]() |
![]() |
| Create profile details | ![]() |
![]() |
| Create results | ![]() |
![]() |
| Edit profile details | ![]() |
![]() |
| Edit results | ![]() |
![]() |
| Delete profile details | ![]() |
![]() |
| Delete profile picture confirm | ![]() |
![]() |
| Delete results | ![]() |
![]() |
Users have the ability to create, update and delete their posts and comments. They can use site buttons to access pages with forms, or the delete modal in the case of deletion.
| Feature | Mobile | Desktop |
|---|---|---|
| Create posts | ![]() |
![]() |
| Edit posts | ![]() |
![]() |
| Delete posts | ![]() |
![]() |
| Create comments | ![]() |
![]() |
| Edit comments | ![]() |
![]() |
| Delete comments | ![]() |
![]() |
All code has been validated and found to have no errors. The results can be seen in the table below.
The tools rated the site highly except for Lighthouse's score for performance. The test was performed on the main feed, which contains high‑quality images. A delay in image rendering is sometimes visible on this page and on the profile view.
Cloudinary has documentation on image optimisation, which did not seem to have an obvious fix, but would be a good start if I wanted to try to optimise image delivery. I did try looking at the platform's optimisation settings available on the website, but did not find that swapping the default encoding to chroma subsampling made a difference.
| Tool | Scores |
|---|---|
| Lighthouse - mobile | ![]() |
| Lighthouse - desktop | ![]() |
| Web Accessibility Evaluation Tool | ![]() |
Testing was primarily achieved through unit tests that checked the processing of data and requests worked as intended. The test suite finds no errors and all 59 cases pass. It is worth noting that it takes a long time to complete the suite, which would be greatly reduced if the Cloudinary endpoint were mocked.
The unit test coverage is highlighted below, with levels of details depending on the complexity of the test cases. All forms and views were tested for various situations.
| Scenario | Coverage Comments |
|---|---|
| Forms receive valid data |
|
| Forms receive invalid data |
|
The CloudinaryField used does not have file type validation or subclasses that define file types, so submitted images must be validated as images at the view level. As images are crucial to a post, an invalid image causes the submission to be rejected.
| Scenario | Coverage Comments |
|---|---|
| Get specific post or comment content |
|
| Get post or comment create and update pages |
|
| Submitting post or comment create and update requests |
|
| Deleting post or comment |
|
The CloudinaryField used does not have file type validation or subclasses that define specific file types, so submitted images must be validated as images at the view level. Here, invalid images are ignored but do not cause the edit submission to be rejected.
| Scenario | Coverage Comments |
|---|---|
| New User creation also creates a corresponding User Profile object |
|
| Get specific profile |
|
| Get profile editing page |
|
| Submitting profile create and update requests |
|
| Profile image is not valid |
|
| Profile image removal toggled |
|
Manual testing was used to check site responsiveness, buttons respond appropriately, and that everything generally works as expected. Of particular concern was the JavaScript used in the profile edit submission form.
The CloudinaryField used accepts files generally, rather than only images, so when rendering the profile edit form it would not visually display the current image. This and the remove image toggle were added through JavaScript after render. I believe this could also have been resolved with a custom widget defined in the UserProfileForm, but I didn't feel I had the time to figure out how that might work.
Most assets used are my own, but the following were sourced externally:
- Site icons were sourced from Font Awesome
- Marigold Engevita product image sourced from one of their retailers
- Garfield model asset image that appeared in The Garfield Show: Threat of the Space Lasagna
I used GitHub Copilot to generate code such as HTML boilerplates, function doctsrings or for similar tasks that would be tedious to do by hand.
Sometimes this worked quite well, though I found text generated for docstrings would often miss things or unnecessarily hyper‑fixate on certain aspects.
GitHub Copilot was used to assist in debugging and suggesting approaches, with it really helping in filling in gaps that Django documentation, Cloudinary documentation and community discussion didn't seem to address.
I asked the AI to perform checks for any typos or logical errors throughout. It noted the unit tests that were uploading images to Cloudinary didn't then delete the resource after test completeion. It suggested code that could be used to delete these resources and prevent issues.
It also helped with the design by suggesting an accent colour to visibly distinguish page content like buttons. It suggested warm colours such as orange, because users would associate it with warmth and because those colours are known to be able to stimulate our appetite.
I used AI to construct many of the form unit tests as these tests were straightforward. It was able to suggest negative cases I hadn't initially considered.
Unfortunately I didn't find it as useful for testing the views as it often made mistakes when handling the image fields. On a similar note, I had a lot of trouble constructing valid image data for testing and its recommendations were off‑topic or incorrect.
I found using AI to be rather mixed, with it being useful as a sounding board, but finding it often struggled to generate relevant suggestions in many situations. For simple use cases it was a great time saver, which enabled me to focus on
It was sometimes really useful in situations where I had knowledge gaps as it was able to provide new leads to investigate.
| Learning Objective | How it had been achieved | README discussion (if applicable) |
|---|---|---|
| 1.2 Database | User Profile model - the user can interact with this data by visiting their profile as shown in the User Profile Page Buttons section. | User Profile Model |
| 2.2 CRUD Functionality | Post and Comment delete operation now works | Post and Comment CRUD User Profile CRUD |
| 6.2 Document Deployment Process | Additional detail added | Detailed deployment walkthrough |
| 7.1 Design and Implement a Custom Data Model | User Profile model - the user can interact with this data by visiting their profile as shown in the User Profile Page Buttons section. | User Profile Model |
| 8.1 Use AI tools to assist in code creation | Additional detail added | Code creation |
| 8.3 Use AI tools to optimize code for performance and user experience | Additional detail added | Code performance and UX |
| 8.4 Use AI tools to create automated unit tests | Additional detail added | Automated testing |
| 8.5 Reflect on AI’s role in the development process and its impact on workflow | Additional detail added | Workflow impact |
































































