Uso de widgets de fecha / hora de Django en forma personalizada


171

¿Cómo puedo usar los ingeniosos widgets de fecha y hora de JavaScript que el administrador predeterminado usa con mi vista personalizada?

He revisado la documentación de formularios de Django , y menciona brevemente django.contrib.admin.widgets, pero no sé cómo usarla.

Aquí está mi plantilla en la que quiero que se aplique.

<form action="." method="POST">
    <table>
        {% for f in form %}
           <tr> <td> {{ f.name }}</td> <td>{{ f }}</td> </tr>
        {% endfor %}
    </table>
    <input type="submit" name="submit" value="Add Product">
</form>

Además, creo que debe tenerse en cuenta que realmente no he escrito una vista para este formulario, estoy usando una vista genérica. Aquí está la entrada de url.py:

(r'^admin/products/add/$', create_object, {'model': Product, 'post_save_redirect': ''}),

Y soy significativamente nuevo en todo lo relacionado con Django / MVC / MTV, así que por favor, ve con calma ...



Respuestas:


159

La creciente complejidad de esta respuesta a lo largo del tiempo, y los muchos hacks requeridos, probablemente deberían advertirle de no hacerlo. Se basa en detalles de implementación internos no documentados del administrador, es probable que se rompa nuevamente en futuras versiones de Django, y no es más fácil de implementar que simplemente encontrar otro widget de calendario JS y usarlo.

Dicho esto, esto es lo que tienes que hacer si estás decidido a hacer que esto funcione:

  1. Defina su propia subclase ModelForm para su modelo (lo mejor es ponerlo en forms.py en su aplicación) y dígale que use AdminDateWidget / AdminTimeWidget / AdminSplitDateTime (reemplace 'mydate', etc. con los nombres de campo adecuados de su modelo):

    from django import forms
    from my_app.models import Product
    from django.contrib.admin import widgets                                       
    
    class ProductForm(forms.ModelForm):
        class Meta:
            model = Product
        def __init__(self, *args, **kwargs):
            super(ProductForm, self).__init__(*args, **kwargs)
            self.fields['mydate'].widget = widgets.AdminDateWidget()
            self.fields['mytime'].widget = widgets.AdminTimeWidget()
            self.fields['mydatetime'].widget = widgets.AdminSplitDateTime()
  2. Cambie su URLconf para pasar 'form_class': ProductForm en lugar de 'model': Producto a la vista genérica create_object (eso significará "from my_app.forms import ProductForm" en lugar de "from my_app.models import Product", por supuesto).

  3. En el encabezado de su plantilla, incluya {{form.media}} para generar los enlaces a los archivos Javascript.

  4. Y la parte hacky: los widgets de fecha / hora de administración suponen que el material i18n JS se ha cargado, y también requieren core.js, pero no proporcionan ninguno automáticamente. Por lo tanto, en su plantilla anterior {{form.media}} necesitará:

    <script type="text/javascript" src="/my_admin/jsi18n/"></script>
    <script type="text/javascript" src="/media/admin/js/core.js"></script>

    También es posible que desee utilizar el siguiente administrador CSS (gracias Alex por mencionar esto):

    <link rel="stylesheet" type="text/css" href="/media/admin/css/forms.css"/>
    <link rel="stylesheet" type="text/css" href="/media/admin/css/base.css"/>
    <link rel="stylesheet" type="text/css" href="/media/admin/css/global.css"/>
    <link rel="stylesheet" type="text/css" href="/media/admin/css/widgets.css"/>

Esto implica que los medios de administración de Django (ADMIN_MEDIA_PREFIX) están en / media / admin / - puede cambiar eso para su configuración. Lo ideal sería usar un procesador de contexto para pasar estos valores a su plantilla en lugar de codificarla, pero eso está fuera del alcance de esta pregunta.

Esto también requiere que la URL / my_admin / jsi18n / se conecte manualmente a la vista django.views.i18n.javascript_catalog (o null_javascript_catalog si no está usando I18N). Debe hacerlo usted mismo en lugar de pasar por la aplicación de administración para que sea accesible independientemente de si ha iniciado sesión en el administrador (gracias Jeremy por señalar esto). Código de muestra para su URLconf:

(r'^my_admin/jsi18n', 'django.views.i18n.javascript_catalog'),

Por último, si está utilizando Django 1.2 o posterior, necesita un código adicional en su plantilla para ayudar a los widgets a encontrar sus medios:

