This commit refactors the models in the 'models' app, specifically 'clients' and 'services' modules. It includes the PhoneNumberField in the Client model and adds a new Credential model to the services module. The Service model has been modified to include new fields and relationships.
This commit is contained in:
1 parent
27ca456212
commit
8b50ff5412
24 files changed
+1483
-49
No files matched your search
@@ -0,0 +1,233 @@
|
||||
from django.contrib import admin
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from src.admin.base import admin_site
|
||||
from src.models.projects import Milestone, Project, Task
|
||||
|
||||
|
||||
class MilestoneInline(admin.TabularInline):
|
||||
model = Milestone
|
||||
extra = 1
|
||||
fields = ("title", "due_date", "is_completed")
|
||||
show_change_link = True
|
||||
ordering = ("due_date",)
|
||||
verbose_name_plural = "Milestones"
|
||||
|
||||
|
||||
class TaskInline(admin.TabularInline):
|
||||
model = Task
|
||||
extra = 1
|
||||
fields = ("title", "status", "due_date")
|
||||
show_change_link = True
|
||||
ordering = ("status", "due_date")
|
||||
verbose_name_plural = "Tasks"
|
||||
|
||||
|
||||
@admin.register(Project, site=admin_site)
|
||||
class ProjectAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"name_display",
|
||||
"client_display",
|
||||
"status_badge",
|
||||
"start_date",
|
||||
"deadline",
|
||||
"budget_display",
|
||||
)
|
||||
list_filter = ("status", "client", "start_date", "deadline")
|
||||
search_fields = ("name", "client__name", "description")
|
||||
ordering = ("-created_at",)
|
||||
list_per_page = 25
|
||||
inlines = [MilestoneInline, TaskInline]
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
_("Project Overview"),
|
||||
{
|
||||
"fields": ("client", "name", "description", "status"),
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Timeline"),
|
||||
{
|
||||
"fields": ("start_date", "deadline"),
|
||||
"description": "Start and deadline dates help in tracking project progress.",
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Financials"),
|
||||
{
|
||||
"fields": ("budget",),
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Timestamps"),
|
||||
{
|
||||
"fields": ("created_at", "updated_at"),
|
||||
"classes": ("collapse",),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
readonly_fields = ("created_at", "updated_at")
|
||||
|
||||
def name_display(self, obj):
|
||||
return format_html("<strong>{}</strong>", obj.name)
|
||||
|
||||
name_display.short_description = "Project"
|
||||
|
||||
def client_display(self, obj):
|
||||
return obj.client.name
|
||||
|
||||
client_display.short_description = "Client"
|
||||
|
||||
def status_badge(self, obj):
|
||||
color_map = {
|
||||
"PLANNING": "#6c757d",
|
||||
"IN_PROGRESS": "#007bff",
|
||||
"ON_HOLD": "#ffc107",
|
||||
"COMPLETED": "#28a745",
|
||||
"CANCELLED": "#dc3545",
|
||||
}
|
||||
color = color_map.get(obj.status, "#999")
|
||||
return format_html(
|
||||
"<span style='background:{}; color:white; padding:3px 8px; border-radius:6px; font-size:12px;'>{}</span>",
|
||||
color,
|
||||
obj.get_status_display(),
|
||||
)
|
||||
|
||||
status_badge.short_description = "Status"
|
||||
|
||||
def budget_display(self, obj):
|
||||
return format_html("<b>${:,.2f}</b>", obj.budget) if obj.budget else "—"
|
||||
|
||||
budget_display.short_description = "Budget"
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj.deadline and obj.start_date and obj.deadline < obj.start_date:
|
||||
self.message_user(
|
||||
request,
|
||||
"⚠️ Deadline cannot be earlier than start date.",
|
||||
level="warning",
|
||||
)
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(Milestone, site=admin_site)
|
||||
class MilestoneAdmin(admin.ModelAdmin):
|
||||
list_display = ("title_display", "project_display", "due_date", "status_badge")
|
||||
list_filter = ("is_completed", "due_date", "project")
|
||||
search_fields = ("title", "project__name")
|
||||
ordering = ("due_date",)
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
_("Milestone Info"),
|
||||
{
|
||||
"fields": ("project", "title", "due_date", "is_completed"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def title_display(self, obj):
|
||||
return format_html("<strong>{}</strong>", obj.title)
|
||||
|
||||
title_display.short_description = "Title"
|
||||
|
||||
def project_display(self, obj):
|
||||
return obj.project.name
|
||||
|
||||
project_display.short_description = "Project"
|
||||
|
||||
def status_badge(self, obj):
|
||||
color = "#28a745" if obj.is_completed else "#dc3545"
|
||||
text = "Completed" if obj.is_completed else "Pending"
|
||||
return format_html(
|
||||
"<span style='background:{}; color:white; padding:2px 7px; border-radius:5px; font-size:12px;'>{}</span>",
|
||||
color,
|
||||
text,
|
||||
)
|
||||
|
||||
status_badge.short_description = "Status"
|
||||
|
||||
|
||||
@admin.register(Task, site=admin_site)
|
||||
class TaskAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"title_display",
|
||||
"project_display",
|
||||
"status_badge",
|
||||
"due_date_display",
|
||||
)
|
||||
list_filter = ("status", "due_date", "project")
|
||||
search_fields = ("title", "project__name", "description")
|
||||
ordering = ("status", "due_date")
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
_("Task Info"),
|
||||
{
|
||||
"fields": ("project", "title", "description", "status"),
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Schedule"),
|
||||
{
|
||||
"fields": ("due_date",),
|
||||
"description": "Due date cannot be in the past.",
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Timestamps"),
|
||||
{
|
||||
"fields": ("created_at", "updated_at"),
|
||||
"classes": ("collapse",),
|
||||
},
|
||||
),
|
||||
)
|
||||
readonly_fields = ("created_at", "updated_at")
|
||||
|
||||
def title_display(self, obj):
|
||||
return format_html("<strong>{}</strong>", obj.title)
|
||||
|
||||
title_display.short_description = "Title"
|
||||
|
||||
def project_display(self, obj):
|
||||
return obj.project.name
|
||||
|
||||
project_display.short_description = "Project"
|
||||
|
||||
def due_date_display(self, obj):
|
||||
if not obj.due_date:
|
||||
return format_html("<span style='color:#999;'>No due date</span>")
|
||||
elif obj.due_date < timezone.localdate():
|
||||
return format_html("<span style='color:#dc3545;'>{}</span>", obj.due_date)
|
||||
elif obj.due_date == timezone.localdate():
|
||||
return format_html("<span style='color:#f39c12;'>Today</span>")
|
||||
return obj.due_date
|
||||
|
||||
due_date_display.short_description = "Due Date"
|
||||
|
||||
def status_badge(self, obj):
|
||||
color_map = {
|
||||
"TODO": "#6c757d",
|
||||
"IN_PROGRESS": "#007bff",
|
||||
"REVIEW": "#ffc107",
|
||||
"DONE": "#28a745",
|
||||
}
|
||||
color = color_map.get(obj.status, "#333")
|
||||
return format_html(
|
||||
"<span style='background:{}; color:white; padding:3px 8px; border-radius:6px; font-size:12px;'>{}</span>",
|
||||
color,
|
||||
obj.get_status_display(),
|
||||
)
|
||||
|
||||
status_badge.short_description = "Status"
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj.due_date and obj.due_date < timezone.localdate():
|
||||
self.message_user(
|
||||
request, "⚠️ Due date cannot be in the past.", level="warning"
|
||||
)
|
||||
super().save_model(request, obj, form, change)
|
||||
Reference in new issue
Block a user