Utilisation des widgets d'heure / date Django sous forme personnalisée

171

Comment puis-je utiliser les gadgets JavaScript de date et d'heure astucieux que l'administrateur par défaut utilise avec ma vue personnalisée?

J'ai parcouru la documentation des formulaires Django , et elle mentionne brièvement django.contrib.admin.widgets, mais je ne sais pas comment l'utiliser?

Voici mon modèle sur lequel je veux qu'il soit appliqué.

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

Aussi, je pense qu'il convient de noter que je n'ai pas vraiment écrit moi-même une vue pour ce formulaire, j'utilise une vue générique. Voici l'entrée de url.py:

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

Et je suis tout à fait nouveau dans tout Django / MVC / MTV, alors allez-y doucement ...

Josh Hunt
la source

Réponses:

159

La complexité croissante de cette réponse au fil du temps et les nombreux hacks requis devraient probablement vous mettre en garde contre le fait de faire cela. Il repose sur des détails d'implémentation internes non documentés de l'administrateur, risque de se rompre à nouveau dans les futures versions de Django, et n'est pas plus facile à implémenter que de simplement trouver un autre widget de calendrier JS et de l'utiliser.

Cela dit, voici ce que vous devez faire si vous êtes déterminé à faire en sorte que cela fonctionne:

  1. Définissez votre propre sous-classe ModelForm pour votre modèle (il est préférable de la mettre dans forms.py dans votre application) et dites-lui d'utiliser AdminDateWidget / AdminTimeWidget / AdminSplitDateTime (remplacez 'mydate', etc. par les noms de champ appropriés de votre modèle):

    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. Modifiez votre URLconf pour passer 'form_class': ProductForm au lieu de 'model': Product à la vue générique create_object (cela signifiera "from my_app.forms import ProductForm" au lieu de "from my_app.models import Product", bien sûr).

  3. Dans la tête de votre modèle, incluez {{form.media}} pour afficher les liens vers les fichiers Javascript.

  4. Et la partie hacky: les widgets de date / heure d'administration supposent que le contenu de l'i18n JS a été chargé, et nécessitent également core.js, mais ne fournissent ni l'un ni l'autre automatiquement. Donc, dans votre modèle ci-dessus {{form.media}}, vous aurez besoin de:

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

    Vous pouvez également utiliser le CSS d'administration suivant (merci Alex d' avoir mentionné cela):

    <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"/>

Cela implique que le support d'administration de Django (ADMIN_MEDIA_PREFIX) se trouve dans / media / admin / - vous pouvez changer cela pour votre configuration. Idéalement, vous utiliseriez un processeur de contexte pour transmettre ces valeurs à votre modèle au lieu de le coder en dur, mais cela dépasse le cadre de cette question.

