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.
13 KiB
13 KiB
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
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