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.
¡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!
{{ comments.length }} comentarios