Używanie widżetów czasu / daty Django w niestandardowej formie

171

Jak mogę używać sprytnych widżetów daty i czasu JavaScript, których używa domyślny administrator w moim niestandardowym widoku?

Przejrzałem dokumentację formularzy Django i krótko wspomina o django.contrib.admin.widgets, ale nie wiem, jak go używać?

Oto mój szablon, na którym chcę go zastosować.

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

Myślę też, że należy zauważyć, że tak naprawdę nie napisałem osobiście widoku tego formularza, używam widoku ogólnego. Oto wpis z url.py:

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

Jestem całkiem nowy w całej sprawie Django / MVC / MTV, więc proszę, bądź spokojny ...

Josh Hunt
źródło

Odpowiedzi:

159

Rosnąca złożoność tej odpowiedzi z biegiem czasu i wiele wymaganych hacków prawdopodobnie powinno Cię przestrzec przed zrobieniem tego w ogóle. Opiera się na nieudokumentowanych wewnętrznych szczegółach implementacji administratora, prawdopodobnie ponownie się zepsuje w przyszłych wersjach Django i nie jest łatwiejszy do wdrożenia niż po prostu znalezienie innego widżetu kalendarza JS i użycie go.

To powiedziawszy, oto co musisz zrobić, jeśli jesteś zdeterminowany, aby to zadziałało:

  1. Zdefiniuj własną podklasę ModelForm dla swojego modelu (najlepiej umieścić ją w forms.py w swojej aplikacji) i powiedz mu, aby używał AdminDateWidget / AdminTimeWidget / AdminSplitDateTime (zastąp „mydate” itp. Odpowiednimi nazwami pól z Twojego modelu):

    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. Zmień adres URLconf, aby przekazywał „form_class”: ProductForm zamiast „model”: Product do ogólnego widoku create_object (co oczywiście będzie oznaczać „from my_app.forms import ProductForm” zamiast „from my_app.models import Product”, oczywiście).

  3. W nagłówku szablonu umieść {{form.media}}, aby wyświetlić linki do plików Javascript.

  4. I zepsuta część: widżety daty / czasu administratora zakładają, że elementy JS i18n zostały załadowane, a także wymagają core.js, ale nie dostarczają żadnego z nich automatycznie. Więc w swoim szablonie powyżej {{form.media}} będziesz potrzebować:

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

    Możesz także chcieć użyć następującego CSS administratora (dzięki Alex za wspomnienie o tym):

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

Oznacza to, że nośnik administratora Django (ADMIN_MEDIA_PREFIX) znajduje się w / media / admin / - możesz to zmienić dla swojej konfiguracji. Idealnie byłoby użyć procesora kontekstu do przekazania tych wartości do szablonu zamiast zakodowania go na sztywno, ale to wykracza poza zakres tego pytania.

Wymaga to również, aby adres URL / my_admin / jsi18n / był ręcznie połączony z widokiem django.views.i18n.javascript_catalog (lub null_javascript_catalog, jeśli nie używasz I18N). Musisz to zrobić samodzielnie, zamiast przechodzić przez aplikację administratora, aby była dostępna niezależnie od tego, czy jesteś zalogowany jako administrator (dzięki Jeremy za wskazanie tego). Przykładowy kod dla twojego URLconf:

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

Na koniec, jeśli używasz Django 1.2 lub nowszego, potrzebujesz dodatkowego kodu w swoim szablonie, aby pomóc widżetom znaleźć ich multimedia:

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

Dzięki lupefiasco za ten dodatek.

Carl Meyer
źródło
3
Najbardziej pomocny post online do zrobienia tego, ale po pojawieniu się widżetu daty / czasu i wypełnieniu pola, zamierzam go teraz usunąć, ponieważ pomimo tego, że pole wymagane = Fałsz w polu formularza wyświetla teraz komunikat „Wprowadź prawidłową datę / godzinę”. dla mojego niewymaganego pola, jeśli pozostawię je puste. Wróć do jQuery dla mnie.
PhoebeB
Czy to jest droga do Django 1.1.1?
Nacho
1
W Django 1.2 RC1 i późniejszych musisz dokonać niewielkiej zmiany w powyższym. Zobacz: stackoverflow.com/questions/38601/…
mshafrir
1
Czy wiesz, czy to nadal dotyczy Django 1.4? Chociaż jestem przekonany, że wybiorę datepicker jQuery, jestem ciekawy, czy zespół Django mógł upublicznić swój widget. (Podejrzewam, że nie, ponieważ ta kontrola jakości wydaje się być najbardziej dokumentową wokół)
John C
1
Nie było ruchu w kierunku ułatwienia ponownego wykorzystania widżetów administratora poza administratorem i nie przypuszczałbym, że takie będą. Wymagałoby to dużo pracy, a trzeba popracować nad elementami o znacznie wyższym priorytecie. (Nawiasem mówiąc, jestem częścią podstawowego zespołu Django).
Carl Meyer,
64

Ponieważ rozwiązanie jest hakerskie, myślę, że użycie własnego widżetu daty / czasu z niektórymi JavaScript jest bardziej wykonalne.

zgoda
źródło
8
Zgadzam się. Próbowałem to zrobić kilka miesięcy temu z moim projektem django. W końcu zrezygnowałem i po prostu dodałem własne z jQueryUI. Zajęło to całe 10 minut, aby to działało.
ksiądz
8
Dla porządku, zgadzam się, zagłosowałem za tą odpowiedzią i tak naprawdę nigdy nie użyłem tej techniki w mojej odpowiedzi na miejscu produkcji ;-)
Carl Meyer
12