Cela nécessite également que l'URL / my_admin / jsi18n / soit câblée manuellement à la vue django.views.i18n.javascript_catalog (ou null_javascript_catalog si vous n'utilisez pas I18N). Vous devez le faire vous-même au lieu de passer par l'application d'administration afin qu'elle soit accessible, que vous soyez connecté ou non à l'administrateur (merci Jeremy de l' avoir signalé). Exemple de code pour votre URLconf:

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

Enfin, si vous utilisez Django 1.2 ou une version ultérieure, vous avez besoin d'un code supplémentaire dans votre modèle pour aider les widgets à trouver leur média:

{% 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>

Merci lupefiasco pour cet ajout.

Carl Meyer
la source
3
Le message le plus utile en ligne pour ce faire, mais après avoir enfin obtenu le widget date / heure et le champ de remplissage, je vais maintenant le supprimer car malgré le fait d'avoir required = False dans le champ du formulaire, il affiche maintenant le message "Entrez une date / heure valide." pour mon champ non obligatoire si je le laisse vide. Revenons à jquery pour moi.
PhoebeB
Est-ce la manière de procéder sur Django 1.1.1?
Nacho
1
Dans Django 1.2 RC1 et versions ultérieures, vous devez apporter une petite modification à ce qui précède. Voir: stackoverflow.com/questions/38601/…
mshafrir
1
Savez-vous si cela s'applique toujours à Django 1.4? Bien que je sois convaincu d'aller avec le sélecteur de date de jQuery, je suis curieux de savoir si l'équipe Django aurait pu rendre son widget plus accessible au public. (Je soupçonne que non, car ce contrôle qualité semble être le plus documenté)
John C
1
Il n'y a eu aucun mouvement pour rendre les widgets d'administration plus facilement réutilisables en dehors de l'administrateur, et je ne suppose pas qu'il y en aura. Ce serait beaucoup de travail et il y a des éléments beaucoup plus prioritaires sur lesquels travailler. (Je fais partie de l'équipe principale de Django, btw).
Carl Meyer
64

Comme la solution est hackish, je pense que l'utilisation de votre propre widget date / heure avec du JavaScript est plus faisable.

zgoda
la source
8
Je suis d'accord. J'ai essayé de le faire il y a quelques mois avec mon projet django. J'ai finalement abandonné et j'ai ajouté le mien avec jQueryUI. Il a fallu 10 minutes pour le faire fonctionner.
Priestc
8
Pour mémoire, je suis d'accord, j'ai voté pour cette réponse, et je n'ai jamais vraiment utilisé la technique dans ma réponse dans un site de production ;-)
Carl Meyer
12

Oui, j'ai fini par remplacer le / admin / jsi18n / url.

Voici ce que j'ai ajouté dans mes urls.py. Assurez-vous qu'il est au-dessus de / admin / url

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

Et voici la fonction i18n_javascript que j'ai créée.

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

la source
Oh, très bon point. J'ajouterai ceci à ma réponse ci-dessus afin qu'il soit plus facile pour les gens de trouver avant d'avoir des problèmes.
Carl Meyer
12

Je me retrouve souvent à faire référence à cet article et j'ai trouvé que la documentation définit un peu moins pirate de remplacer les widgets par défaut.

( Pas besoin de remplacer la méthode __init__ de ModelForm )

Cependant, vous devez toujours câbler votre JS et CSS de manière appropriée, comme Carl le mentionne.

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

Référencez les types de champs pour trouver les champs de formulaire par défaut.

monkut
la source
5
Je ne suis pas d'accord pour dire que cette approche est moins hackish. Cela a l'air plus joli, bien sûr, mais vous remplacez complètement les champs de formulaire générés par le formulaire de modèle, ce qui pourrait également effacer des options des champs de modèle, comme help_text. Le remplacement de init ne modifie que le widget et laisse le reste du champ de formulaire tel quel.
Carl Meyer
11

Mon code de tête pour la version 1.4 (certains nouveaux et certains supprimés)

{% 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 %}
Romamo
la source
3
impressionnant . J'essayais ceci des 2 dernières semaines et cela m'aide beaucoup merci
numéro
l'utilisation de {{STATIC_URL}} ou {% load staticfiles%} avec {% static 'admin / js / core.js'%} ... est requise pour Django 1.4+, car tout l'espace de noms MEDIA_URL a été abandonné et supprimé. le / admin / jsi18n se résout correctement si vous avez ^ admin / mappé dans server / urls.py
jeberle
trucs sympas, utiles dans le cas de Django 1.4
Ashish
10

À partir de Django 1.2 RC1, si vous utilisez l'astuce du sélecteur de date d'administration Django, ce qui suit doit être ajouté à votre modèle, ou vous verrez l'url de l'icône de calendrier référencée via "/ 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>
mshafrir
la source
7

En complément de la réponse de Carl Meyer, je voudrais faire remarquer que vous devez mettre cet en-tête dans un bloc valide (à l'intérieur de l'en-tête) dans votre modèle.

{% 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 %}
Alex. S.
la source
J'ai également trouvé ce lien utile: groups.google.ca/group/django-users/msg/1a40cf5f153cd23e
Alex. S.
6

Pour Django> = 2.0

Remarque: l'utilisation de widgets d'administration pour les champs date-heure n'est pas une bonne idée car les feuilles de style d'administration peuvent entrer en conflit avec les feuilles de style de votre site au cas où vous utilisez bootstrap ou tout autre framework CSS. Si vous construisez votre site sur bootstrap, utilisez mon widget bootstrap-datepicker django-bootstrap-datepicker-plus .

Étape 1: ajoutez une javascript-catalogURL au urls.pyfichier de votre projet (pas de l'application) .

from django.views.i18n import JavaScriptCatalog

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

Étape 2: Ajoutez les ressources JavaScript / CSS requises à votre modèle.

<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 #}

Étape 3: utilisez les widgets d'administration pour les champs de saisie de date et d'heure dans votre 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,
        }
Munim Munna
la source
5

Ce qui suit fonctionnera également en dernier recours si ce qui précède a échoué

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

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

Pareil que

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

    class Meta:
        model = Payments

mettez ceci dans vos forms.py from django.forms.extras.widgets import SelectDateWidget

Dennis Kioko
la source
1
il me donne une erreur 'DateField' l'objet n'a pas d'attribut 'needs_multipart_form'
madeeha ameer
3

Qu'en est-il simplement d'assigner une classe à votre widget, puis de lier cette classe au sélecteur de date 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'

Et du JavaScript pour le modèle:

  $(".datepicker").datepicker();
trubliphone
la source
1

Solution mise à jour et solution de contournement pour SplitDateTime avec required = False :

forms.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'),
Sam A.
la source
J'ai également rempli un ticket pour corriger le code des widgets SplitDateTime.djangoproject.com/ticket/12303
Sam A.
1

J'utilise ça, c'est super, mais j'ai 2 problèmes avec le template:

  1. Je vois les icônes de calendrier deux fois pour chaque fichier de modèle.
  2. Et pour TimeField, j'ai « Entrez une date valide. '

    Voici une capture d'écran du formulaire

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

forms.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']
potemkine
la source
0

Dans Django 10. myproject / urls.py: au début des urlpatterns

  from django.views.i18n import JavaScriptCatalog

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

Dans mon 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>
José Luis Quichimbo
la source
0

Ma configuration Django: 1.11 Bootstrap: 3.3.7

Comme aucune des réponses n'est complètement claire, je partage mon code de modèle qui ne présente aucune erreur.

Moitié supérieure du modèle:

{% 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>

Moitié inférieure:

<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 %}
Arindam Roychowdhury
la source
0

3 juin 2020 (Toutes les réponses n'ont pas fonctionné, vous pouvez essayer cette solution que j'ai utilisée. Juste pour TimeField)

Utilisez des Charfieldchamps simples pour l'heure ( début et fin dans cet exemple) dans les formulaires.

forms.py

nous pouvons utiliser Formou ModelFormici.

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

Convertit l'entrée de chaîne en objet de temps dans les vues.

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()
sandeep
la source