- LO1 Using Agile to plan/design a Full-Stack app with Django
- LO2 Develop & implement a data model to query & manipulate data
- LO3 Implement authorisation, authentication & permissions
- LO4 Design, create, execute Full-Stack/Django testing
- LO5 Utilise GitHub & hosting services safely
- LO6 Deploy the web app using a cloud based platform ensuring security
- LO7 Demonstrate object-based concepts by implementing a custom model
- LO8 Leverage AI tools to orchestrate the software development process
- Credits
MaxWags is a full-stack application built with Django that serves as a local dog walking business homepage and a community platform.
I imagined it to be commissioned by someone called Max, a lifelong dog lover, who owns a business that serves the local community, collecting dogs in the morning from their customers and taking them to a private countryside field for several hours to play and socialise while their owners are at work, before returning them safely home.
Inspiration came from a local business who used to take care of my dog whilst I was working and the realisation that community spaces are decreasing globally, so the secondary purpose is to be an online space to meet other like-minded customers who all have something in common. I believe this would also help foster a sense of customer loyalty to both the community and the business.
The initial design used the facebook colour scheme as I believed it would be easier to navigate for the companies older clients whilst also being familiar and friendly for their younger customers, however this had to change due to contrast issues for colourblind users.
In truth, the idea behind this project was an excuse to somehow dedicate my final project to my labrador Laiker, who passed away part way through the course and would always be underfoot whilst I studied.Initially there were 4 contrast errors, due to using roughly similar colours to Facebook (blue under white text), this caused an issue for colourblind users so a new colour scheme of green under white text was chosen.
- Guest: Curious pet owners or community members. Can browse the homepage and view public dog posts.
- Registered User: Dog owners whose pets attend MaxWags sessions. Can comment on posts and engage with the community.
- Walker (Staff): MaxWags staff responsible for dog care and content. Can upload new photos and captions from each session.
- Admin (Site Owner): Business owner or developer. Manages all users, posts, and comments via the Django admin panel.
- Homepage: Introduces MaxWags and its services with a clean, friendly design.
- Dog Posts Feed: A gallery of dog photos uploaded by walkers, with captions and timestamps.
- Comments: Owners can comment on posts to create a sense of community.
- Authentication: Role-based registration and login for users and staff.
- Media Hosting: Cloud-based image storage via Cloudinary for reliability and performance.
- Responsive Design: Fully mobile-friendly, built with Bootstrap 5 for accessibility and ease of use.
| Page | Pass/Fail | Evidence |
|---|---|---|
| Home | Pass | ![]() |
| Home | Pass | ![]() |
| Posts | Pass | ![]() |
| Home | Pass | ![]() |
| Register | Pass | ![]() |
| Login | Pass | ![]() |
| Upload | Pass | ![]() |
| Edit | Pass | ![]() |
| Delete | Pass | ![]() |
A Postgres Database linked to Django to allow the management of data records with at least one custom model (included in the code snippet below).
class DogPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='dog_posts')
image = CloudinaryField('image', default='placeholder')
caption = models.CharField(max_length=120, blank=True)
date_posted = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.user.username}'s post on {self.date_posted.strftime('%d-%m-%Y %H:%M:%S')}"
The project was managed with AGILE in mind utilising a projectboard and user stories. The public project board can be found here: https://github.com/users/FollowRob/projects/10
| User Story |
|---|
| As a Guest, I want to view a welcoming home page so that I can learn what MaxWags is about and navigate the site easily. |
| As a Guest, I want to browse dog posts so that I can see photos of dogs and their captions without signing up. |
| As a Guest, I want to create an account so that I can log in and interact with the community. |
| As a Registered User, I want to securely log in and log out so that I can access member features. |
| As a Walker, I want to upload a photo and caption of dogs I’ve walked so that owners can see their pets having fun. |
| As a Registered User, I want to comment on posts so that I can engage with other users. |
| As a Registered User, I want to edit or delete my own comments so that I can correct mistakes or remove unwanted messages. |
| As an Admin, I want to assign or remove walker status so that I can control which users can upload dog posts. |
| As a User, I want the website to be easy to navigate and readable on all devices so that I can use it anywhere. |
| As a User, I want clear visual feedback when I take an action so that I know if it succeeded or failed. |
| As a Developer, I want the MaxWags app deployed on Heroku with Cloudinary storage so that it’s accessible online. |
Code quality can be assessed within the files within the repo however an example of custom Python logic with compound statements (from views.py) has been included below for brevity:
@login_required
def add_comment_view(request, post_id):
post = get_object_or_404(DogPost, id=post_id)
if request.method == 'POST':
text = request.POST.get('text')
if text:
Comment.objects.create(
post=post,
user=request.user,
text=text,
)
messages.success(request, 'Comment added successfully.')
else:
messages.error(request, 'Comment cannot be empty.')
return redirect('posts')
PEP8 standards were adhered to utilising the guidelines captured in the link below: Link to PEP8 guidelines
As a note on linting, only files edited personally were linted, standard files created by Django were ignored.
| File name | Pass/Fail | Notes | Image |
|---|---|---|---|
| admin.py | Pass | ![]() |
|
| forms.py | Pass | ![]() |
|
| models.py | Pass | ![]() |
|
| urls.py | Pass | ![]() |
|
| views.py | Pass | ![]() |
|
| settings.py | Pass | Four errors on code generated by Django | ![]() |
Initially my design philosophy was that the website would be tailed to everyone however that also just means it's specific to nobody so instead I went for a minimalistic design with similar design elements as Facebook, initially using a similar blue for the navbar with white text over the top. The idea here was that it would be minimal and easy for the elderly (high potential as clients) to use and navigate and that the similar colouring to facebook would be familiar and comfortable to a younger generation. However due to WAVE requirements the colour scheme had to be changed away from the "Facebook blue" to accomodate colourblind users and instead a relaxing green was chosen.
A UX design wireframe can be found in section: LO1.1 and a ERD for the Python logic can be found below:

