feat(projects): add projects app's basic structure and endpoints

This commit is contained in:
2026-03-11 18:51:04 +08:00
parent 7b6b288c41
commit 7152ab9aca
13 changed files with 893 additions and 2 deletions

View File

@@ -0,0 +1,32 @@
from rest_framework.exceptions import ValidationError
from apps.projects.models import ProjectMembership
def add_project_member(project, user, role):
"""
Adds a user to a project. Ensures no duplicate active memberships exist.
"""
if ProjectMembership.objects.filter(project=project, user=user, is_deleted=False).exists():
raise ValidationError({"user_id": "This user is already a member of the project."})
return ProjectMembership.objects.create(
project=project,
user=user,
role=role,
is_active=True
)
def update_project_member(membership, **kwargs):
"""
Updates a project membership (e.g., changing role or active status).
"""
update_fields = []
for field, value in kwargs.items():
if hasattr(membership, field) and getattr(membership, field) != value:
setattr(membership, field, value)
update_fields.append(field)
if update_fields:
update_fields.append("updated_at")
membership.save(update_fields=update_fields)
return membership

View File

@@ -0,0 +1,74 @@
from django.db import transaction
from rest_framework.exceptions import ValidationError, PermissionDenied
from apps.projects.models import Project, ProjectMembership
from apps.workspaces.models import WorkspaceMembership
@transaction.atomic
def create_project(user, workspace, name, client=None, description="", color=""):
"""
Creates a new project and automatically assigns the creator
as an active MANAGER of that project.
"""
workspace_member = WorkspaceMembership.objects.filter(
workspace=workspace,
user=user,
is_active=True,
is_deleted=False
).first()
if not workspace_member:
raise PermissionDenied("You do not have access to this workspace.")
if Project.objects.filter(workspace=workspace, name=name, is_deleted=False).exists():
raise ValidationError({"name": "A project with this name already exists in the workspace."})
project = Project.objects.create(
workspace=workspace,
name=name,
client=client,
description=description,
color=color
)
ProjectMembership.objects.create(
project=project,
user=user,
role=ProjectMembership.Role.MANAGER,
is_active=True
)
return project
def update_project(project, **kwargs):
"""
Updates specific fields of an existing project.
"""
update_fields = []
# Optional manual uniqueness check if name is being updated
if "name" in kwargs and kwargs["name"] != project.name:
if Project.objects.filter(workspace=project.workspace, name=kwargs["name"], is_deleted=False).exists():
raise ValidationError({"name": "A project with this name already exists in the workspace."})
for field, value in kwargs.items():
if hasattr(project, field) and getattr(project, field) != value:
setattr(project, field, value)
update_fields.append(field)
if update_fields:
update_fields.append("updated_at")
project.save(update_fields=update_fields)
return project
def toggle_project_archive(project) -> Project:
"""
Archives or unarchives a project.
"""
project.is_archived = not project.is_archived
project.save(update_fields=["is_archived", "updated_at"])
return project

View File

@@ -0,0 +1,42 @@
from apps.projects.models import ProjectRate, ProjectUserRate
def create_project_rate(project, amount, currency="USD"):
return ProjectRate.objects.create(
project=project,
amount=amount,
currency=currency
)
def update_project_rate(rate_instance, **kwargs):
update_fields = []
for field, value in kwargs.items():
if hasattr(rate_instance, field) and getattr(rate_instance, field) != value:
setattr(rate_instance, field, value)
update_fields.append(field)
if update_fields:
update_fields.append("updated_at")
rate_instance.save(update_fields=update_fields)
return rate_instance
def create_project_user_rate(project, user, amount, currency="USD"):
return ProjectUserRate.objects.create(
project=project,
user=user,
amount=amount,
currency=currency
)
def update_project_user_rate(user_rate_instance, **kwargs):
update_fields = []
for field, value in kwargs.items():
if hasattr(user_rate_instance, field) and getattr(user_rate_instance, field) != value:
setattr(user_rate_instance, field, value)
update_fields.append(field)
if update_fields:
update_fields.append("updated_at")
user_rate_instance.save(update_fields=update_fields)
return user_rate_instance