- 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.
This commit is contained in:
1 parent
4afe6eaa3a
commit
62aedcaff7
6 files changed
+128
-93
No files matched your search
@@ -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(
|
||||
"<strong>{}</strong><br><small style='color: #888;'>{}</small>",
|
||||
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"
|
||||
|
||||
|
||||
+13
-19
@@ -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(
|
||||
"<strong>{}</strong><br><small>{}</small>",
|
||||
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("<b>${:,.2f}</b>", 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(
|
||||
"<span style='color: white; background: {}; padding: 3px 8px; border-radius: 6px; font-size: 12px;'>{}</span>",
|
||||
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("<b>${:,.2f}</b>", 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(
|
||||
"<strong>{}</strong><br><small>{}</small>",
|
||||
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("<b>${:,.2f}</b>", obj.amount)
|
||||
return format_currency(obj.amount)
|
||||
|
||||
amount_display.short_description = "Amount"
|
||||
+14
-14
@@ -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("<strong>{}</strong>", 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("<strong>{}</strong>", 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("<span style='color: #999;'>In Progress</span>")
|
||||
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(
|
||||
"<span style='color: {}; font-weight: 600;'>{:.2f} hrs</span>",
|
||||
color,
|
||||
hours,
|
||||
)
|
||||
return format_html("<span style='color: #888;'>—</span>")
|
||||
return format_badge(f"{hours:.2f} hrs", background=color)
|
||||
return format_placeholder()
|
||||
|
||||
duration_display.short_description = "Duration"
|
||||
|
||||
|
||||
+20
-26
@@ -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("<strong>{}</strong>", 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(
|
||||
"<span style='background:{}; color:white; padding:3px 8px; border-radius:6px; font-size:12px;'>{}</span>",
|
||||
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("<b>${:,.2f}</b>", 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("<strong>{}</strong>", 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(
|
||||
"<span style='background:{}; color:white; padding:2px 7px; border-radius:5px; font-size:12px;'>{}</span>",
|
||||
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("<strong>{}</strong>", 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("<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 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(
|
||||
"<span style='background:{}; color:white; padding:3px 8px; border-radius:6px; font-size:12px;'>{}</span>",
|
||||
color,
|
||||
obj.get_status_display(),
|
||||
)
|
||||
return format_badge(obj.get_status_display(), background=color)
|
||||
|
||||
status_badge.short_description = "Status"
|
||||
|
||||
|
||||
+19
-25
@@ -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("<strong>{}</strong>", 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(
|
||||
"<span style='background:{}; color:white; padding:3px 8px; border-radius:6px; font-size:12px;'>{}</span>",
|
||||
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("<b>${:,.2f}</b>", obj.cost)
|
||||
return format_currency(obj.cost)
|
||||
|
||||
cost_display.short_description = "Cost"
|
||||
|
||||
def renewal_price_display(self, obj):
|
||||
return format_html("<b>${:,.2f}</b>", 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("<span style='color:#888;'>—</span>")
|
||||
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"<span style='color:{color}; font-size:14px;'>{icon}</span>"
|
||||
)
|
||||
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(
|
||||
"<strong>{}</strong><br><small>{}</small>",
|
||||
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("<span style='color:#888;'>—</span>")
|
||||
return obj.username or format_placeholder()
|
||||
|
||||
username_display.short_description = "Username"
|
||||
|
||||
def url_display(self, obj):
|
||||
if obj.url:
|
||||
return format_html("<a href='{}' target='_blank'>{}</a>", obj.url, obj.url)
|
||||
return format_html("<span style='color:#888;'>—</span>")
|
||||
return format_link(obj.url)
|
||||
|
||||
url_display.short_description = "URL"
|
||||
|
||||
|
||||
@@ -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("<strong>{}</strong>", text)
|
||||
|
||||
|
||||
def format_strong_with_subtext(primary, secondary, secondary_color="#888"):
|
||||
return format_html(
|
||||
"<strong>{}</strong><br><small style='color: {};'>{}</small>",
|
||||
primary,
|
||||
secondary_color,
|
||||
secondary if secondary else "—",
|
||||
)
|
||||
|
||||
|
||||
def format_placeholder(value=None, placeholder="—", color="#888"):
|
||||
return (
|
||||
value
|
||||
if value
|
||||
else format_html("<span style='color:{};'>{}</span>", color, placeholder)
|
||||
)
|
||||
|
||||
|
||||
def format_badge(label, background="#333", text_color="white"):
|
||||
return format_html(
|
||||
"<span style='background:{}; color:{}; padding:3px 8px; border-radius:6px; font-size:12px;'>{}</span>",
|
||||
background,
|
||||
text_color,
|
||||
label,
|
||||
)
|
||||
|
||||
|
||||
def format_currency(amount):
|
||||
return format_html("<b>${:,.2f}</b>", 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(
|
||||
"<span style='color:{}; font-size:14px;'>{}</span>",
|
||||
color,
|
||||
icon,
|
||||
)
|
||||
|
||||
|
||||
def format_link(url, label=None):
|
||||
return (
|
||||
format_html("<a href='{}' target='_blank'>{}</a>", url, label or url)
|
||||
if url
|
||||
else format_placeholder()
|
||||
)
|
||||
Reference in new issue
Block a user