{% load adminmedia %} /* At the top of the template. */

/* In the head section of the template. */
<script type="text/javascript">
window.__admin_media_prefix__ = "{% filter escapejs %}{% admin_media_prefix %}{% endfilter %}";
</script>

Gracias lupefiasco por esta adición.


3
La publicación más útil en línea para hacer esto, pero después de que finalmente aparezca el widget de fecha / hora y llene el campo, ahora lo eliminaré porque a pesar de haber requerido = Falso en el campo del formulario, ahora muestra el mensaje "Ingrese una fecha / hora válida". para mi campo no requerido si lo dejo en blanco. De vuelta a jquery por mí.
PhoebeB

¿Es este el camino a seguir en Django 1.1.1?
Nacho

1
En Django 1.2 RC1 y versiones posteriores, debe realizar un pequeño cambio en lo anterior. Ver: stackoverflow.com/questions/38601/…
mshafrir

1
¿Sabes si esto todavía se aplica a Django 1.4? Aunque estoy convencido de ir con el selector de fechas de jQuery, tengo curiosidad por saber si el equipo de Django podría haber hecho que su widget esté más disponible públicamente. (Sospecho que no, ya que este control de calidad parece ser la mayor documentación)
John C

1
No ha habido ningún movimiento para hacer que los widgets de administrador sean más fácilmente reutilizables fuera del administrador, y no creo que haya ninguno. Sería mucho trabajo, y hay elementos de mayor prioridad para trabajar. (Soy parte del equipo central de Django, por cierto).
Carl Meyer

64

Como la solución es complicada, creo que usar su propio widget de fecha / hora con algo de JavaScript es más factible.


8
Estoy de acuerdo. Intenté hacer esto hace unos meses con mi proyecto django. Finalmente me di por vencido y acabo de agregar el mío con jQueryUI. Tomó todos los 10 minutos para que funcione.
priestc

8
Para el registro, estoy de acuerdo, he votado esta respuesta, y nunca he usado la técnica en mi respuesta en un sitio de producción ;-)
Carl Meyer

12

Sí, terminé anulando / admin / jsi18n / url.

Esto es lo que agregué en mi urls.py. Asegúrate de que esté encima de / admin / url

    (r'^admin/jsi18n', i18n_javascript),

Y aquí está la función i18n_javascript que creé.

from django.contrib import admin
def i18n_javascript(request):
  return admin.site.i18n_javascript(request)

Oh, muy buen punto. Agregaré esto a mi respuesta anterior para que sea más fácil de encontrar antes de que tengan problemas.
Carl Meyer

12

Me encuentro haciendo referencia a esta publicación mucho, y descubrí que la documentación define un poco menos hacky de anular los widgets predeterminados.

( No es necesario anular el método __init__ de ModelForm )

Sin embargo, aún necesita conectar su JS y CSS adecuadamente como Carl menciona.

formas.py

from django import forms
from my_app.models import Product
from django.contrib.admin import widgets                                       


class ProductForm(forms.ModelForm):
    mydate = forms.DateField(widget=widgets.AdminDateWidget)
    mytime = forms.TimeField(widget=widgets.AdminTimeWidget)
    mydatetime = forms.SplitDateTimeField(widget=widgets.AdminSplitDateTime)

    class Meta:
        model = Product

Tipos de campos de referencia para buscar los campos de formulario predeterminados.


55
No estoy de acuerdo con que este enfoque sea menos agresivo. Se ve mejor, claro, pero está anulando por completo los campos del formulario generados por el formulario del modelo, lo que también podría borrar las opciones de los campos del modelo, como help_text. Anular init solo cambia el widget y deja el resto del campo de formulario como estaba.
Carl Meyer

11

Mi código principal para la versión 1.4 (algunas nuevas y otras eliminadas)

{% block extrahead %}

<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/forms.css"/>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/base.css"/>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/global.css"/>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/widgets.css"/>

<script type="text/javascript" src="/admin/jsi18n/"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/core.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/admin/RelatedObjectLookups.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/jquery.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/jquery.init.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/actions.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/calendar.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/admin/DateTimeShortcuts.js"></script>

{% endblock %}

3
increíble . Estaba intentando esto de las últimas 2 semanas y esto me ayuda mucho gracias
numerah

