A professional logistics and product sales platform that enables Suppliers, Sellers, and Drivers to discover and work with each other.
- Supplier: Adds products to the system and offers them for sale
- Seller: Places orders for products from suppliers
- Driver: Delivers orders
LogiFood/
βββ apps/ # Core utilities and base classes
β βββ core/ # Core app with base classes only
β βββ models.py # Base models (TimeStampedModel)
β βββ serializers.py # Base serializers
β βββ views.py # Base viewsets
β βββ services.py # Base service layer
β βββ utils.py # Utility functions
β βββ exceptions.py # Custom exceptions
β βββ permissions.py # Custom permissions
β βββ pagination.py # Custom pagination
β βββ filters.py # Custom filters
β βββ urls.py # Core URLs (health check)
βββ apps/ # Application modules (single root)
β βββ core/ # Shared base (models, utils, permissions, health)
β βββ users/ # User management module
β βββ products/ # Category, Product
β βββ orders/ # Deal, Delivery, RequestToDriver, etc.
βββ tests/ # Test suite (layered architecture)
β βββ conftest.py # Shared pytest fixtures
β βββ test_users/ # User module tests
β β βββ test_models.py
β β βββ test_serializers.py
β β βββ test_services.py
β β βββ test_views.py
β βββ test_products/ # Product module tests
β β βββ test_models.py
β β βββ test_serializers.py
β β βββ test_services.py
β β βββ test_views.py
β βββ test_orders/ # Order module tests
β β βββ test_models.py
β β βββ test_serializers.py
β β βββ test_services.py
β β βββ test_views.py
β βββ test_core/ # Core utility tests
β βββ test_utils.py
β βββ test_health_check.py
βββ config/ # Project configuration
β βββ settings/ # Settings modules
β β βββ base.py # Base settings
β β βββ development.py # Development settings
β β βββ production.py # Production settings
β βββ urls.py # Main URL configuration
β βββ wsgi.py # WSGI configuration
β βββ asgi.py # ASGI configuration
βββ static/ # Static files
βββ media/ # Media files
βββ templates/ # Template files
βββ logs/ # Log files
βββ requirements.txt # Python dependencies
βββ .env.example # Environment variables template
βββ .gitignore # Git ignore rules
βββ pytest.ini # Pytest configuration
βββ manage.py # Django management script
βββ setup_env.bat # Windows setup script
βββ setup_env.sh # Linux/Mac setup script
- Python 3.10 or higher
- PostgreSQL (recommended) or SQLite
- pip
| Action | Windows (CMD) | Windows (Git Bash) | Linux/Mac |
|---|---|---|---|
| Setup | setup_env.bat |
bash setup_env.sh |
./setup_env.sh |
| Activate venv | venv\Scripts\activate.bat |
source venv/Scripts/activate |
source venv/bin/activate |
| Copy .env | copy .env.example .env |
cp .env.example .env |
cp .env.example .env |
| Run server | python manage.py runserver |
python manage.py runserver |
python manage.py runserver |
- Run the setup script:
setup_env.bat- Activate the virtual environment:
venv\Scripts\activate.bat- Make the setup script executable (Linux/Mac only):
chmod +x setup_env.sh- Run the setup script:
# Windows (Git Bash)
bash setup_env.sh
# Linux/Mac
./setup_env.sh- Activate the virtual environment:
# Windows (Git Bash)
source venv/Scripts/activate
# Linux/Mac
source venv/bin/activate- Copy the environment example file:
# Windows (Command Prompt)
copy .env.example .env
# Windows (Git Bash) / Linux / Mac
cp .env.example .env-
Edit
.envfile with your settings:- Set
SECRET_KEY(generate a new one for production) - Configure database credentials
- Set
DEBUG=Truefor development - Configure other settings as needed
- Set
-
Run migrations:
# Make sure virtual environment is activated first
python manage.py migrate- Load initial data (market food categories):
python manage.py load_categoriesThis command loads market food categories including:
- Main Categories: Citrus Fruits, Vegetables, Fruits, Legumes, Grains, Dairy Products, Meat and Meat Products, Fish and Seafood, Nuts and Dried Fruits, Spices and Herbs, Bakery Products, Beverages, Oils and Fats, Honey and Natural Products
- Sub-categories: Leafy Vegetables, Root Vegetables, Nightshade Vegetables, Cucurbitaceae (under Vegetables); Stone Fruits, Berries, Tropical Fruits (under Fruits); Red Meat, Poultry, Processed Meat (under Meat); Milk and Cream, Cheese, Yogurt and Fermented Products (under Dairy)
To reset and reload all categories:
python manage.py load_categories --reset- Create a superuser:
python manage.py createsuperuser- Run the development server:
python manage.py runserverNote: Always activate the virtual environment before running Django commands:
- Windows (Command Prompt):
venv\Scripts\activate.bat - Windows (Git Bash):
source venv/Scripts/activate - Linux/Mac:
source venv/bin/activate
The API will be available at http://localhost:8000/
The project runs with Docker Compose (PostgreSQL, Redis, Django):
-
Ensure Docker and Docker Compose are installed.
-
Copy environment and set credentials (optional; defaults work with
.env.example):cp .env.example .env # Edit .env: DB_PASSWORD, SECRET_KEY, etc. -
Start all services:
docker compose up --build
-
The API runs at
http://localhost:8000/. Migrations run on startup. -
Dev data (categories + sample users/products/deals): set
LOAD_DEV_DATA=1in.env(or run once):docker compose exec web python manage.py load_dev_dataWith
LOAD_DEV_DATA=1, the entrypoint runsload_dev_dataafter migrate so the stack starts with categories and sample data. Sample usersβ password:sample123. -
Optional commands:
docker compose up -d # Run in background docker compose exec web python manage.py load_dev_data --reset # Wipe and reload dev data docker compose exec web python manage.py createsuperuser docker compose exec web pytest -m unit docker compose down # Stop and remove containers docker compose down -v # Also remove volumes (DB data)
The web service uses DB_HOST=db and REDIS_URL=redis://redis:6379/1 via docker-compose.yml; your .env still provides DB_NAME, DB_USER, DB_PASSWORD, SECRET_KEY, etc.
Once the server is running, access the API documentation:
- Swagger UI: http://localhost:8000/api/docs/
- ReDoc: http://localhost:8000/api/redoc/
- Schema JSON: http://localhost:8000/api/schema/
This project follows a layered architecture pattern (also known as n-tier architecture), which separates concerns into distinct layers. This approach provides:
Responsibility: Data structure and database representation
- Django ORM: All database interactions use Django's Object-Relational Mapping
- Base Models:
TimeStampedModelprovides automaticcreated_at,updated_at, andcreated_byfields - Custom User Model: Extended Django's
AbstractUserwith role-based system - Relationships: Foreign keys, OneToOne, and reverse relationships managed by ORM
- Business Logic: Model methods for calculations and validations
Why Django ORM?
- Type-safe queries with IDE autocomplete
- Automatic SQL generation and optimization
- Built-in migration system
- Protection against SQL injection
- Database abstraction (works with PostgreSQL, MySQL, SQLite)
Responsibility: Data validation, transformation, and serialization
- Input Validation: Validates incoming request data
- Data Transformation: Converts between Python objects and JSON
- Nested Serialization: Handles complex object relationships
- Role-based Fields: Different fields based on user roles
Responsibility: Business logic and orchestration
- Business Rules: All business logic is centralized here
- Transaction Management: Ensures data consistency with
@transaction.atomic - Reusability: Service methods can be used by views, management commands, or other services
- Testability: Business logic can be tested without HTTP layer
- Error Handling: Custom
BusinessLogicErrorfor domain-specific errors
Why Service Layer?
- Separation of Concerns: Views handle HTTP, services handle business logic
- SOLID Principles: Single Responsibility (views = HTTP, services = business logic)
- Reusability: Same business logic can be used in API, CLI, or background tasks
- Testability: Business logic can be unit tested without HTTP overhead
- Maintainability: Changes to business rules are centralized
Responsibility: HTTP request/response handling
- DRF Generic Views: Uses
ViewSetandGenericAPIViewfor consistency - Permission Control: Role-based access control via custom permissions
- Response Formatting: Standardized success/error responses
- Minimal Logic: Views delegate to services, keeping them thin
Why DRF Generic Views?
- Consistency: Standard patterns across all endpoints
- Less Boilerplate: Built-in CRUD operations
- Best Practices: Follows Django REST Framework conventions
- API Documentation: Automatic schema generation for Swagger/ReDoc
Responsibility: Helper functions and utilities
- Response Helpers: Standardized API response formatting
- Common Utilities: Reusable functions across the application
- Repository Pattern: Service layer acts as repository, abstracting database operations
- Factory Pattern: Fixtures in tests create objects with default values
- Strategy Pattern: Different serializers based on action (create vs. update)
- Observer Pattern: Django signals for automatic profile creation
-
Single Responsibility: Each class has one reason to change
- Models: Data structure
- Serializers: Data validation/transformation
- Services: Business logic
- Views: HTTP handling
-
Open/Closed: Base classes (
BaseService,TimeStampedModel) are open for extension, closed for modification -
Liskov Substitution: All service classes can be used interchangeably through
BaseService -
Interface Segregation: Small, focused interfaces (permissions, serializers)
-
Dependency Inversion: Views depend on service abstractions, not concrete implementations
See the Database Structure section below for detailed information about models and relationships.
This project uses Django ORM (Object-Relational Mapping) for all database operations. Django ORM provides:
- Type Safety: IDE autocomplete and type checking
- SQL Injection Protection: Parameterized queries by default
- Database Abstraction: Works with PostgreSQL, MySQL, SQLite without code changes
- Migration System: Version-controlled database schema changes
- Query Optimization:
select_related()andprefetch_related()for efficient queries - Relationship Management: Automatic handling of foreign keys, reverse relationships
TimeStampedModel (Abstract Base Model)
created_at: Auto-populated timestampupdated_at: Auto-updated timestampcreated_by: Foreign key to User (tracks who created the record)- All models inherit from this for consistent timestamp tracking
User (Custom User Model)
- Extends Django's
AbstractUser - Role System: SUPPLIER, SELLER, DRIVER
- Relationships:
- OneToOne β
SupplierProfile,SellerProfile,DriverProfile(via reverse relationships)
- OneToOne β
SupplierProfile
- OneToOne with User
- Relationships:
- OneToMany β
Product(supplier.products) - OneToMany β
Deal(supplier.deals)
- OneToMany β
SellerProfile
- OneToOne with User
- Relationships:
- OneToMany β
Deal(seller.deals)
- OneToMany β
DriverProfile
- OneToOne with User
- Relationships:
- OneToMany β
Delivery(driver_profile.deliveries) - OneToMany β
RequestToDriver(driver.driver_requests) - Note: Driver information for deals is stored in
RequestToDriver, not directly onDeal
- OneToMany β
Category
- Self-referential (parent-child hierarchy)
- Relationships:
- OneToMany β
Product(category.products) - Self β
Category(parent.children)
- OneToMany β
Product
- Relationships:
- ManyToOne β
SupplierProfile(product.supplier) - ManyToOne β
Category(product.category) - OneToMany β
DealItem(product.deal_items) - OneToMany β
DeliveryItem(product.delivery_items)
- ManyToOne β
Deal
- Represents an agreement between Seller and Supplier
- Relationships:
- ManyToOne β
SellerProfile(deal.seller) - ManyToOne β
SupplierProfile(deal.supplier) - OneToMany β
DealItem(deal.items) - OneToMany β
Delivery(deal.deliveries) - OneToMany β
RequestToDriver(deal.driver_requests)
- ManyToOne β
DealItem
- Items within a Deal
- Relationships:
- ManyToOne β
Deal(item.deal) - ManyToOne β
Product(item.product)
- ManyToOne β
Delivery
- Actual delivery instance created from Deal
- Relationships:
- ManyToOne β
Deal(delivery.deal) - ManyToOne β
DriverProfile(delivery.driver_profile, nullable) - OneToMany β
DeliveryItem(delivery.items)
- ManyToOne β
DeliveryItem
- Items within a Delivery
- Relationships:
- ManyToOne β
Delivery(item.delivery) - ManyToOne β
Product(item.product)
- ManyToOne β
RequestToDriver
- Driver request/negotiation for a Deal
- Relationships:
- ManyToOne β
Deal(request.deal) - ManyToOne β
DriverProfile(request.driver) - Unique constraint: One request per driver per deal
- ManyToOne β
- Note: When a
RequestToDriveris accepted (status=ACCEPTED), the driver information is used for creating deliveries. Driver information is no longer stored directly onDealmodel.
User (1) ββ(1:1)ββ SupplierProfile (1) ββ(1:N)ββ Product
β
βββ(1:N)ββ Deal ββ(1:N)ββ Delivery
β
βββ(1:N)ββ RequestToDriver ββ(N:1)ββ DriverProfile
User (1) ββ(1:1)ββ SellerProfile (1) ββ(1:N)ββ Deal
User (1) ββ(1:1)ββ DriverProfile (1) ββ(1:N)ββ Delivery
β
βββ(1:N)ββ RequestToDriver ββ(N:1)ββ Deal
Category (self-referential)
β
βββ(1:N)ββ Product
Django ORM provides several optimization techniques used in this project:
-
select_related(): For ForeignKey and OneToOne relationships
Deal.objects.select_related('seller', 'supplier') # Driver information is accessed via RequestToDriver: deal.driver_requests.filter(status='ACCEPTED').first()
-
prefetch_related(): For ManyToMany and reverse ForeignKey relationships
Category.objects.prefetch_related('children')
-
only() / defer(): Load only needed fields
Product.objects.only('name', 'price')
- Version Control: All migrations are committed to git
- Backward Compatibility: Migrations are designed to be reversible
- Data Migrations: Custom migrations for data transformations (e.g.,
load_categories) - Zero Downtime: Migrations are designed to work with running applications
The project uses Django REST Framework Token Authentication. Email is optional; login is done with username.
POST /api/auth/register/
{
"username": "supplier1",
"password": "securepassword123",
"password2": "securepassword123",
"role": "SUPPLIER",
"company_name": "ABC Food Ltd.",
"phone_number": "+15551234567",
"city": "New York",
"address": "123 Main St, New York"
}POST /api/auth/register/
{
"username": "seller1",
"password": "securepassword123",
"password2": "securepassword123",
"role": "SELLER",
"business_name": "Central Market",
"business_type": "Market",
"phone_number": "+15559876543",
"city": "Boston",
"address": "456 Oak Ave, Boston"
}POST /api/auth/register/
{
"username": "driver1",
"password": "securepassword123",
"password2": "securepassword123",
"role": "DRIVER",
"license_number": "34ABC123",
"vehicle_type": "VAN",
"vehicle_plate": "34 ABC 123",
"phone_number": "+15557654321",
"city": "New York"
}POST /api/auth/login/
{
"username": "supplier1",
"password": "securepassword123"
}Include the JWT token in the Authorization header:
Authorization: Bearer <access_token>
| Method | URL | Description |
|---|---|---|
| POST | /api/auth/register/ |
User registration (role-based) |
| POST | /api/auth/login/ |
Login (with username) |
| POST | /api/auth/logout/ |
Logout |
| GET/PUT | /api/auth/profile/ |
View/update profile |
| GET/PUT | /api/auth/profile/role/ |
View/update role profile |
| POST | /api/auth/change-password/ |
Change password |
| PUT | /api/auth/toggle-availability/ |
Driver availability toggle |
| Method | URL | Description |
|---|---|---|
| GET | /api/products/categories/ |
List categories |
| GET | /api/products/items/ |
List all products |
| GET | /api/products/items/<id>/ |
Product detail |
| GET/POST | /api/products/my-products/ |
Supplier products (own) |
| GET/PUT/DELETE | /api/products/my-products/<id>/ |
Supplier product management |
| Method | URL | Description |
|---|---|---|
| GET/POST | /api/orders/deals/ |
Deal list/create |
| GET/PUT/DELETE | /api/orders/deals/<id>/ |
Deal detail/update/delete |
| POST | /api/orders/deals/<id>/approve/ |
Approve deal |
| PUT | /api/orders/deals/<id>/update_status/ |
Update deal status |
| PUT | /api/orders/deals/<id>/assign_driver/ |
Assign driver to deal (creates and auto-approves RequestToDriver) |
| PUT | /api/orders/deals/<id>/request_driver/ |
Request driver for deal (creates RequestToDriver) |
| POST | /api/orders/deals/<id>/complete/ |
Complete deal and create deliveries |
| GET/POST | /api/orders/driver-requests/ |
Driver request list/create |
| GET/PUT | /api/orders/driver-requests/<id>/ |
Driver request detail/update |
| POST | /api/orders/driver-requests/<id>/approve/ |
Approve driver request |
| GET | /api/orders/deliveries/ |
Delivery list |
| GET | /api/orders/deliveries/<id>/ |
Delivery detail |
| PUT | /api/orders/deliveries/<id>/update_status/ |
Update delivery status |
| PUT | /api/orders/deliveries/<id>/assign_driver/ |
Assign driver to delivery (supplier only) |
| Method | URL | Description |
|---|---|---|
| GET | /api/users/profiles/ |
List profiles by role (SUPPLIER, SELLER, DRIVER) |
| GET | /api/orders/available-deliveries/ |
Available deliveries for drivers |
| POST | /api/orders/accept-delivery/<id>/ |
Driver accept delivery |
This project follows a comprehensive testing strategy with 77% code coverage (src + apps), ensuring reliability and maintainability.
We use pytest as our primary testing framework, following the AAA pattern (Arrange-Act-Assert) for clear and readable tests. Our testing strategy covers three main levels:
Test individual components in isolation:
- Models (
test_models.py): Test model methods, validations, relationships, and business logic - Serializers (
test_serializers.py): Test data validation, transformation, and serialization logic - Services (
test_services.py): Test business logic, data operations, and service layer methods
Example Unit Test:
def test_create_deal(self, seller_user, supplier_user):
"""Test creating a deal"""
deal = Deal.objects.create(
seller=seller_user.seller_profile,
supplier=supplier_user.supplier_profile,
delivery_handler=Deal.DeliveryHandler.SYSTEM_DRIVER,
status=Deal.Status.DEALING
)
assert deal.seller == seller_user.seller_profile
assert deal.status == Deal.Status.DEALINGTest component interactions and API endpoints:
- Views (
test_views.py): Test API endpoints, authentication, permissions, and request/response handling - End-to-End Flows: Test complete user workflows (registration β product creation β deal β delivery)
Example Integration Test:
def test_create_deal_as_seller(self, seller_client, supplier_user, product):
"""Test seller can create a deal"""
response = seller_client.post('/api/orders/deals/', {
'supplier_id': supplier_user.supplier_profile.id,
'items': [{'product_id': product.id, 'quantity': 10}]
})
assert response.status_code == 201
assert Deal.objects.count() == 1Shared Fixtures (tests/conftest.py):
api_client: DRF API client for making requestsuser,supplier_user,seller_user,driver_user: User fixtures with different rolesproduct,category,deal: Domain object fixturesauthenticated_client,supplier_client, etc.: Pre-authenticated API clients
Test Organization:
The test suite follows a layered architecture pattern, mirroring the application structure:
tests/
βββ conftest.py # Shared pytest fixtures
βββ test_users/ # User module tests
β βββ test_models.py # User model unit tests
β βββ test_serializers.py # User serializer tests
β βββ test_services.py # User service layer tests
β βββ test_views.py # User API integration tests
βββ test_products/ # Product module tests
β βββ test_models.py # Product/Category model tests
β βββ test_serializers.py # Product serializer tests
β βββ test_services.py # Product service layer tests
β βββ test_views.py # Product API integration tests
βββ test_orders/ # Order module tests
β βββ test_models.py # Deal/Delivery/RequestToDriver model tests
β βββ test_serializers.py # Order serializer tests
β βββ test_services.py # Order service layer tests
β βββ test_views.py # Order API integration tests
βββ test_e2e/ # End-to-end flow tests (API-only)
β βββ test_order_flow.py # Full order flows via HTTP
βββ test_core/ # Core utility tests
βββ test_utils.py
βββ test_health_check.py
Test Organization Principles:
- One file per layer: Each module has separate test files for models, serializers, services, and views
- Consistent structure: All modules follow the same test organization pattern
- Clear separation: Unit tests (models, serializers, services) are separate from integration tests (views)
- E2E layer: Full user journeys are tested in
test_e2e/via API only (no direct DB manipulation) - Markers: Unit tests use
pytest.mark.unit, integration tests (views, health check) usepytest.mark.integration, E2E tests usepytest.mark.e2eβ run withpytest -m unit,pytest -m integration,pytest -m e2e - Shared fixtures: Common test data is defined in
conftest.pyfor reusability
This organization ensures:
- Maintainability: Easy to find and update tests for specific layers
- Scalability: New modules can follow the same pattern
- Clarity: Test structure mirrors application architecture
- Best Practices: Follows industry-standard testing patterns
Run all tests:
pytestRun with coverage report:
pytest --cov --cov-report=term-missingRun specific test file:
pytest tests/test_users/test_views.pyRun specific test:
pytest tests/test_users/test_views.py::TestUserLogin::test_login_successRun tests by marker:
pytest -m unit # Run only unit tests
pytest -m integration # Run only integration tests
pytest -m e2e # Run only end-to-end flow tests
pytest -m "not slow" # Skip slow testsCurrent test coverage: 88% (run pytest --cov=apps --cov-report=term-missing)
Coverage is maintained through:
- Comprehensive unit tests for all models and services
- Integration tests for all API endpoints
- Edge case testing for business logic
- Error handling and validation testing
- β
Professional project structure with
apps/for modules (single root) - β Environment-based configuration
- β Layered architecture (Models, Views, Serializers, Services)
- β Token Authentication (Django REST Framework)
- β Custom User Model
- β API Documentation (Swagger/ReDoc)
- β CORS support
- β Database abstraction (PostgreSQL ready)
- β Logging configuration
- β Development/Production settings separation
- β Base classes for reusability
- β Custom exceptions and permissions
- β Pagination support
- β Comprehensive test suite with pytest
All commands below require the virtual environment to be activated.
- Load Categories: Load market food categories into the database
To reset and reload all categories:
python manage.py load_categories
python manage.py load_categories --reset
- Django Debug Toolbar: Available in development mode
- Black: Code formatting
black . - isort: Import sorting
isort . - flake8: Linting
flake8 . - pytest: Testing framework (see Testing section above)
- Create a new module in
apps/:
mkdir -p apps/<module_name>-
Create the module structure:
__init__.pyapps.py- Django app config (setname = 'apps.<module_name>'andlabel = '<module_name>')models.py- Database modelsserializers.py- Data serializationviews.py- API viewsurls.py- URL routingservices.py- Business logicadmin.py- Admin configurationutils.py- Module utilities (optional)
-
Add the module to
INSTALLED_APPSinconfig/settings/base.py:
LOCAL_APPS = [
'apps.core',
'apps.users',
'apps.products',
'apps.orders',
'apps.<module_name>', # Add your new module
]- Include URLs in
config/urls.py:
path('api/<module_path>/', include('apps.<module_name>.urls')),- Create tests in
tests/test_<module_name>/:test_models.pytest_views.pytest_serializers.pytest_services.py
- Never commit
.envfile to version control - Use strong
SECRET_KEYin production - Set
DEBUG=Falsein production - Configure
ALLOWED_HOSTSproperly - Use HTTPS in production
- Keep dependencies updated
This project is open source and available under the MIT License.
Contributions are welcome! Please feel free to submit a Pull Request.