Reputation: 2801
I have three fields of type DateField
, what I want is to change its appearance in the admin if the value of that field is a specific date without repeating the same method to check the value of each field and change their appearance(DRY).
class Case(TimeStampedModel, models.Model):
fulfillment = models.DateField(default=date.today)
caducity = models.DateField(default=date.today)
prescription = models.DateField(default=date.today)
# With this approach I need to repeat this method to check the value for every field
def is_prescripted(self):
"""Check if one case is prescripted."""
if self.prescription == date.today():
return True
return False
Admin
from django.contrib import admin
from .models import Case
@admin.register(Case)
class CaseAdmin(admin.ModelAdmin):
list_display = (
"_fulfillment",
"_caducity",
"_prescription",
)
# With this approach I need to repeat this method to change the appearance for every field
def _prescription(self, obj: Case) -> str:
"""Render a red badge alert in the admin for cases that are prescribed."""
date = formats.date_format(obj.prescription)
if obj.is_prescripted():
return create_badge(text=date)
return date
class CaseManager(models.Manager):
def expired(self) -> "QuerySet[Case]":
"""Get all fulfilled, caducated and prescribed cases."""
return self.get_queryset().filter(
Q(fulfillment=get_today())
| Q(caducity=get_today())
| Q(prescription=get_today())
)
Model
class Case(TimeStampedModel, models.Model):
# ... model fileds
objects = CaseManager()
Admin
from .models import Case
@admin.register(Case)
class CaseAdmin(admin.ModelAdmin):
list_display = (
"_fulfillment",
"_caducity",
"_prescription",
)
# With this approach I need to repeat this method to change the appearance for every field
def _prescription(self, obj: Case) -> str:
"""Render a red badge alert in the admin for cases that are prescribed."""
date = formats.date_format(obj.prescription)
for prescribed_case in Case.objects.prescribed():
if prescribed_case == obj:
return create_badge(text=date)
return date
Helper functions
This function is used to print a red bagde in the admin for dates that meet a condition.
from django.utils.html import format_html
def create_badge(
text: str = "", bg_color: str = "tomato", color: str = "white", padding: str = "2"
) -> str:
"""Create a css badge style for some text."""
badge = (
f"<span style='background-color: {bg_color};"
f"color: {color}; padding: {padding}px;"
"white-space: nowrap;'"
f">{text}</span>"
)
return f"{format_html(badge)}"
First approach: The problem with the first approach is that I need to create three identical model and admin methods to check the value for every field and to change their appearance in the admin.
Second approach: With this approach I solve the problem of repeating three model methods to check the value of each field, but in the admin I need to compare all the values thrown by the method manager with the values of the instances of the model for every field which is inefficient. and there is still the problem of changing the appearance for each field in the admin, just like first approach, I should repeat the same method for each field to change its appearance.
Upvotes: 1
Views: 953
Reputation: 550
Maybe a third approach: override/replace admindatewidget for datefield.
For each field that django displays in an admin form, a widget is used. For a datefield type field, the AdminDateWiget is used (site-packages \ django \ contrib \ admin \ AdminDateWiget) which extends another widget:
class DateInput(DateTimeBaseInput):
format_key = 'DATE_INPUT_FORMATS'
template_name = 'django/forms/widgets/date.html'
As you can see, default template is date.html:
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
Good news, You can change the meta and replace template for a specific model.
Two steps:
1st : You need to create a new html. I copy paste date.html and add logic to compare the date value.
spedate.html :
{% now "Y-m-d" as current_time %}
{% if widget.value == current_time %}
<input type="{{ widget.type }}" class='my css' name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
{% else %}
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
{% endif %}
2nd : link your new template
Create a new class in admin.py and read the docs
admin.py
class CustomerAdmin(admin.ModelAdmin):
template_name = "app_name/.../spedate.html"
formfield_overrides = {
models.DateField: { 'widget': AdminDateWidget },
}
admin.site.register(Case,CustomerAdmin)
Upvotes: 3