From 62aedcaff74ea79561d67ca6cfe517d3a1e69505 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Tue, 9 Dec 2025 14:44:49 +0545 Subject: [PATCH] - Introducing `format_strong`, `format_strong_with_subtext`, `format_placeholder`, `format_badge`, `format_currency`, `format_boolean_icon`, and `format_link` functions in `src/admin/shared.py`. --- src/admin/clients.py | 16 +++++------- src/admin/finance.py | 32 +++++++++-------------- src/admin/productivity.py | 28 ++++++++++---------- src/admin/projects.py | 46 ++++++++++++++------------------ src/admin/services.py | 44 ++++++++++++++----------------- src/admin/shared.py | 55 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 128 insertions(+), 93 deletions(-) create mode 100644 src/admin/shared.py diff --git a/src/admin/clients.py b/src/admin/clients.py index 375242a..85aa51b 100644 --- a/src/admin/clients.py +++ b/src/admin/clients.py @@ -1,11 +1,13 @@ from django.contrib import admin from django.forms import ModelForm -from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from phonenumber_field.formfields import SplitPhoneNumberField -# Assuming these imports are correct based on your setup -from src.admin.base import admin_site +from src.admin.shared import ( + admin_site, + format_placeholder, + format_strong_with_subtext, +) from src.models.clients import Client @@ -64,11 +66,7 @@ class ClientAdmin(admin.ModelAdmin): def name_display(self, obj): """Display name with subtle emphasis.""" - return format_html( - "{}
{}", - obj.name, - obj.short_code or "—", - ) + return format_strong_with_subtext(obj.name, obj.short_code) name_display.short_description = "Client" @@ -77,7 +75,7 @@ class ClientAdmin(admin.ModelAdmin): if obj.phone: # Uses the .as_international property from the PhoneNumber wrapper return obj.phone.as_international - return "—" + return format_placeholder() formatted_phone.short_description = "Phone" diff --git a/src/admin/finance.py b/src/admin/finance.py index bea461a..e5e8b1e 100644 --- a/src/admin/finance.py +++ b/src/admin/finance.py @@ -1,8 +1,12 @@ from django.contrib import admin -from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ -from src.admin.base import admin_site +from src.admin.shared import ( + admin_site, + format_badge, + format_currency, + format_strong_with_subtext, +) from src.models.finance import Expense, Invoice, Payment @@ -76,11 +80,7 @@ class InvoiceAdmin(admin.ModelAdmin): ) def client_display(self, obj): - return format_html( - "{}
{}", - obj.client.name, - obj.client.short_code, - ) + return format_strong_with_subtext(obj.client.name, obj.client.short_code) client_display.short_description = "Client" @@ -90,7 +90,7 @@ class InvoiceAdmin(admin.ModelAdmin): project_display.short_description = "Project" def amount_display(self, obj): - return format_html("${:,.2f}", obj.amount) + return format_currency(obj.amount) amount_display.short_description = "Amount" @@ -103,11 +103,7 @@ class InvoiceAdmin(admin.ModelAdmin): "CANCELLED": "#6c757d", } color = color_map.get(obj.status, "#333") - return format_html( - "{}", - color, - obj.get_status_display(), - ) + return format_badge(obj.get_status_display(), background=color) status_badge.short_description = "Status" @@ -154,7 +150,7 @@ class ExpenseAdmin(admin.ModelAdmin): readonly_fields = ("created_at",) def amount_display(self, obj): - return format_html("${:,.2f}", obj.amount) + return format_currency(obj.amount) amount_display.short_description = "Amount" @@ -182,15 +178,13 @@ class PaymentAdmin(admin.ModelAdmin): ) def invoice_display(self, obj): - return format_html( - "{}
{}", - obj.invoice.invoice_number, - obj.invoice.client.name, + return format_strong_with_subtext( + obj.invoice.invoice_number, obj.invoice.client.name ) invoice_display.short_description = "Invoice" def amount_display(self, obj): - return format_html("${:,.2f}", obj.amount) + return format_currency(obj.amount) amount_display.short_description = "Amount" diff --git a/src/admin/productivity.py b/src/admin/productivity.py index caef48c..d33c65e 100644 --- a/src/admin/productivity.py +++ b/src/admin/productivity.py @@ -1,9 +1,13 @@ from django.contrib import admin -from django.utils.html import format_html from django.utils.timezone import localtime from django.utils.translation import gettext_lazy as _ -from src.admin.base import admin_site +from src.admin.shared import ( + admin_site, + format_badge, + format_placeholder, + format_strong, +) from src.models.productivity import Note, TimeEntry @@ -34,7 +38,7 @@ class NoteAdmin(admin.ModelAdmin): def title_display(self, obj): """Show truncated title for clarity.""" - return format_html("{}", obj.title[:60]) + return format_strong(obj.title[:60]) title_display.short_description = "Title" @@ -77,17 +81,17 @@ class TimeEntryAdmin(admin.ModelAdmin): readonly_fields = ("duration",) def description_display(self, obj): - return format_html("{}", obj.description[:50]) + return format_strong(obj.description[:50]) description_display.short_description = "Description" def project_display(self, obj): - return obj.project.name if obj.project else "—" + return obj.project.name if obj.project else format_placeholder() project_display.short_description = "Project" def task_display(self, obj): - return obj.task.name if obj.task else "—" + return obj.task.name if obj.task else format_placeholder() task_display.short_description = "Task" @@ -95,7 +99,7 @@ class TimeEntryAdmin(admin.ModelAdmin): return ( localtime(obj.start_time).strftime("%Y-%m-%d %H:%M") if obj.start_time - else "—" + else format_placeholder() ) start_time_display.short_description = "Start" @@ -103,7 +107,7 @@ class TimeEntryAdmin(admin.ModelAdmin): def end_time_display(self, obj): if obj.end_time: return localtime(obj.end_time).strftime("%Y-%m-%d %H:%M") - return format_html("In Progress") + return format_badge("In Progress", background="#999") end_time_display.short_description = "End" @@ -111,12 +115,8 @@ class TimeEntryAdmin(admin.ModelAdmin): if obj.duration: hours = obj.duration.total_seconds() / 3600 color = "#28a745" if hours < 8 else "#f39c12" - return format_html( - "{:.2f} hrs", - color, - hours, - ) - return format_html("—") + return format_badge(f"{hours:.2f} hrs", background=color) + return format_placeholder() duration_display.short_description = "Duration" diff --git a/src/admin/projects.py b/src/admin/projects.py index 5383ade..30989e6 100644 --- a/src/admin/projects.py +++ b/src/admin/projects.py @@ -1,9 +1,15 @@ 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.admin.shared import ( + admin_site, + format_badge, + format_currency, + format_placeholder, + format_strong, + format_strong_with_subtext, +) from src.models.projects import Milestone, Project, Task @@ -73,7 +79,7 @@ class ProjectAdmin(admin.ModelAdmin): readonly_fields = ("created_at", "updated_at") def name_display(self, obj): - return format_html("{}", obj.name) + return format_strong(obj.name) name_display.short_description = "Project" @@ -91,16 +97,12 @@ class ProjectAdmin(admin.ModelAdmin): "CANCELLED": "#dc3545", } color = color_map.get(obj.status, "#999") - return format_html( - "{}", - color, - obj.get_status_display(), - ) + return format_badge(obj.get_status_display(), background=color) status_badge.short_description = "Status" def budget_display(self, obj): - return format_html("${:,.2f}", obj.budget) if obj.budget else "—" + return format_currency(obj.budget) if obj.budget else format_placeholder() budget_display.short_description = "Budget" @@ -131,7 +133,7 @@ class MilestoneAdmin(admin.ModelAdmin): ) def title_display(self, obj): - return format_html("{}", obj.title) + return format_strong(obj.title) title_display.short_description = "Title" @@ -143,11 +145,7 @@ class MilestoneAdmin(admin.ModelAdmin): def status_badge(self, obj): color = "#28a745" if obj.is_completed else "#dc3545" text = "Completed" if obj.is_completed else "Pending" - return format_html( - "{}", - color, - text, - ) + return format_badge(text, background=color) status_badge.short_description = "Status" @@ -189,7 +187,7 @@ class TaskAdmin(admin.ModelAdmin): readonly_fields = ("created_at", "updated_at") def title_display(self, obj): - return format_html("{}", obj.title) + return format_strong(obj.title) title_display.short_description = "Title" @@ -200,11 +198,11 @@ class TaskAdmin(admin.ModelAdmin): def due_date_display(self, obj): if not obj.due_date: - return format_html("No due date") - elif obj.due_date < timezone.localdate(): - return format_html("{}", obj.due_date) - elif obj.due_date == timezone.localdate(): - return format_html("Today") + return format_placeholder(None, "No due date", "#999") + if obj.due_date < timezone.localdate(): + return format_badge(obj.due_date, background="#dc3545") + if obj.due_date == timezone.localdate(): + return format_badge("Today", background="#f39c12") return obj.due_date due_date_display.short_description = "Due Date" @@ -217,11 +215,7 @@ class TaskAdmin(admin.ModelAdmin): "DONE": "#28a745", } color = color_map.get(obj.status, "#333") - return format_html( - "{}", - color, - obj.get_status_display(), - ) + return format_badge(obj.get_status_display(), background=color) status_badge.short_description = "Status" diff --git a/src/admin/services.py b/src/admin/services.py index 5a53934..87c04e9 100644 --- a/src/admin/services.py +++ b/src/admin/services.py @@ -1,9 +1,17 @@ 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.admin.shared import ( + admin_site, + format_badge, + format_boolean_icon, + format_currency, + format_link, + format_placeholder, + format_strong, + format_strong_with_subtext, +) from src.models.services import Credential, Service @@ -75,7 +83,7 @@ class ServiceAdmin(admin.ModelAdmin): readonly_fields = ("created_at", "updated_at") def name_display(self, obj): - return format_html("{}", obj.name) + return format_strong(obj.name) name_display.short_description = "Service" @@ -93,28 +101,24 @@ class ServiceAdmin(admin.ModelAdmin): "OTHER": "#6c757d", } color = color_map.get(obj.service_type, "#333") - return format_html( - "{}", - color, - obj.get_service_type_display(), - ) + return format_badge(obj.get_service_type_display(), background=color) type_badge.short_description = "Type" def cost_display(self, obj): - return format_html("${:,.2f}", obj.cost) + return format_currency(obj.cost) cost_display.short_description = "Cost" def renewal_price_display(self, obj): - return format_html("${:,.2f}", obj.renewal_price) + return format_currency(obj.renewal_price) renewal_price_display.short_description = "Renewal" def expiry_display(self, obj): """Color-coded expiry indicator.""" if not obj.expiry_date: - return format_html("—") + return format_placeholder() today = timezone.localdate() delta = (obj.expiry_date - today).days @@ -138,11 +142,7 @@ class ServiceAdmin(admin.ModelAdmin): expiry_display.short_description = "Expiry" def auto_renew_display(self, obj): - icon = "✅" if obj.auto_renew else "❌" - color = "#28a745" if obj.auto_renew else "#999" - return format_html( - f"{icon}" - ) + return format_boolean_icon(obj.auto_renew) auto_renew_display.short_description = "Auto Renew" @@ -183,23 +183,17 @@ class CredentialAdmin(admin.ModelAdmin): # --- DISPLAY HELPERS --- def service_display(self, obj): - return format_html( - "{}
{}", - obj.service.name, - obj.service.client.name, - ) + return format_strong_with_subtext(obj.service.name, obj.service.client.name) service_display.short_description = "Service" def username_display(self, obj): - return obj.username or format_html("—") + return obj.username or format_placeholder() username_display.short_description = "Username" def url_display(self, obj): - if obj.url: - return format_html("{}", obj.url, obj.url) - return format_html("—") + return format_link(obj.url) url_display.short_description = "URL" diff --git a/src/admin/shared.py b/src/admin/shared.py new file mode 100644 index 0000000..ac69b24 --- /dev/null +++ b/src/admin/shared.py @@ -0,0 +1,55 @@ +from django.utils.html import format_html +from src.admin.base import * #noqa: F401 + +def format_strong(text): + return format_html("{}", text) + + +def format_strong_with_subtext(primary, secondary, secondary_color="#888"): + return format_html( + "{}
{}", + primary, + secondary_color, + secondary if secondary else "—", + ) + + +def format_placeholder(value=None, placeholder="—", color="#888"): + return ( + value + if value + else format_html("{}", color, placeholder) + ) + + +def format_badge(label, background="#333", text_color="white"): + return format_html( + "{}", + background, + text_color, + label, + ) + + +def format_currency(amount): + return format_html("${:,.2f}", amount) if amount is not None else "—" + + +def format_boolean_icon( + flag, true_icon="✅", false_icon="❌", true_color="#28a745", false_color="#999" +): + color = true_color if flag else false_color + icon = true_icon if flag else false_icon + return format_html( + "{}", + color, + icon, + ) + + +def format_link(url, label=None): + return ( + format_html("{}", url, label or url) + if url + else format_placeholder() + ) \ No newline at end of file