Mentras Backend is a Django REST Framework API for a platform that combines community features with small-business operations. It supports user onboarding, forum-based interaction, inventory and menu workflows, and business-profile management for SME owners.
This repository is a strong portfolio project for recruiters because it goes beyond basic CRUD. It shows authentication, role-aware access, third-party integrations, transactional stock updates, content moderation, media uploads, and a modular backend structure that can grow into a larger product.
The backend is designed around a practical idea: one platform where users can participate in communities while business owners manage parts of their operation.
Current implemented domains:
Users and access: account creation, JWT login, email verification, Google login, profile updates, and role flags.Forums: public forum discovery, forum creation, image-backed forum profiles, posting, and moderation-friendly validation.Inventory and menus: item registration, menu creation, menu-item assignment, stock deduction, and movement history.PyME management: creation and maintenance of SME profiles linked to verified owner accounts.
There is also data-model groundwork for a future commerce layer through Product, Order, and ProductOrder models in the pyme app.
From an engineering and hiring perspective, this codebase demonstrates:
- building a multi-app backend with clear domain boundaries,
- using a custom
Usermodel with UUID primary keys, - implementing
JWT-based authentication and token refresh, - integrating external services like
Cloudinary,Google OAuth, and email delivery, - enforcing business rules with serializer validation and database transactions,
- handling media uploads in API workflows,
- and protecting operational endpoints with custom permissions.
- Custom
Usermodel extendingAbstractUser. - UUID-based user identifiers.
- Registration endpoint that sends a 6-digit verification code by email.
- Email activation flow with expiration handling.
- JWT login and refresh using
SimpleJWT. - Google sign-in flow that verifies the Google ID token server-side.
- Profile editing and self-service account deletion.
- Role flags such as
is_admin,is_mod,is_mentor, andis_pyme_owner.
- Forum listing and creation.
- Forum detail retrieval for authenticated users.
- Optional forum profile image upload through
Cloudinary. - Automatic creation of a
ForumUseradmin relationship when an authenticated user creates a forum. - Post creation tied to the authenticated author.
- Optional post image lists stored as validated JSON.
- Profanity filtering on forum names, descriptions, post titles, and post text.
- Author-only deletion for posts.
- Item creation with image upload.
- Menu creation and retrieval.
- Menu composition through
MenuItem. - Stock deduction when an item is attached to a menu.
- Inventory protection against insufficient stock.
- Atomic write flow so stock and menu assignment remain consistent.
MenuMovementaudit trail capturing who performed an action, what changed, and when.
- Verified business owners can create and manage their own
Pymerecords. - Each
Pymesupports category linkage, profile image, description, and foundation date. - Owners can list only their own businesses and update or delete them.
- Access is restricted so one owner cannot read or mutate another owner’s
Pyme.
The repository is organized as separate Django apps, which keeps the product domains easy to understand and extend:
mentrasBackend/
├── apps/
│ ├── user/ # auth, profiles, email verification, Google login
│ ├── forum/ # forums, posts, moderation, forum membership/admin links
│ ├── stock/ # items, menus, stock deduction, movement logs
│ └── pyme/ # SME profiles and future commerce groundwork
├── globals/ # shared helpers for permissions, tokens, Cloudinary
├── mentrasBackend/
│ ├── settings.py
│ └── urls.py
└── manage.py
This structure is one of the strongest signals in the project: the backend is not written as a single monolith file set, but as separate functional modules with focused responsibilities.
Base route groups:
/api/user//api/forum//api/stock//api/pyme//api/accounts/
The examples below use realistic payloads so the API is easier to read at a glance. UUID values are sample values.
Authentication notes:
- Endpoints under
stockandpymerequire an authenticated user withis_email_verified=true. GET /api/forum/andGET /api/forum/post/are public.- Forum and post creation only make sense when the request is authenticated.
- Any endpoint with
profile_picis typically sent asmultipart/form-data. A JSON example is still included here to show the field shape.
Returns all users.
[
{
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88887777,
"profile_pic": "https://res.cloudinary.com/demo/image/upload/profile_pics/maria.jpg",
"is_mod": false,
"is_admin": false,
"is_mentor": true,
"is_pyme_owner": true
}
]Creates a user and sends a verification code by email.
Request:
{
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88887777,
"password": "StrongPass123!",
"is_mod": false,
"is_admin": false,
"is_mentor": true,
"is_pyme_owner": true
}Response:
{
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88887777,
"profile_pic": "",
"is_mod": false,
"is_admin": false,
"is_mentor": true,
"is_pyme_owner": true
}Returns one user profile.
{
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88887777,
"profile_pic": "https://res.cloudinary.com/demo/image/upload/profile_pics/maria.jpg",
"is_mod": false,
"is_admin": false,
"is_mentor": true,
"is_pyme_owner": true
}Updates the authenticated user.
Request:
{
"phone_number": 88880000,
"is_mentor": false
}Response:
{
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88880000,
"profile_pic": "https://res.cloudinary.com/demo/image/upload/profile_pics/maria.jpg",
"is_mod": false,
"is_admin": false,
"is_mentor": false,
"is_pyme_owner": true
}Deletes the authenticated user if the UUID matches the logged-in account.
{
"status": "User deleted successfully"
}JWT login.
Request:
{
"username": "maria_dev",
"password": "StrongPass123!"
}Response:
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.refresh-token",
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.access-token"
}Refreshes the access token.
Request:
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.refresh-token"
}Response:
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.new-access-token"
}Marks the user as verified if the code is correct and not expired.
Request:
{
"code": "483921"
}Response:
{
"status": "Email verified successfully"
}Sends a new verification code.
{
"status": "Verification code resent successfully"
}Accepts either id_token or credential.
Request:
{
"credential": "google-id-token-from-frontend"
}Response:
{
"user": {
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria-dev",
"email": "maria@example.com",
"first_name": "Maria Dev"
},
"tokens": {
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.refresh-token",
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.access-token"
},
"created": true
}Lists forums ordered by newest first.
[
{
"id": "a6cb5f32-e8e8-4315-ae75-0e0fe6c23f2c",
"name": "Backend Costa Rica",
"description": "A place to discuss Django, APIs, and deployment.",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/forum_pics/backend-cr.jpg",
"is_private": false,
"created_at": "2026-04-28T10:30:00Z"
}
]Creates a forum. If the request is authenticated, the creator is also linked as forum admin.
Request:
{
"name": "Backend Costa Rica",
"description": "A place to discuss Django, APIs, and deployment.",
"is_private": false
}Response:
{
"id": "a6cb5f32-e8e8-4315-ae75-0e0fe6c23f2c",
"name": "Backend Costa Rica",
"description": "A place to discuss Django, APIs, and deployment.",
"profile_pic": "",
"is_private": false,
"created_at": "2026-04-28T10:30:00Z"
}Returns one forum.
{
"id": "a6cb5f32-e8e8-4315-ae75-0e0fe6c23f2c",
"name": "Backend Costa Rica",
"description": "A place to discuss Django, APIs, and deployment.",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/forum_pics/backend-cr.jpg",
"is_private": false,
"created_at": "2026-04-28T10:30:00Z"
}Updates a forum partially. Only a forum administrator can edit it.
To update the forum profile photo, send the request as multipart/form-data
with the field profile_pic containing the image file:
profile_pic: forum-cover.jpg
Request:
{
"description": "A place to discuss Django, DRF, APIs, testing, and deployment.",
"is_private": true
}Deletes a forum. Only a forum administrator can delete it.
Lists posts ordered by newest first.
[
{
"id": "72bb4ecb-95b2-4a97-9c5f-6efff5d8d952",
"title": "How are you handling serializer validation?",
"text": "I moved profanity and image checks into the serializer layer.",
"user": {
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88887777,
"profile_pic": "https://res.cloudinary.com/demo/image/upload/profile_pics/maria.jpg",
"is_mod": false,
"is_admin": false,
"is_mentor": true,
"is_pyme_owner": true
},
"images": "[\"https://cdn.example.com/posts/validation-board.png\"]",
"created_at": "2026-04-28T11:00:00Z",
"forum_id": 0
}
]Creates a post inside a forum.
Request:
{
"forum_id": "a6cb5f32-e8e8-4315-ae75-0e0fe6c23f2c",
"title": "How are you handling serializer validation?",
"text": "I moved profanity and image checks into the serializer layer.",
"images": "[\"https://cdn.example.com/posts/validation-board.png\"]"
}Response:
{
"id": "72bb4ecb-95b2-4a97-9c5f-6efff5d8d952",
"title": "How are you handling serializer validation?",
"text": "I moved profanity and image checks into the serializer layer.",
"user": {
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88887777,
"profile_pic": "https://res.cloudinary.com/demo/image/upload/profile_pics/maria.jpg",
"is_mod": false,
"is_admin": false,
"is_mentor": true,
"is_pyme_owner": true
},
"images": "[\"https://cdn.example.com/posts/validation-board.png\"]",
"created_at": "2026-04-28T11:00:00Z",
"forum_id": 0
}Returns one post.
{
"id": "72bb4ecb-95b2-4a97-9c5f-6efff5d8d952",
"title": "How are you handling serializer validation?",
"text": "I moved profanity and image checks into the serializer layer.",
"user": {
"id": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"username": "maria_dev",
"email": "maria@example.com",
"phone_number": 88887777,
"profile_pic": "https://res.cloudinary.com/demo/image/upload/profile_pics/maria.jpg",
"is_mod": false,
"is_admin": false,
"is_mentor": true,
"is_pyme_owner": true
},
"images": "[\"https://cdn.example.com/posts/validation-board.png\"]",
"created_at": "2026-04-28T11:00:00Z",
"forum_id": 0
}Deletes the post if the requester is the author.
Successful response:
{}Lists available inventory items.
[
{
"id": "fe2d0b9a-59ca-4dc6-b6f6-7c5e1772f2df",
"name": "Cold Brew Bottle",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/item_pics/cold-brew.jpg",
"price": "4.50",
"stock": 120
}
]Creates an inventory item.
Request:
{
"name": "Cold Brew Bottle",
"profile_pic": "<multipart file>",
"price": "4.50",
"stock": 120
}Updates an item owned by the authenticated user. The fields name, price, stock, and profile_pic can be updated partially. Send profile_pic as multipart/form-data when changing the image.
Response:
{
"id": "fe2d0b9a-59ca-4dc6-b6f6-7c5e1772f2df",
"name": "Cold Brew Bottle",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/item_pics/cold-brew.jpg",
"price": "4.50",
"stock": 120
}Lists menus with their attached items and movement history.
[
{
"id": "bc7797e9-a4b7-4cae-99d8-a0f5b95b2d22",
"name": "Breakfast Menu",
"description": "Morning drinks and sandwiches.",
"menu_items": [
{
"id": "f3d504ca-177e-4b97-b4fc-7741a0af7ed8",
"menu": "bc7797e9-a4b7-4cae-99d8-a0f5b95b2d22",
"item": {
"id": "fe2d0b9a-59ca-4dc6-b6f6-7c5e1772f2df",
"name": "Cold Brew Bottle",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/item_pics/cold-brew.jpg",
"price": "4.50",
"stock": 118
},
"quantity": 2
}
],
"movements": []
}
]Creates a menu.
Request:
{
"name": "Breakfast Menu",
"description": "Morning drinks and sandwiches."
}Response:
{
"id": "bc7797e9-a4b7-4cae-99d8-a0f5b95b2d22",
"name": "Breakfast Menu",
"description": "Morning drinks and sandwiches.",
"menu_items": [],
"movements": []
}Attaches an item to a menu and deducts stock atomically.
Request:
{
"item_id": "fe2d0b9a-59ca-4dc6-b6f6-7c5e1772f2df",
"quantity": 2
}Response:
{
"id": "bc7797e9-a4b7-4cae-99d8-a0f5b95b2d22",
"name": "Breakfast Menu",
"description": "Morning drinks and sandwiches.",
"menu_items": [
{
"id": "f3d504ca-177e-4b97-b4fc-7741a0af7ed8",
"menu": "bc7797e9-a4b7-4cae-99d8-a0f5b95b2d22",
"item": {
"id": "fe2d0b9a-59ca-4dc6-b6f6-7c5e1772f2df",
"name": "Cold Brew Bottle",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/item_pics/cold-brew.jpg",
"price": "4.50",
"stock": 118
},
"quantity": 2
}
],
"movements": [
{
"id": "91ed74f2-150d-49f5-ae30-249f67d8bc5f",
"menu": "bc7797e9-a4b7-4cae-99d8-a0f5b95b2d22",
"menu_name": "Breakfast Menu",
"item": {
"id": "fe2d0b9a-59ca-4dc6-b6f6-7c5e1772f2df",
"name": "Cold Brew Bottle",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/item_pics/cold-brew.jpg",
"price": "4.50",
"stock": 118
},
"item_name": "Cold Brew Bottle",
"menu_item": "f3d504ca-177e-4b97-b4fc-7741a0af7ed8",
"performed_by": "maria_dev",
"action": "item_added",
"action_display": "Item added",
"quantity": 2,
"previous_quantity": null,
"details": "Added Cold Brew Bottle to Breakfast Menu",
"created_at": "2026-04-28T11:20:00Z"
}
]
}Returns the movement log for a menu.
[
{
"id": "91ed74f2-150d-49f5-ae30-249f67d8bc5f",
"menu": "bc7797e9-a4b7-4cae-99d8-a0f5b95b2d22",
"menu_name": "Breakfast Menu",
"item": {
"id": "fe2d0b9a-59ca-4dc6-b6f6-7c5e1772f2df",
"name": "Cold Brew Bottle",
"profile_pic": "https://res.cloudinary.com/demo/image/upload/item_pics/cold-brew.jpg",
"price": "4.50",
"stock": 118
},
"item_name": "Cold Brew Bottle",
"menu_item": "f3d504ca-177e-4b97-b4fc-7741a0af7ed8",
"performed_by": "maria_dev",
"action": "item_added",
"action_display": "Item added",
"quantity": 2,
"previous_quantity": null,
"details": "Added Cold Brew Bottle to Breakfast Menu",
"created_at": "2026-04-28T11:20:00Z"
}
]Lists the authenticated owner's businesses.
[
{
"id": "daf8a111-8c82-49bf-8462-e6fc3757d4cb",
"name": "Cafe Aurora",
"description": "Specialty coffee, bakery, and brunch.",
"owner": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"category": {
"id": "d51f8b87-0ec7-4e95-b0de-f4d0d0ce649d",
"name": "Food & Beverage"
},
"profile_pic": "https://res.cloudinary.com/<cloud-name>/image/upload/pyme_pics/cafe-aurora.jpg",
"access_date": "2026-04-28T09:00:00Z",
"foundation_date": "2020-05-14"
}
]Returns the same owner-scoped list in a dedicated route.
[
{
"id": "daf8a111-8c82-49bf-8462-e6fc3757d4cb",
"name": "Cafe Aurora",
"description": "Specialty coffee, bakery, and brunch.",
"owner": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"category": {
"id": "d51f8b87-0ec7-4e95-b0de-f4d0d0ce649d",
"name": "Food & Beverage"
},
"profile_pic": "https://res.cloudinary.com/<cloud-name>/image/upload/pyme_pics/cafe-aurora.jpg",
"access_date": "2026-04-28T09:00:00Z",
"foundation_date": "2020-05-14"
}
]Creates a business profile. The account must have is_pyme_owner=true.
Request:
{
"name": "Cafe Aurora",
"description": "Specialty coffee, bakery, and brunch.",
"category_id": "d51f8b87-0ec7-4e95-b0de-f4d0d0ce649d",
"profile_pic": "<multipart file>",
"foundation_date": "2020-05-14"
}Response:
{
"id": "daf8a111-8c82-49bf-8462-e6fc3757d4cb",
"name": "Cafe Aurora",
"description": "Specialty coffee, bakery, and brunch.",
"owner": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"category": {
"id": "d51f8b87-0ec7-4e95-b0de-f4d0d0ce649d",
"name": "Food & Beverage"
},
"profile_pic": "https://res.cloudinary.com/<cloud-name>/image/upload/pyme_pics/cafe-aurora.jpg",
"access_date": "2026-04-28T09:00:00Z",
"foundation_date": "2020-05-14"
}Returns one owner-controlled business.
{
"id": "daf8a111-8c82-49bf-8462-e6fc3757d4cb",
"name": "Cafe Aurora",
"description": "Specialty coffee, bakery, and brunch.",
"owner": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"category": {
"id": "d51f8b87-0ec7-4e95-b0de-f4d0d0ce649d",
"name": "Food & Beverage"
},
"profile_pic": "https://res.cloudinary.com/<cloud-name>/image/upload/pyme_pics/cafe-aurora.jpg",
"access_date": "2026-04-28T09:00:00Z",
"foundation_date": "2020-05-14"
}Updates a business profile.
Request:
{
"description": "Specialty coffee, bakery, brunch, and seasonal desserts.",
"foundation_date": "2020-05-20"
}Response:
{
"id": "daf8a111-8c82-49bf-8462-e6fc3757d4cb",
"name": "Cafe Aurora",
"description": "Specialty coffee, bakery, brunch, and seasonal desserts.",
"owner": "8d54c72f-cd39-4d42-8c15-9a865f1f5d1d",
"category": {
"id": "d51f8b87-0ec7-4e95-b0de-f4d0d0ce649d",
"name": "Food & Beverage"
},
"profile_pic": "https://res.cloudinary.com/<cloud-name>/image/upload/pyme_pics/cafe-aurora.jpg",
"access_date": "2026-04-28T09:00:00Z",
"foundation_date": "2020-05-20"
}Deletes the business profile.
Successful response:
{}These are the kinds of implementation details that matter in a technical review:
UUIDsare used across the main entities instead of predictable incremental IDs.- Email verification is part of the account lifecycle, not an afterthought.
- Google authentication is validated on the backend rather than blindly trusted from the client.
- Media uploads are abstracted through shared Cloudinary helpers.
- Forum and post moderation rules live in serializers, which keeps the API defensive.
- Inventory changes use
transaction.atomic()and stock-level checks to avoid inconsistent writes. - Operational activity is logged through
MenuMovement, which adds auditability to menu changes. - The
IsEmailVerifiedpermission guards business-sensitive endpoints.
PythonDjangoDjango REST FrameworkPostgreSQLdjangorestframework-simplejwtdj-rest-authdjango-allauthCloudinaryGoogle OAuthbetter-profanity
Python 3.12+PostgreSQL- a
Cloudinaryaccount - SMTP credentials for sending verification emails
python -m venv venv
source venv/bin/activate
pip install -r requirements.txtCreate a .env file in the project root:
DB_NAME=
DB_USER=
DB_PASSWORD=
DB_PORT=
SECRET_JWT_KEY=
GOOGLE_APP_PASSWORD=
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_SECRET=
FACEBOOK_CLIENT_ID=
FACEBOOK_SECRET=
FACEBOOK_KEY=
MICROSOFT_CLIENT_ID=
MICROSOFT_SECRET=
MICROSOFT_KEY=python manage.py migrate
python manage.py runserverThe API will be available at http://127.0.0.1:8000/.