This change removes unused DRF-related dependencies, updates .env.sample and .gitignore to reflect new configuration, and adds a comprehensive project architecture plan (PLAN.md). It simplifies the stack and prepares the codebase for a modular, permission‑driven design. New Features - Added detailed PLAN.md outlining project structure, apps, permissions, and tech stack. Enhancements - Cleaned up .env.sample by removing obsolete environment variables. - Updated .gitignore to exclude generated directories and track migrations properly. Bug Fixes - No bug fixes included in this commit.
539 lines
13 KiB
Markdown
539 lines
13 KiB
Markdown
# Django Project Architecture Plan
|
|
|
|
## Overview
|
|
|
|
This project is a modular Django-based project management and business operations platform with:
|
|
|
|
- Role-based access control
|
|
- Project management
|
|
- Private kanban workflows
|
|
- Issue tracking
|
|
- Ticketing system
|
|
- Finance & payroll
|
|
- Activity tracking
|
|
- HTMX + Alpine.js frontend architecture
|
|
|
|
---
|
|
|
|
# Project Structure
|
|
|
|
````text
|
|
myproject/
|
|
├── config/ # settings, urls, wsgi
|
|
├── apps/
|
|
│ ├── accounts/ # CustomUser, role system
|
|
│ ├── projects/ # Project, Membership
|
|
│ ├── finance/ # Invoice, Payment, Expense, Payroll
|
|
│ ├── kanban/ # Board, Column, Card (private per user)
|
|
│ ├── issues/ # Issue, Comment, Label, Status
|
|
│ ├── tickets/ # Ticket, FeatureRequest
|
|
│ └── activity/ # ActivityLog, progress aggregation
|
|
├── templates/
|
|
│ ├── base.html
|
|
│ ├── partials/ # HTMX partial templates
|
|
│ └── components/ # Alpine.js UI components
|
|
└── static/
|
|
```
|
|
|
|
---
|
|
|
|
# Actors
|
|
|
|
## Admin
|
|
- Full system access
|
|
- Manage users, projects, finance, tickets, and reports
|
|
- Read-only access to aggregated kanban progress
|
|
|
|
## Client
|
|
- View own projects
|
|
- Create/view tickets
|
|
- View invoices and payments
|
|
- Comment on issues
|
|
|
|
## Team Member
|
|
- Work on assigned issues
|
|
- Access own kanban board
|
|
- View assigned projects
|
|
- Access payroll information
|
|
|
|
---
|
|
|
|
# Django Apps Breakdown
|
|
|
|
## 1. accounts
|
|
|
|
### Responsibilities
|
|
- Authentication
|
|
- Authorization
|
|
- Role management
|
|
- Project memberships
|
|
|
|
### Main Models
|
|
|
|
```python
|
|
class CustomUser(AbstractUser):
|
|
ROLE_CHOICES = [
|
|
("admin", "Admin"),
|
|
("client", "Client"),
|
|
("member", "Team Member"),
|
|
]
|
|
|
|
role = models.CharField(max_length=20, choices=ROLE_CHOICES)
|
|
```
|
|
|
|
```python
|
|
class ProjectMembership(models.Model):
|
|
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
|
|
project = models.ForeignKey("projects.Project", on_delete=models.CASCADE)
|
|
joined_at = models.DateTimeField(auto_now_add=True)
|
|
```
|
|
|
|
---
|
|
|
|
## 2. projects
|
|
|
|
### Responsibilities
|
|
- Core project management
|
|
- Team assignment
|
|
- Project lifecycle
|
|
|
|
### Main Models
|
|
|
|
```python
|
|
class Project(models.Model):
|
|
|
|
class Status(models.TextChoices):
|
|
PLANNING = "planning"
|
|
ACTIVE = "active"
|
|
REVIEW = "review"
|
|
DONE = "done"
|
|
|
|
name = models.CharField(max_length=255)
|
|
client = models.ForeignKey(
|
|
"accounts.CustomUser",
|
|
on_delete=models.CASCADE,
|
|
related_name="client_projects"
|
|
)
|
|
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=Status.choices,
|
|
default=Status.PLANNING
|
|
)
|
|
|
|
members = models.ManyToManyField(
|
|
"accounts.CustomUser",
|
|
through="accounts.ProjectMembership"
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 3. finance
|
|
|
|
### Responsibilities
|
|
- Invoices
|
|
- Payments
|
|
- Expenses
|
|
- Payroll management
|
|
|
|
### Main Models
|
|
|
|
## Invoice
|
|
|
|
```python
|
|
class Invoice(models.Model):
|
|
|
|
class Status(models.TextChoices):
|
|
PENDING = "pending"
|
|
PAID = "paid"
|
|
OVERDUE = "overdue"
|
|
|
|
project = models.ForeignKey("projects.Project", on_delete=models.CASCADE)
|
|
client = models.ForeignKey("accounts.CustomUser", on_delete=models.CASCADE)
|
|
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
due_date = models.DateField()
|
|
status = models.CharField(max_length=20, choices=Status.choices)
|
|
```
|
|
|
|
## Payment
|
|
|
|
```python
|
|
class Payment(models.Model):
|
|
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
|
|
paid_at = models.DateTimeField()
|
|
method = models.CharField(max_length=100)
|
|
```
|
|
|
|
## Expense
|
|
|
|
```python
|
|
class Expense(models.Model):
|
|
project = models.ForeignKey("projects.Project", on_delete=models.CASCADE)
|
|
raised_by = models.ForeignKey("accounts.CustomUser", on_delete=models.CASCADE)
|
|
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
category = models.CharField(max_length=100)
|
|
```
|
|
|
|
## TeamPayment
|
|
|
|
```python
|
|
class TeamPayment(models.Model):
|
|
member = models.ForeignKey("accounts.CustomUser", on_delete=models.CASCADE)
|
|
period = models.DateField()
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
```
|
|
|
|
---
|
|
|
|
## 4. kanban
|
|
|
|
### Responsibilities
|
|
- Personal task management
|
|
- Progress tracking
|
|
- Drag-and-drop workflow
|
|
|
|
### Privacy Rules
|
|
- Each board belongs to one user
|
|
- Members cannot access others' boards
|
|
- Admin sees only aggregated progress
|
|
|
|
### Main Models
|
|
|
|
## Board
|
|
|
|
```python
|
|
class Board(models.Model):
|
|
owner = models.ForeignKey(
|
|
"accounts.CustomUser",
|
|
on_delete=models.CASCADE
|
|
)
|
|
|
|
project = models.ForeignKey(
|
|
"projects.Project",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.CASCADE
|
|
)
|
|
```
|
|
|
|
## Column
|
|
|
|
```python
|
|
class Column(models.Model):
|
|
board = models.ForeignKey(Board, on_delete=models.CASCADE)
|
|
name = models.CharField(max_length=100)
|
|
order = models.PositiveIntegerField()
|
|
```
|
|
|
|
## Card
|
|
|
|
```python
|
|
class Card(models.Model):
|
|
column = models.ForeignKey(Column, on_delete=models.CASCADE)
|
|
|
|
title = models.CharField(max_length=255)
|
|
description = models.TextField(blank=True)
|
|
|
|
due_date = models.DateField(null=True, blank=True)
|
|
order = models.PositiveIntegerField()
|
|
```
|
|
|
|
### Secure QuerySet Pattern
|
|
|
|
```python
|
|
class BoardQuerySet(models.QuerySet):
|
|
|
|
def for_user(self, user):
|
|
return self.filter(owner=user)
|
|
```
|
|
|
|
---
|
|
|
|
## 5. issues
|
|
|
|
### Responsibilities
|
|
- Bug tracking
|
|
- Feature tracking
|
|
- Task assignment
|
|
- Internal discussion
|
|
|
|
### Main Models
|
|
|
|
## Issue
|
|
|
|
```python
|
|
class Issue(models.Model):
|
|
|
|
class Status(models.TextChoices):
|
|
OPEN = "open"
|
|
IN_PROGRESS = "in_progress"
|
|
RESOLVED = "resolved"
|
|
CLOSED = "closed"
|
|
|
|
class Priority(models.TextChoices):
|
|
LOW = "low"
|
|
MEDIUM = "medium"
|
|
HIGH = "high"
|
|
|
|
project = models.ForeignKey("projects.Project", on_delete=models.CASCADE)
|
|
|
|
reporter = models.ForeignKey(
|
|
"accounts.CustomUser",
|
|
on_delete=models.CASCADE,
|
|
related_name="reported_issues"
|
|
)
|
|
|
|
assignee = models.ForeignKey(
|
|
"accounts.CustomUser",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="assigned_issues"
|
|
)
|
|
|
|
status = models.CharField(max_length=20, choices=Status.choices)
|
|
priority = models.CharField(max_length=20, choices=Priority.choices)
|
|
```
|
|
|
|
## IssueComment
|
|
|
|
```python
|
|
class IssueComment(models.Model):
|
|
issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
|
|
author = models.ForeignKey("accounts.CustomUser", on_delete=models.CASCADE)
|
|
body = models.TextField()
|
|
```
|
|
|
|
---
|
|
|
|
## 6. tickets
|
|
|
|
### Responsibilities
|
|
- Client support
|
|
- Feature requests
|
|
- Bug reporting
|
|
|
|
### Main Models
|
|
|
|
```python
|
|
class Ticket(models.Model):
|
|
|
|
class TicketType(models.TextChoices):
|
|
BUG = "bug"
|
|
FEATURE = "feature_request"
|
|
SUPPORT = "support"
|
|
|
|
class Status(models.TextChoices):
|
|
NEW = "new"
|
|
ACKNOWLEDGED = "acknowledged"
|
|
IN_PROGRESS = "in_progress"
|
|
DONE = "done"
|
|
REJECTED = "rejected"
|
|
|
|
creator = models.ForeignKey(
|
|
"accounts.CustomUser",
|
|
on_delete=models.CASCADE
|
|
)
|
|
|
|
project = models.ForeignKey(
|
|
"projects.Project",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.CASCADE
|
|
)
|
|
|
|
ticket_type = models.CharField(max_length=30, choices=TicketType.choices)
|
|
status = models.CharField(max_length=30, choices=Status.choices)
|
|
```
|
|
|
|
### Access Rules
|
|
- Only admin and client can create tickets
|
|
- Team members can only view assigned tickets
|
|
- Tickets may be converted into issues
|
|
|
|
---
|
|
|
|
## 7. activity
|
|
|
|
### Responsibilities
|
|
- Audit logging
|
|
- Timeline tracking
|
|
- Progress aggregation
|
|
|
|
### Main Models
|
|
|
|
```python
|
|
class ActivityLog(models.Model):
|
|
actor = models.ForeignKey(
|
|
"accounts.CustomUser",
|
|
on_delete=models.CASCADE
|
|
)
|
|
|
|
verb = models.CharField(max_length=255)
|
|
|
|
content_type = models.ForeignKey(
|
|
ContentType,
|
|
on_delete=models.CASCADE
|
|
)
|
|
|
|
object_id = models.PositiveIntegerField()
|
|
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
```
|
|
|
|
### Rules
|
|
- Members cannot view other members' activity logs
|
|
- Admin dashboard uses aggregated activity metrics
|
|
|
|
---
|
|
|
|
# Frontend Stack
|
|
|
|
## HTMX
|
|
Use HTMX for:
|
|
- Kanban drag-and-drop persistence
|
|
- Ticket status updates
|
|
- Issue comments
|
|
- Live search
|
|
- Partial rendering
|
|
- Inline updates
|
|
|
|
## Alpine.js
|
|
Use Alpine.js for:
|
|
- Modal state
|
|
- Dropdowns
|
|
- Tabs
|
|
- Confirm dialogs
|
|
- Inline edit toggles
|
|
|
|
## Suggested Drag-and-Drop Pattern
|
|
|
|
- Use Sortable.js for drag UI
|
|
- Trigger HTMX POST request on drag end
|
|
- Persist new ordering server-side
|
|
|
|
---
|
|
|
|
# Permission Pattern
|
|
|
|
Instead of Django object permissions, use reusable helper functions and view mixins.
|
|
|
|
## Example
|
|
|
|
```python
|
|
class ProjectMemberRequired(LoginRequiredMixin):
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
project = get_object_or_404(
|
|
Project,
|
|
pk=kwargs["pk"]
|
|
)
|
|
|
|
if not project.members.filter(
|
|
pk=request.user.pk
|
|
).exists():
|
|
raise PermissionDenied
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
```
|
|
|
|
---
|
|
|
|
# Access Control Matrix
|
|
|
|
| Module | Admin | Client | Team Member |
|
|
|------------------|----------------|----------------------|---------------------|
|
|
| Kanban Board | Progress only | No access | Own board only |
|
|
| Issues | Full | View + comment | Assigned only |
|
|
| Tickets/Features | Full | Create + view own | View assigned |
|
|
| Finance | Full | Own invoices | Own payroll |
|
|
| Projects | Full | View own project | Assigned project |
|
|
|
|
---
|
|
|
|
# Suggested Folder Layout
|
|
|
|
```text
|
|
core/
|
|
├── accounts
|
|
├── projects
|
|
├── notifications
|
|
└── activity
|
|
|
|
features/
|
|
├── kanban
|
|
├── issues
|
|
└── tickets
|
|
|
|
finance/
|
|
├── invoices
|
|
├── payments
|
|
├── expenses
|
|
└── payroll
|
|
|
|
templates/
|
|
├── base
|
|
├── components
|
|
├── partials
|
|
└── pages
|
|
```
|
|
|
|
---
|
|
|
|
# Recommended Tech Stack
|
|
|
|
## Backend
|
|
- Django
|
|
- Django REST Framework (optional API layer)
|
|
- PostgreSQL
|
|
- Redis
|
|
- Celery
|
|
|
|
## Frontend
|
|
- HTMX
|
|
- Alpine.js
|
|
- Tailwind CSS
|
|
- django-tailwind-cli
|
|
|
|
## Infrastructure
|
|
- Nginx
|
|
- Gunicorn
|
|
- Docker
|
|
- GitHub Actions CI/CD
|
|
|
|
---
|
|
|
|
# Future Improvements
|
|
|
|
- Real-time notifications with Django Channels
|
|
- WebSocket activity feed
|
|
- Time tracking
|
|
- Project analytics dashboard
|
|
- File attachments
|
|
- Email automation
|
|
- AI-assisted issue summaries
|
|
- Multi-tenant architecture
|
|
|
|
---
|
|
|
|
# Final Notes
|
|
|
|
This architecture keeps:
|
|
- Apps modular
|
|
- Permissions predictable
|
|
- HTMX interactions clean
|
|
- Kanban privacy secure
|
|
- Finance isolated
|
|
- Features scalable
|
|
|
|
The structure is optimized for:
|
|
- Long-term maintainability
|
|
- Team collaboration
|
|
- Incremental feature expansion
|
|
- Clear domain separation
|
|
````
|