The MaxWags app uses a relational database powered by PostgreSQL (via Django ORM).
The schema includes three main models: User, DogPost, and Comment.
class User(AbstractUser):
is_walker = models.BooleanField(default=False)
class DogPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
image = models.ImageField(upload_to='dog_images/')
caption = models.CharField(max_length=120)
date_posted = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
post = models.ForeignKey(DogPost, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
date_posted = models.DateTimeField(auto_now_add=True)python manage.py makemigrations python manage.py migrate
The Comments model provides any registered user with full CRUD functionality on any posts. Logged in users can create, view, edit and delete comments on staff posts. Users can only edit or delete their own comments.
class Comment(models.Model):
post = models.ForeignKey(DogPost, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
date_posted = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Comment by {self.user.username} on {self.post.id}"
@login_required
def add_comment(request, post_id):
post = get_object_or_404(DogPost, id=post_id)
if request.method == 'POST':
text = request.POST.get('text')
if text:
Comment.objects.create(post=post, user=request.user, text=text)
messages.success(request, "Comment added successfully!")
return redirect('posts')
|
| Operation | Description | Evidence |
|---|---|---|
| DogPost | Post created message | ![]() |
| Logout | Logout message | ![]() |
| Comment | Comment deleted message | ![]() |
| Comment | Comment updated message | ![]() |
| Comment | Comment added message | ![]() |
Changes are fed to the user with real-time messages within the application, on the page that the change occurs on.
Max wags contains a form with validation for potential users to be able to register for an account. They can still view posts and comments without an accounts but an account is needed to comment. Maxwags also contains a form to allow staff to upload posts - this page is not available to non-staff users and defaults back to the homepage.
There are 3 roles on the Maxwags web application - a login for each role is included with submission.
| Role | Description |
|---|---|
| User | Able to login and create, edit and delete comments on posts |
| Walker | Same access as user but also able to upload posts |
| Superuser | Complete access to everything within the admin panel - delete any comment, post, user etc. |
| Role | Image |
|---|---|
| User/Walker | ![]() |
| Superuser | ![]() |
| Status | Description | Evidence |
|---|---|---|
| Logged in | Navbar when logged in | ![]() |
| Logged in | Comment function when logged in | ![]() |
| Logged out | Navbar when logged out | ![]() |
| Logged out | Comment message when logged out | ![]() |
Manual testing was chosen as there were a limited about of models making automated testing seem like overkill.
| Page | User Action | Expected Result | Pass/Fail |
|---|---|---|---|
| Nav links | |||
| Click on Logo | Redirection to Home page | Pass | |
| Click on Posts link in navbar | Redirection to Posts page | Pass | |
| Click on Upload link in navbar | Redirection to Upload page (if user is Walker) | Pass | |
| Click on Logout link in navbar | Log out user | Pass | |
| Click on Login link in navbar | Redirection to Login page | Pass | |
| Click on Register link in navbar | Redirection to Register page | Pass | |
| Posts Page | |||
| Click on Post button on post | Adds Entered comment onto specific post | Pass | |
| Click on Edit button on comment | Navigates to edit comment page | Pass | |
| Click on Delete button on comment | Navigates to delete comment page | Pass | |
| Edit page | |||
| Click on Save Changes | Saves the comment and navigates to Posts page | Pass | |
| Click on Cancel | Does not save the comment and navigates to Posts page | Pass | |
| Delete page | |||
| Click on Yes, delete | Deletes the comment and navigates to Posts page | Pass | |
| Click on Cancel | Does not delete the comment and navigates to Posts page | Pass | |
| Register | |||
| Enter valid email address | Field will only accept email address format | Pass | |
| Enter valid password (twice) | Field will only accept password format | Pass | |
| Click on Create Account button | Creates the account, logs in navigates to Posts page | Pass | |
| Log In | |||
| Click Login button | Redirects user to Posts page and logs in (provded details are correct) | Pass | |
| Log Out | |||
| Click Logout button | Logs out user, Redirects user to Home page | Pass | |
| Footer | |||
| Click on the Facebook icon | Opens new broswer tab to Facebook homepage | Pass | |
| Click on the Instagram icon | Opens new broswer tab to Instagram homepage | Pass | |
| Click on the Twitter (X) icon | Opens new broswer tab to Twitter homepage | Pass | |
| Upload | |||
| Click Choose file button | Allows user to select an image from their PC | Pass | |
| Click Upload button | Uploads posts and redirects to Posts page | Pass | |
| Click Back to posts text button | Redirects to Posts page | Pass |
| Broswer | Image | Pass/Fail |
|---|---|---|
| Chrome | ![]() |
Pass |
| Brave | ![]() |
Pass |
| Safari | ![]() |
Pass |
Not applicable - no custom JavaScript added for project.
| User Story | Acceptance Criteria | Pass/Fail |
|---|---|---|
| As a Guest, I want to view a welcoming home page so that I can learn what MaxWags is about and navigate the site easily. | Visiting “/” shows the home page with information about MaxWags’ dog walking services. | Pass |
| The home page contains links to register, log in, and view posts. | Pass | |
| Navbar and footer appear consistently across all pages. | Pass | |
| As a Guest, I want to browse dog posts so that I can see photos of dogs and their captions without signing up. | Visiting “/posts” shows a feed of all dog posts ordered by date. | Pass |
| Each post shows the username, image, caption, and date posted. | Pass | |
| Guests can view but not comment or upload posts. | Pass | |
| As a Guest, I want to create an account so that I can log in and interact with the community. | Registration form collects username, email, and password. | Pass |
| Form validates input and shows error messages for invalid or duplicate entries. | Pass | |
| On successful registration, the user is automatically logged in and redirected to the posts page. | Pass | |
| As a Registered User, I want to securely log in and log out so that I can access member features. | Users can log in using username and password. | Pass |
| Failed logins show a clear error message. | Pass | |
| Logging out redirects the user to the home page with a confirmation message. | Pass | |
| As a Walker, I want to upload a photo and caption of dogs I’ve walked so that owners can see their pets having fun. | Only users with is_walker=True can access the upload page. | Pass |
| Upload form includes an image and optional caption. | Pass | |
| Images are uploaded to Cloudinary, and posts appear instantly on the posts page. | Pass | |
| Invalid files are rejected with a clear message. | Pass | |
| As a Registered User, I want to comment on posts so that I can engage with other users. | Authenticated users can add comments below posts. | Pass |
| Comments show username, text, and timestamp. | Pass | |
| Empty comments are rejected with an error message. | Pass | |
| Successful comments trigger a success message. | Pass | |
| As a Registered User, I want to edit or delete my own comments so that I can correct mistakes or remove unwanted messages. | Edit and delete options are visible only on comments by the logged-in user. | Pass |
| Edited comments update immediately. | Pass | |
| Deleted comments disappear from the post feed with a confirmation message. | Pass | |
| As an Admin, I want to assign or remove walker status so that I can control which users can upload dog posts. | Admin can toggle is_walker via the Django Admin panel. | Pass |
| Only walkers see the “Upload” link in the navbar. | Pass | |
| Walker uploads appear the same as regular posts, linked to their account. | Pass | |
| As a User, I want the website to be easy to navigate and readable on all devices so that I can use it anywhere. | Layout uses Bootstrap 5 responsive grid system. | Pass |
| Navbar collapses into a burger menu on mobile. | Pass | |
| Text maintains sufficient contrast and uses accessible font sizes. | Pass | |
| As a User, I want clear visual feedback when I take an action so that I know if it succeeded or failed. | Flash messages appear after registration, login, logout, post, and comment actions. | Pass |
| Alerts are styled with Bootstrap and dismissible. | Pass | |
| Success, warning, and error messages use distinct colors. | Pass | |
| As a Developer, I want the MaxWags app deployed on Heroku with Cloudinary storage so that it’s accessible online. | The app runs successfully on Heroku using a managed Postgres database. | Pass |
| Cloudinary handles all media uploads. | Pass | |
| Static files are served via WhiteNoise. | Pass | |
| Environment variables are used for secret keys and API credentials. | Pass |
As a note on linting, only files edited personally were linted, standard files created by Django were ignored.
| File name | Pass/Fail | Notes | Image |
|---|---|---|---|
| admin.py | Pass | ![]() |
|
| forms.py | Pass | ![]() |
|
| models.py | Pass | ![]() |
|
| urls.py | Pass | ![]() |
|
| views.py | Pass | ![]() |
|
| settings.py | Pass | Four errors on code generated by Django | ![]() |
| File name | Pass/Fail | Notes | Image |
|---|---|---|---|
| style.css | Pass | ![]() |
Satisfied witin GitHub repository: https://github.com/FollowRob/MaxWags_Capstone_Project
Satisfied within GitHub repository and Heroku deployment: https://github.com/FollowRob/MaxWags_Capstone_Project https://capstone-maxwags-e4277e29559a.herokuapp.com/
Satisfied with the deployment via Heroku: https://capstone-maxwags-e4277e29559a.herokuapp.com/
The site was deployed on Heroku and connected to GitHub for version control. This was done by following the below steps:
- Log in to GitHub and create a new repository
- Sign up for Heroku and create a new account.
- Create a new app and choose a suitable region for deployment.
- In the app settings, go to config vars and click "reveal config vars".
- The app requires configuration for the following variables: SECRET_KEY, DATABASE_URL & CLOUDINARY_URL. Assign the corresponding values from the project's env.py to these variables.
- Integrate Heroku with GitHub by choosing the GitHub integration option in Heroku.
- Locate and select the GitHub repository.
- Choose manual deployment from the selected branch of the GitHub repository.
- Deploy by clicking the manual deploy button.
- Once deployed, the site is accessible through the live link provided at the top of the document.
Satisfied by in settings.py & .gitignore - if you're reading this, let's hope I remembered to set Debug to False.
The custom model was utilised to allow certain users (staff/walkers) to upload posts of their customers pets for the users to interact with.
Code snippet:
class User(AbstractUser):
is_walker = models.BooleanField(default=False)
class DogPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
image = models.ImageField(upload_to='dog_images/')
caption = models.CharField(max_length=120)
date_posted = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
post = models.ForeignKey(DogPost, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
date_posted = models.DateTimeField(auto_now_add=True)AI tools such as ChatGPT and Co-pilot were utilised to assist with the repetitive coding tasks and minutiae of the project such as producing boilerplate code for templates, generating relevant code comments and basic html components such as links for the base footer. Each code snippet produced by AI was manually reviewed and edited to ensure it aligned with the project achitecture.
AI was certainly used for debugging and problem solving, for instance it helped confirm that it would be quicker and easier to create a new database after the original user model was edited rather than spend time fixing a database with only one entry. In this instance AI was used in a conversational manner, emmulating a senior colleague to bounce ideas off and get to the bottom of why errors were occuring rather than just providing code snippets to fix issues.
As for user experince the colour pallete was chosen with the assistance of ChatGPT, I'd described the look and feel I wanted the web application to have also stipulating that it must adhere to WCAG accessibility standards and use a dark green colou. The feedback provided by ChatGPT contributed to a cleaner and improved colour scheme evolving the "Facebook blue" to a more inclusive green-ish palette.
This section was not applicable for this project, as manual testing was deemed satisfactory for demonstrating CRUD functionality and general site performance.
OVerall the AI assistance utilised helped bridge the gaps in Python best practices like giving advice on PEP8 adhereance, it helped by clarifying unfamiliar or unecessarily verbose error messages and provided some design inspiration. Overall AI's role was similar to that of a senior colleague and/or mentor; someone to bounce ideas off and validate my reasoning rather than a shortcut for answers. While this project most certainly could have been completed without the use of AI, it would have taken far longer, I feel the utilisation of AI enhanced both my productivity and my understanding significantly.
-
Post images:
- Walkies - source of images for the posts
-
Django guidance:
- freecodecamp.org - used to better understand Django setup best practices
-
Google Fonts:
-
Favicon:
- Dog icon used for the favicon sourced from Font Awesome.
-
Icons:
- Social media icons (Facebook, Instagram, X/Twitter) provided by Bootstrap Icons.
-
Frameworks & Libraries:
- Bootstrap 5.3 - for responsive layout and UI components.
- Font Awesome - for additional iconography.
-
Hosting & Storage:
- Heroku - used for application hosting and deployment.
- Cloudinary - used for cloud-based image storage and media delivery.
-
Database:
- PostgreSQL - relational database used in production via Heroku.
-
AI Assistance:
- ChatGPT - used for debugging, improving accessibility and UX decisions.
- GitHub Copilot - assisted with repetitive coding and Django boilerplate generation.























































