A Django eCommerce application with three types of users - buyers, vendors, and admins. Buyers browse and order products, vendors run stores and fulfil orders, and admins moderate products and view vendor revenue. The page-by-page behaviour of the app is described in the accompanying UI Plan document.
- Python 3 + Django 6.0
- SQLite (
db.sqlite3, included for development) - Server-rendered Django templates (no separate front end)
This project uses SQLite, Django's built-in relational database engine. SQLite is a
fully relational SQL database: it stores data in tables with columns, rows, primary keys,
and foreign-key relationships, and is queried with standard SQL via Django's ORM. It was
chosen over a client-server engine such as MariaDB/MySQL because it is file-based and
requires no separate database server or credentials, which keeps development and grading
setup to a single python manage.py migrate step. The schema is created and version-managed
through Django's migration system (the shop/migrations/ folder), so switching to MariaDB
or PostgreSQL later only requires changing the DATABASES setting and re-running migrate.
From the project root (the folder containing manage.py):
# 1. Create and activate a virtual environment
python -m venv venv
venv\Scripts\activate # Windows (PowerShell / cmd)
# source venv/bin/activate # macOS / Linux
# 2. Install dependencies
pip install Django Pillow
# 3. Apply database migrations
python manage.py migrate
# 4. Start the development server
python manage.py runserverThe app is then available at http://127.0.0.1:8000/.
Pillowis required because products support image uploads. Django itself is the only other dependency.
There is a single login page for everyone (/login/). After you submit your username and
password, the app decides where to send you based on your account type:
| User type | How the account is created | Where login takes you |
|---|---|---|
| Buyer | "Sign up as new buyer" on the login page (or /register/buyer/) |
Buying home page |
| Vendor | "Sign up as new vendor" on the login page (or /register/vendor/) |
Vendor home page |
| Admin | Created from the command line (see below) | Admin dashboard |
- On the login page, click the new-buyer sign-up button (or go to
/register/buyer/). - Fill in your details and submit - you are logged in straight away and taken to the buyer home page.
- On later visits, log in with that username and password; you will land on the buyer home page.
- On the login page, click the new-vendor sign-up button (or go to
/register/vendor/). - Fill in your details, including banking information, and submit - you are taken to the vendor home page.
- On later visits, log in with that username and password; you will land on the vendor home page.
Admins are Django staff/superusers. They are not created through the website - create one from the command line:
python manage.py createsuperuserEnter a username, email, and password when prompted. Then log in at the normal /login/ page with
those credentials. Because the account is a staff user, you will be routed to the admin dashboard.
Tip: the same superuser can also use Django's built-in admin site at
/admin/.
Categories are not created through the website - they must be added manually
from the command line. Open the Django shell from the project root (the folder
containing manage.py, with the virtual environment activated):
python manage.py shellThen create the categories you need:
from shop.models import Category
Category.objects.create(name="Books")
Category.objects.create(name="Electronics")
Category.objects.create(name="Clothing")
exit()Once added, the categories become available for vendors to assign to their products and for buyers to search/filter by.
This project sends email to the console, not to a real inbox. The relevant setting is:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"This means password-reset emails (and order-confirmation emails) are printed in the terminal
window where python manage.py runserver is running instead of being delivered by email. No
mail server or credentials are needed for development.
To reset a password:
-
Make sure the server is running (
python manage.py runserver) and keep an eye on that terminal. -
On the login page, click the forgot-password button (or go to
/password-reset/). -
Enter the email address linked to the account and submit.
-
Switch to the terminal running the server. The reset email is printed there - find the message titled "Password Reset" and copy the reset link, which looks like:
http://127.0.0.1:8000/reset/<uid>/<token>/ -
Paste that link into your browser, enter a new password, and submit. You are then redirected back to the login page to sign in with the new password.
Reset links expire after 1 hour (
PASSWORD_RESET_TIMEOUT = 3600). If the link has expired, request a new one. Nothing is printed if the email address doesn't match an existing account.
The project ships with an automated test suite in shop/tests.py that exercises the
app end to end: the database models, buyer/vendor registration and login, the
session-based cart, checkout and stock handling, search, store/product management,
order fulfilment, the sales summary, the review system, the REST API endpoints and
the password-reset flow.
From the project root (the folder containing manage.py, with the virtual environment
activated):
# Run the whole suite
python manage.py test shop
Django builds a **temporary test database**, runs the tests against it, and destroys
it afterwards - your real `db.sqlite3` data is never touched. A passing run ends with
`OK`; any failures are listed individually with a traceback. No server needs to be
running, and the email backend is the console backend, so confirmation/reset emails
are captured in memory rather than sent.
### How the suite is organised
| Test class | What it covers |
|------------|----------------|
| `ModelTests` | Model fields, `__str__` values, order totals, review averages |
| `RegistrationTests` | Buyer/vendor sign-up, duplicate username and password-mismatch handling |
| `AuthenticationTests` | Login/logout and role-based redirects (buyer/vendor/admin) |
| `CartTests` | Adding to, viewing and removing items from the session cart |
| `CheckoutTests` | Turning a cart into an order, stock reduction and out-of-stock rejection |
| `SearchTests` | Product search by title and category |
| `StoreManagementTests` | Vendor store/product create, edit, delete and ownership checks |
| `OrderFulfilmentTests` | Listing orders to fulfil and updating order status |
| `SalesSummaryTests` | Revenue and 80% profit calculations on delivered orders |
| `ReviewTests` | Verified vs unverified reviews and purchase requirements |
| `ApiTests` | The five REST API endpoints and their auth/permission rules |
| `PasswordResetTests` | The forgot-password email flow |
> The `ApiTests` class needs Django REST Framework (`pip install djangorestframework`).
> It is already listed in `requirements.txt`.
The `ApiTests` class is the automated counterpart to the manual Postman walk-through
in the **API Testing Guide** below - the same endpoints are checked here without
needing Postman or a running server.
# API Testing Guide
This guide explains how to test the RESTful API built for the eCommerce
application. All testing is done with **Postman**.
The API provides five endpoints:
| # | Action | Method | URL | Auth required |
|---|--------|--------|-----|---------------|
| 1 | View all stores | GET | `/api/stores/` | No |
| 2 | Add a store | POST | `/api/stores/add/` | Yes (vendor) |
| 3 | View all products | GET | `/api/products/` | No |
| 4 | Add a product | POST | `/api/products/add/` | Yes (vendor) |
| 5 | View a product's reviews | GET | `/api/products/<product_id>/reviews/` | Yes (vendor, must own product) |
---
## Before you start
1. **Run the server.** From the project folder:
python manage.py runserver
The API is served from `http://127.0.0.1:8000/`. Leave the server running
while you test.
2. **Have a vendor login ready.** Three of the endpoints require a registered
**vendor** account, authenticated with **Basic Auth** (username + password).
- You can register a vendor through the site's normal registration page, or
- Use a test vendor account if one has been provided with the submission.
> **Important:** Basic Auth uses the account's **username**, *not* its email
> address. Use the username that was set when the vendor registered.
---
## Endpoint 1 - View all stores (GET, no auth)
1. Open a new request tab in Postman (the **+** button).
2. Set the method to **GET**.
3. Enter the URL:
http://127.0.0.1:8000/api/stores/
4. Click **Send**.
**Expected result:** Status **200 OK** and a JSON list of stores. If no stores
exist yet, an empty list `[]` is returned - this is normal, not an error.
---
## Endpoint 2 - Add a store (POST, vendor auth)
This endpoint creates a new store and links it to the authenticated vendor.
1. New request tab. Set the method to **POST**.
2. Enter the URL:
http://127.0.0.1:8000/api/stores/add/
3. Set the authentication:
- Click the **Authorization** tab.
- Set **Type** to **Basic Auth**.
- Enter the vendor's **username** and **password**.
4. Set the body:
- Click the **Body** tab.
- Select **raw**, then choose **JSON** from the dropdown on the right.
- Enter:
```json
{
"name": "BaconShop",
"description": "We sell Bacon!"
}
```
> Note: the vendor is **not** sent in the body. It is set automatically from
> the logged-in user, so the store is always attached to the correct vendor.
5. Click **Send**.
**Expected result:** Status **201 Created**, with the new store returned in the
response. The `vendor` value in the response is filled in automatically and
matches the vendor you authenticated as.
**Error cases worth checking:**
- Authenticating as a user who is **not** a vendor returns **403 Forbidden**
with a message that a registered vendor is required.
- Sending the request with **no authentication** returns **401 Unauthorized**.
---
## Endpoint 3 - View all products (GET, no auth)
1. New request tab. Set the method to **GET**.
2. Enter the URL:
http://127.0.0.1:8000/api/products/
3. Click **Send**.
**Expected result:** Status **200 OK** and a JSON list of products (or `[]` if
none exist).
---
## Endpoint 4 - Add a product (POST, vendor auth)
This endpoint creates a new product and links it to the authenticated vendor.
1. New request tab. Set the method to **POST**.
2. Enter the URL:
http://127.0.0.1:8000/api/products/add/
3. Set **Authorization** to **Basic Auth** with the vendor's username and
password (same as Endpoint 2).
4. Set the **Body** to **raw / JSON**:
```json
{
"title": "Bacon Strips",
"description": "Premium smoked bacon",
"price": "49.99",
"quantity": 10
}
title,descriptionandpriceare required.storeandcategoryare optional and can be supplied as their id numbers if desired. Theimagefield should be left out when testing through raw JSON, since file uploads are not sent this way.
- Click Send.
Expected result: Status 201 Created, with the new product returned and
the vendor filled in automatically.
Error cases: same as the store endpoint - a non-vendor user gets 403, and no authentication gives 401.
This endpoint returns the reviews for one product. The vendor can only view reviews for a product that they own.
-
New request tab. Set the method to GET.
-
Enter the URL, replacing
1with a real product id:http://127.0.0.1:8000/api/products/1/reviews/ -
Set Authorization to Basic Auth with the vendor's username and password.
-
Click Send.
Expected result: Status 200 OK and a JSON list of that product's
reviews (or [] if the product has none).
Ownership check: If the product id does not belong to the
authenticated vendor, the endpoint returns 404 Not Found by design. This
is the ownership rule working correctly, not a bug. To find a product id that
belongs to the vendor, use Endpoint 3 (/api/products/) and look at the
vendor value of each product.
| Endpoint | Setup | Expected status |
|---|---|---|
GET /api/stores/ |
No auth | 200 + list |
POST /api/stores/add/ |
Basic Auth (vendor) + JSON body | 201 + new store |
POST /api/stores/add/ |
Non-vendor user | 403 |
POST /api/stores/add/ |
No auth | 401 |
GET /api/products/ |
No auth | 200 + list |
POST /api/products/add/ |
Basic Auth (vendor) + JSON body | 201 + new product |
GET /api/products/<id>/reviews/ |
Basic Auth (vendor, owns product) | 200 + list |
GET /api/products/<id>/reviews/ |
Vendor does not own product | 404 |
Endpoints that use Django REST Framework's Response (the POST endpoints and
the reviews endpoint) return XML, because the project sets an XML renderer
as the global default in settings.py. The data is saved correctly either
way - this only affects how the response is displayed. The two product/store
list endpoints return JSON, as they use Django's JsonResponse directly.
This feature integrates a third-party API (Reddit) into the Django e-commerce
application. Visiting /reddit/ fetches posts from a chosen subreddit and
displays them on a page.
The feature is implemented across all required components:
| File | Responsibility |
|---|---|
functions/reddit.py |
Helper function get_reddit_posts() - builds the request URL, sends a GET request with a User-Agent header, and parses the JSON response into a list of posts (title, author, link). |
views.py |
reddit_feed view - calls the helper and passes the posts into the template. |
templates/shop/reddit_feed.html |
Loops through the posts and displays each title, author, and a clickable link to the original post. |
urls.py |
URL pattern mapping /reddit/ to the view. |
The Django side works correctly. Visiting /reddit/ returns an HTTP 200, and
the view, helper, and template all execute as intended (see terminal
screenshots in commerce_app\planning).
The live fetch from Reddit does not return posts because Reddit responds with
HTTP 403 (Forbidden) to unauthenticated requests to its .json endpoints
from this network. This was confirmed in the server logs.
The following standard fixes were tried, without success:
- Setting a descriptive User-Agent header, then a browser-style User-Agent.
- Switching the request host from
www.reddit.comtoold.reddit.com. - Beginning setup of Reddit's official OAuth API (registering a script app for a client ID and secret).
The JSON endpoint continued to return 403, and the OAuth app registration also failed to complete from this network. This is consistent with Reddit's recent tightening of access to its public JSON endpoints, which now pushes developers toward the authenticated API and appears to block requests from certain networks/IPs regardless of headers.
The third-party API integration is complete and correctly wired into the Django project. The remaining issue is Reddit's server-side access restriction, which is external to the application code.
commerce_app/
├── manage.py # Django command-line entry point
├── db.sqlite3 # SQLite database (development data)
├── requirements.txt # Python dependencies
├── README.md # this file
├── commerce_app/ # project configuration
│ ├── settings.py # settings (database, email backend, DRF, etc.)
│ ├── urls.py # root URL configuration
│ ├── wsgi.py / asgi.py # server entry points
│ └── __init__.py
├── planning/ # requirements, UI plan and sequence diagrams
└── shop/ # the main application
├── models.py # Vendor, Buyer, Store, Product, Order, OrderItem, Category, Review
├── views.py # all page logic and auth/password-reset handling
├── urls.py # URL routes (namespaced under "shop")
├── forms.py # form classes
├── serializers.py # Django REST Framework serializers for the API
├── admin.py # Django admin registrations
├── apps.py # app config
├── tests.py # automated test suite (see "Running the automated tests")
├── migrations/ # database schema migrations
├── functions/
│ └── reddit.py # third-party (Reddit) API helper function
├── templates/ # HTML templates (base.html + shop/ pages)
└── static/shop/ # CSS and static assets