Tak, w końcu nadpisałem / admin / jsi18n / url.

Oto, co dodałem w moim urls.py. Upewnij się, że znajduje się nad / admin / url

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

A oto funkcja i18n_javascript, którą stworzyłem.

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

źródło
Och, bardzo dobra uwaga. Dodam to do mojej odpowiedzi powyżej, aby ułatwić ludziom znalezienie tego, zanim będą mieli problemy.
Carl Meyer,
12

Często odwołuję się do tego posta i stwierdziłem, że dokumentacja definiuje nieco mniej hakerski sposób na zastąpienie domyślnych widżetów.

( Nie ma potrzeby nadpisywania metody __init__ ModelForm )

Jednak nadal musisz odpowiednio połączyć JS i CSS, jak wspomina Carl.

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

Odwołaj się do typów pól, aby znaleźć domyślne pola formularza.

monkut
źródło
5
Nie zgadzam się, że to podejście jest mniej hakerskie. Oczywiście, wygląda to ładniej, ale całkowicie nadpisujesz pola formularza wygenerowane przez formularz modelu, co może również usunąć opcje z pól modelu, takie jak help_text. Zastąpienie init zmienia tylko widżet i pozostawia resztę pola bez zmian.
Carl Meyer
11

Mój kod głowy dla wersji 1.4 (niektóre nowe i niektóre usunięte)

{% 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
źródło
3
niesamowite . Próbowałem tego od ostatnich 2 tygodni, a to pomogło mi dzięki alot
numerah
używanie {{STATIC_URL}} lub {% load staticfiles%} z {% static 'admin / js / core.js'%} ... jest wymagane dla Django 1.4+, ponieważ cała przestrzeń nazw MEDIA_URL została wycofana i usunięta. / admin / jsi18n rozwiązuje się poprawnie, jeśli masz ^ admin / mapped in server / urls.py
jeberle
fajne rzeczy, przydatne w przypadku Django 1.4
Ashish
10

Począwszy od Django 1.2 RC1, jeśli używasz sztuczki z widmem wyboru daty administratora Django, do szablonu należy dodać następujące elementy lub zobaczysz, że adres URL ikony kalendarza jest wskazywany przez „/ 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
źródło
7

Uzupełniając odpowiedź Carla Meyera, chciałbym skomentować, że musisz umieścić ten nagłówek w jakimś prawidłowym bloku (wewnątrz nagłówka) w swoim szablonie.

{% 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.
źródło
Przydatny okazał się również ten link: groups.google.ca/group/django-users/msg/1a40cf5f153cd23e
Alex. S.
6

Dla Django> = 2.0

Uwaga: używanie widżetów administratora dla pól daty i czasu nie jest dobrym pomysłem, ponieważ arkusze stylów administratora mogą kolidować z arkuszami stylów witryny, jeśli używasz bootstrap lub innych frameworków CSS. Jeśli budujesz swoją witrynę na bootstrapie, użyj mojego widgetu bootstrap-datepicker django-bootstrap-datepicker-plus .

Krok 1: Dodaj javascript-catalogadres URL do urls.pypliku projektu (nie aplikacji) .

from django.views.i18n import JavaScriptCatalog

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

Krok 2: Dodaj wymagane zasoby JavaScript / CSS do szablonu.

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

Krok 3: Użyj widżetów administratora do pól wprowadzania daty i godziny w pliku 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
źródło
5

Poniższe będzie również działać w ostateczności, jeśli powyższe zawiodło

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

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

Taki sam jak

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

    class Meta:
        model = Payments

umieść to w swoim forms.py from django.forms.extras.widgets import SelectDateWidget

Dennis Kioko
źródło
1
daje mi błąd Obiekt „DateField” nie ma atrybutu „need_multipart_form”
madeeha ameer
3

A co powiesz na przypisanie klasy do widżetu, a następnie powiązanie tej klasy z datownikiem 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'

I trochę JavaScript do szablonu:

  $(".datepicker").datepicker();
trubliphone
źródło
1

Zaktualizowane rozwiązanie i obejście dla SplitDateTime z 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.
źródło
Wypełniłem również zgłoszenie, aby naprawić kod widżetów SplitDateTime. Djangoproject.com/ticket/12303
Sam A.,
1

Używam tego, jest super, ale mam 2 problemy z szablonem:

  1. Widzę ikony kalendarza dwukrotnie dla każdego w szablonie.
  2. A dla TimeField mam „ Wprowadź prawidłową datę”. '

    Oto zrzut ekranu formularza

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']
potemkin
źródło
0

W Django 10. myproject / urls.py: na początku wzorców url

  from django.views.i18n import JavaScriptCatalog

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

W moim 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>
Jose Luis Quichimbo
źródło
0

Moja konfiguracja Django: 1.11 Bootstrap: 3.3.7

Ponieważ żadna z odpowiedzi nie jest całkowicie jasna, udostępniam kod szablonu, który nie przedstawia żadnych błędów.

Górna połowa szablonu:

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

Dolna połowa:

<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
źródło
0

3 czerwca 2020 r. (Wszystkie odpowiedzi nie zadziałały, możesz wypróbować to rozwiązanie, którego użyłem. Tylko dla TimeField)

Użyj prostych Charfieldpól czasu ( początek i koniec w tym przykładzie) w formularzach.

forms.py

możemy użyć Formlub ModelFormtutaj.

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

Konwertuj ciąg wejściowy na obiekt czasu w widokach.

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
źródło