Django personalizar el campo Datetime en el panel administrativo | Programador Web Valencia

Django personalizar el campo Datetime en el panel administrativo

3 minutos

Django

El selector de fechas y horas, o datepicker, que muestra por defecto el panel administrativo de Django es bastante bueno, pero ocasionalmente necesitamos ir más allá. Por supuesto que existe la posibilidad de personalizar cualquier campo en Django, pero DateTime es especial, ya que requiere de un campo de fecha y otro de hora. Requiere algunos pasos extra en el caso de que busquemos adaptar a nuestras necesidades, o rehacerlo integrando librerías de JavaScript (posiblemente la situación más habitual).

El objetivo del tutorial será crear una plantilla del campo DateTime para incluir un HTML, CSS o JavaScript a medida. Como ejemplo podéis visualizar el resultado final con el plugin air-datepicker, un datepicker ligero realizado en JavaScript.

Demo con calendario

Demo con time

¡Empecemos!

1. Modificar la configuración de los formularios

Para sobrescribir los widgets de Django necesitamos modificar la configuración de los formularios. En settings.py añadimos la app django.forms y la opción FORM_RENDERER:


INSTALLED_APPS = [
    ...
    "django.forms", # Nuevo
    ...
]

FORM_RENDERER = "django.forms.renderers.TemplatesSetting" # Nuevo

2. Crear filtros personalizados

Cuando estemos trabajando con la plantilla, nos indicará que su valor por defecto en string con el formato YYYY-MM-DD HH:MM:SS (por ejemplo 2021-08-31 12:00:00), pero nosotros queremos separarlo en dos campos, uno para la fecha y otro para el tiempo. Para ello necesitamos crear un par de filtros personalizados con el objetivo de convertirlos en date o time.

Creamos un archivo custom_filters.py, que puede estar en cualquier directorio.


from django import template
from datetime import datetime

register = template.Library()


@register.filter
def string_to_date(value):
    my_datetime = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
    return my_datetime.strftime("%Y-%m-%d")

@register.filter
def string_to_time(value):
    my_datetime = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
    return my_datetime.strftime("%H:%M:%S")

Y lo registramos en settings.py.


TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
            "libraries": {
                "custom_filters": "custom_filters", # Nuevo
            },
        },
    },
]

3. Crear plantilla

Es el momento de crear la plantilla HTML con el código que queremos que se muestre en el panel administrativo.

En el directorio de templates crea datetime.html con el siguiente contenido:


{% load custom_filters %}
<!-- custom widget: datetime -->
<div class="custom-datetime-widget">
    <label>
        Día
        <input id="id_{{ widget.name }}_date" type="{{ widget.type }}" name="{{ widget.name }}_0" value="{% if widget.value %}{{ widget.value|string_to_date }}{% else %}{% endif %}" class="vTextField {{ widget.attrs.class }}" autocomplete="off"{% include "django/forms/widgets/attrs.html" %}>
    </label>
    <label>
        Hora
        <input id="id_{{ widget.name }}_time" type="{{ widget.type }}" name="{{ widget.name }}_1" value="{% if widget.value %}{{ widget.value|string_to_time }}{% else %}{% endif %}" class="vTextField {{ widget.attrs.class }}" autocomplete="off"{% include "django/forms/widgets/attrs.html" %}>
    </label>
</div>

<script>
    const value = {% if widget.value %}new Date("{{ widget.value }}"){% else %}new Date(){% endif %};
    const dateDOM = document.querySelector("#id_{{ widget.name }}_date");
    const timeDOM = document.querySelector("#id_{{ widget.name }}_time");
</script>

Utiliza el código anterior como base para crear tu propio widget. En este caso, se compone de 2 campos input. Además, he añadido un id a cada campo para poder acceder a ellos desde JavaScript (los cuales he puesto un ejemplo dentro de <script>).

4. Sustituir el campo nativo por el nuestro

Crea un archivo llamado widgets.py en el directorio de la app y añade el siguiente código:


from django.contrib.admin import widgets


class CustomDateTimeWidget(widgets.AdminSplitDateTime):
    template_name = "datetime.html"

Ahora editaremos el archivo admin.py de la app.

Importamos el widget y lo añadimos a la clase CustomAdminForm. No olvides importar el modelo que necesites utilizar, en mi ejemplo usaré Product.


from .widgets import CustomDateTimeWidget

class CustomAdminForm(forms.ModelForm):
    class Meta:
        model = Product
        widgets = {
            "start": CustomDateTimeWidget,
            "finish": CustomDateTimeWidget,
        }
        fields = "__all__"

En la primera línea hemos importado el widget, a continuación lo hemos creado la clase CustomAdminForm que hereda de forms.ModelForm. En la clase Meta hemos añadido el modelo Product junto a los campos start y finish que queremos personalizar.

Por último, en el mismo archivo donde nos encontramos (admin.py), sobrescribimos el atributo form de la clase ProductAdmin (en tu caso puede que tenga otro nombre) con nuestro formulario personalizado CustomAdminForm.


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    form = CustomAdminForm
    ...

Algunos datos de interés

Cuando importes código CSS o JavaScript en la plantilla, recuerda que debes hacerlo con la etiqueta {% static %} si buscas que sea global.


<link rel="stylesheet" href="{% static "css/air-datepicker.min.css" %}">
<script src="{% static "js/air-datepicker.min.js" %}"></script>

Pero, en caso de no ser necesario, puedes incluirlo únicamente a la página donde se encuentra el formulario personalizado con Media:


from .widgets import CustomDateTimeWidget

class CustomAdminForm(forms.ModelForm):

    class Meta:
        model = Product
        widgets = {
            "start": CustomDateTimeWidget,
            "finish": CustomDateTimeWidget,
        }
        fields = "__all__"

	class Media:
		css = {
            "all": ("admin_custom.css",)
        }
        js = ("admin_custom.js",)

Espero que te haya sido de utilidad. ¡Hasta la próxima!

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me ayudas?

Comprame un café
Pulsa sobre la imagen

No te sientas obligado a realizar una donación, pero cada aportación mantiene el sitio en activo logrando que continúe existiendo y sea accesible para otras personas. Además me motiva a crear nuevo contenido.

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario

Tal vez también te interese...