usando {{STATIC_URL}} o {% load staticfiles%} con {% static 'admin / js / core.js'%} ... es necesario para Django 1.4+, ya que todo el espacio de nombres MEDIA_URL fue desaprobado y eliminado. / admin / jsi18n se resuelve correctamente si tiene ^ admin / mapeado en server / urls.py
jeberle

cosas buenas, útiles en el caso de Django 1.4
Ashish

10

A partir de Django 1.2 RC1, si está utilizando el truco del widget de selector de fecha de administración de Django, se debe agregar lo siguiente a su plantilla, o verá que la URL del ícono del calendario se hace referencia a través de "/ missing-admin-media-prefix / ".

{% load adminmedia %} /* At the top of the template. */

/* In the head section of the template. */
<script type="text/javascript">
window.__admin_media_prefix__ = "{% filter escapejs %}{% admin_media_prefix %}{% endfilter %}";
</script>

7

Complementando la respuesta de Carl Meyer, me gustaría comentar que necesita poner ese encabezado en algún bloque válido (dentro del encabezado) dentro de su plantilla.

{% block extra_head %}

<link rel="stylesheet" type="text/css" href="/media/admin/css/forms.css"/>
<link rel="stylesheet" type="text/css" href="/media/admin/css/base.css"/>
<link rel="stylesheet" type="text/css" href="/media/admin/css/global.css"/>
<link rel="stylesheet" type="text/css" href="/media/admin/css/widgets.css"/>

<script type="text/javascript" src="/admin/jsi18n/"></script>
<script type="text/javascript" src="/media/admin/js/core.js"></script>
<script type="text/javascript" src="/media/admin/js/admin/RelatedObjectLookups.js"></script>

{{ form.media }}

{% endblock %}


6

Para Django> = 2.0

Nota: el uso de widgets de administración para los campos de fecha y hora no es una buena idea, ya que las hojas de estilo de administración pueden entrar en conflicto con las hojas de estilo de su sitio en caso de que esté utilizando bootstrap o cualquier otro marco CSS. Si está construyendo su sitio en bootstrap, use mi widget bootstrap-datepicker django-bootstrap-datepicker-plus .

Paso 1: Agregue javascript-catalogURL al urls.pyarchivo de su proyecto (no de la aplicación) .

from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    path('jsi18n', JavaScriptCatalog.as_view(), name='javascript-catalog'),
]

Paso 2: Agregue los recursos JavaScript / CSS necesarios a su plantilla.

