Cristian Flórez
Cristian Flórez

Reputation: 2801

How to change the appearance of some similar fields in Django admin according some condition

What I want

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).

What I've tried

1. First approach: use a model method to checks the field value

Model
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

2. Second approach: use a manager method to checks the value from all fields at the same time

Model Manager
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)}"

The problem

  1. 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.

  2. 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

Answers (1)

Eric Martin
Eric Martin

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

https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_overrides

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

Related Questions