<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script type="text/javascript" src="{% static '/admin/js/core.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static '/admin/css/widgets.css' %}">
<style>.calendar>table>caption{caption-side:unset}</style><!--caption fix for bootstrap4-->
{{ form.media }}        {# Form required JS and CSS #}

Paso 3: use widgets de administrador para los campos de entrada de fecha y hora en su forms.py.

from django.contrib.admin import widgets
from .models import Product

class ProductCreateForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ['name', 'publish_date', 'publish_time', 'publish_datetime']
        widgets = {
            'publish_date': widgets.AdminDateWidget,
            'publish_time': widgets.AdminTimeWidget,
            'publish_datetime': widgets.AdminSplitDateTime,
        }

5

Lo siguiente también funcionará como último recurso si falla lo anterior

class PaymentsForm(forms.ModelForm):
    class Meta:
        model = Payments

    def __init__(self, *args, **kwargs):
        super(PaymentsForm, self).__init__(*args, **kwargs)
        self.fields['date'].widget = SelectDateWidget()

Igual que

class PaymentsForm(forms.ModelForm):
    date = forms.DateField(widget=SelectDateWidget())

    class Meta:
        model = Payments

pon esto en tus formularios.py from django.forms.extras.widgets import SelectDateWidget


1
me da el error 'El campo' DateField 'no tiene el atributo' needs_multipart_form '
madeeha ameer

3

¿Qué pasa con solo asignar una clase a su widget y luego vincular esa clase al selector de fechas JQuery?

Django forms.py:

class MyForm(forms.ModelForm):

  class Meta:
    model = MyModel

  def __init__(self, *args, **kwargs):
    super(MyForm, self).__init__(*args, **kwargs)
    self.fields['my_date_field'].widget.attrs['class'] = 'datepicker'

Y algunos JavaScript para la plantilla:

  $(".datepicker").datepicker();


1

Solución actualizada y solución alternativa para SplitDateTime con required = False :

formas.py

from django import forms

class SplitDateTimeJSField(forms.SplitDateTimeField):
    def __init__(self, *args, **kwargs):
        super(SplitDateTimeJSField, self).__init__(*args, **kwargs)
        self.widget.widgets[0].attrs = {'class': 'vDateField'}
        self.widget.widgets[1].attrs = {'class': 'vTimeField'}  


class AnyFormOrModelForm(forms.Form):
    date = forms.DateField(widget=forms.TextInput(attrs={'class':'vDateField'}))
    time = forms.TimeField(widget=forms.TextInput(attrs={'class':'vTimeField'}))
    timestamp = SplitDateTimeJSField(required=False,)

form.html

<script type="text/javascript" src="/admin/jsi18n/"></script>
<script type="text/javascript" src="/admin_media/js/core.js"></script>
<script type="text/javascript" src="/admin_media/js/calendar.js"></script>
<script type="text/javascript" src="/admin_media/js/admin/DateTimeShortcuts.js"></script>

urls.py

(r'^admin/jsi18n/', 'django.views.i18n.javascript_catalog'),

También he llenado un ticket para arreglar el código de widgets SplitDateTime.djangoproject.com/ticket/12303
Sam A.

1

Lo uso, es genial, pero tengo 2 problemas con la plantilla:

  1. Veo los iconos del calendario dos veces por cada plantilla presentada.
  2. Y para TimeField tengo ' Ingrese una fecha válida. '

    Aquí hay una captura de pantalla del formulario

modelos.py

from django.db import models
    name=models.CharField(max_length=100)
    create_date=models.DateField(blank=True)
    start_time=models.TimeField(blank=False)
    end_time=models.TimeField(blank=False)

formas.py

from django import forms
from .models import Guide
from django.contrib.admin import widgets

class GuideForm(forms.ModelForm):
    start_time = forms.DateField(widget=widgets.AdminTimeWidget)
    end_time = forms.DateField(widget=widgets.AdminTimeWidget)
    create_date = forms.DateField(widget=widgets.AdminDateWidget)
    class Meta:
        model=Guide
        fields=['name','categorie','thumb']

0

En Django 10. myproject / urls.py: al comienzo de urlpatterns

  from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'),
.
.
.]

En mi template.html:

{% load staticfiles %}

    <script src="{% static "js/jquery-2.2.3.min.js" %}"></script>
    <script src="{% static "js/bootstrap.min.js" %}"></script>
    {# Loading internazionalization for js #}
    {% load i18n admin_modify %}
    <script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/jquery.init.js" %}"></script>

    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/base.css" %}">
    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/forms.css" %}">
    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/login.css" %}">
    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/widgets.css" %}">



    <script type="text/javascript" src="{% static "/admin/js/core.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/SelectFilter2.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/admin/RelatedObjectLookups.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/actions.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/calendar.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/admin/DateTimeShortcuts.js" %}"></script>

0

Mi configuración de Django: 1.11 Bootstrap: 3.3.7

Como ninguna de las respuestas es completamente clara, comparto mi código de plantilla que no presenta ningún error.

Mitad superior de la plantilla:

{% extends 'base.html' %}
{% load static %}
{% load i18n %}

{% block head %}
    <title>Add Interview</title>
{% endblock %}

{% block content %}

<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/core.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
<script type="text/javascript" src="{% static 'js/jquery.js' %}"></script>

Mitad inferior:

<script type="text/javascript" src="/admin/jsi18n/"></script>
<script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/jquery.init.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/actions.min.js' %}"></script>
{% endblock %}

0

3 de junio de 2020 (Todas las respuestas no funcionaron, puedes probar esta solución que utilicé. Solo para TimeField)

Use Charfieldcampos simples para tiempo ( inicio y fin en este ejemplo) en formularios.

formas.py

podemos usar Formo ModelFormaquí.

class TimeSlotForm(forms.ModelForm):
    start = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'HH:MM'}))
    end = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'HH:MM'}))

    class Meta:
        model = TimeSlots
        fields = ('start', 'end', 'provider')

Convierta la entrada de cadena en objeto de tiempo en las vistas.

import datetime
def slots():
    if request.method == 'POST':
        form = create_form(request.POST)
        if form.is_valid():                
            slot = form.save(commit=False)
            start = form.cleaned_data['start']
            end = form.cleaned_data['end']
            start = datetime.datetime.strptime(start, '%H:%M').time()
            end = datetime.datetime.strptime(end, '%H:%M').time()
            slot.start = start
            slot.end = end
            slot.save()
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.