Usando widgets de data / hora do Django no formulário personalizado

171

Como posso usar os widgets legais de data e hora do JavaScript que o administrador padrão usa na minha exibição personalizada?

Eu examinei a documentação dos formulários do Django e menciona brevemente django.contrib.admin.widgets, mas não sei como usá-lo?

Aqui está o meu modelo no qual eu quero que ele seja aplicado.

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

Além disso, acho que deve ser observado que eu realmente não escrevi uma visualização para este formulário, estou usando uma visualização genérica. Aqui está a entrada do url.py:

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

E eu sou relevante para toda a coisa do Django / MVC / MTV, então por favor, vá com calma ...

Josh Hunt
fonte

Respostas:

159

A crescente complexidade dessa resposta ao longo do tempo, e os muitos hacks necessários, provavelmente devem alertá-lo contra isso. Ele conta com detalhes de implementação interna não documentada do administrador, provavelmente quebrará novamente em versões futuras do Django, e não é mais fácil de implementar do que apenas encontrar outro widget de calendário JS e usá-lo.

Dito isto, eis o que você deve fazer se estiver determinado a fazer isso funcionar:

  1. Defina sua própria subclasse ModelForm para o seu modelo (melhor para colocá-lo em forms.py no seu aplicativo) e diga-lhe para usar o AdminDateWidget / AdminTimeWidget / AdminSplitDateTime (substitua 'mydate' etc. pelos nomes de campo adequados do seu 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. Altere seu URLconf para passar 'form_class': ProductForm em vez de 'model': Product para a visualização create_object genérica (que significa "de my_app.forms import ProductForm" em vez de "de my_app.models import Product", é claro).

  3. Na cabeça do seu modelo, inclua {{form.media}} para gerar os links para os arquivos Javascript.

  4. E a parte hacky: os widgets de data / hora do administrador presumem que o material do i18n JS foi carregado e também exigem o core.js, mas não fornecem nenhum dos dois automaticamente. Portanto, no seu modelo acima {{form.media}}, você precisará:

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

    Você também pode usar o seguinte CSS de administrador (obrigado Alex por mencionar isso):

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

Isso implica que a mídia administrativa do Django (ADMIN_MEDIA_PREFIX) está em / media / admin / - você pode mudar isso para sua configuração. Idealmente, você usaria um processador de contexto para transmitir esses valores ao seu modelo em vez de codificá-lo, mas isso está além do escopo desta pergunta.

Isso também requer que a URL / my_admin / jsi18n / seja conectada manualmente à visualização django.views.i18n.javascript_catalog (ou null_javascript_catalog se você não estiver usando I18N). Você precisa fazer isso sozinho, em vez de acessar o aplicativo de administração, para que fique acessível, independentemente de estar conectado ao administrador (obrigado Jeremy por apontar isso). Código de exemplo para o seu URLconf:

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

Por fim, se você estiver usando o Django 1.2 ou posterior, precisará de algum código adicional no seu modelo para ajudar os widgets a encontrar sua 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>

Obrigado lupefiasco por esta adição.

Carl Meyer
fonte
3
A publicação mais útil on-line para fazer isso, mas finalmente tendo o widget de data / hora aparecendo e preenchendo o campo, agora vou removê-lo porque, apesar de ter exigido = False no campo do formulário, agora mostra a mensagem "Digite uma data / hora válida". para o meu campo não obrigatório, se eu o deixar em branco. De volta ao jquery para mim.
PhoebeB
É este o caminho a seguir no Django 1.1.1?
Nacho
1
No Django 1.2 RC1 e posterior, você precisa fazer uma pequena alteração no item acima. Veja: stackoverflow.com/questions/38601/…
mshafrir
1
Você sabe se isso ainda se aplica ao Django 1.4? Embora eu esteja convencido de usar o datepicker do jQuery, estou curioso para saber se a equipe do Django pode ter disponibilizado seu widget mais publicamente. (Suspeito que não, pois isso QA parece ser o mais em torno documentação)
John C
1
Não houve nenhum movimento no sentido de tornar os widgets de administração mais facilmente reutilizáveis ​​fora do administrador, e eu não acho que haverá algum. Seria muito trabalho, e há itens de prioridade muito mais altos para trabalhar. (Eu faço parte da equipe principal do Django, btw).
Carl Meyer
64

Como a solução é imprudente, acho que usar seu próprio widget de data / hora com algum JavaScript é mais viável.

zgoda
fonte
8
Concordo. Eu tentei fazer isso há alguns meses atrás com o meu projeto django. Acabei desistindo e apenas adicionei o meu com o jQueryUI. Levou 10 minutos para fazê-lo funcionar.
priestc
8
Para o registro, eu concordo, eu upvoted esta resposta, e eu nunca realmente usou a técnica em minha resposta em um local de produção ;-)
Carl Meyer
12

Sim, acabei substituindo o / admin / jsi18n / url.

Aqui está o que eu adicionei em meus urls.py. Verifique se está acima do / admin / url

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

E aqui está a função i18n_javascript que criei.

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

fonte
Oh, muito bom ponto. Acrescentarei isso à minha resposta acima, para que as pessoas encontrem mais facilmente antes que tenham problemas.
10119 Carl Meyer
12

Eu me pego consultando bastante este post e descobri que a documentação define uma maneira um pouco menos invasiva de substituir os widgets padrão.

( Não é necessário substituir o método __init__ do ModelForm )

No entanto, você ainda precisa conectar seu JS e CSS adequadamente, como Carl menciona.

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

Tipos de campos de referência para encontrar os campos de formulário padrão.

monkut
fonte
5
Não concordo que essa abordagem seja menos tola. Parece melhor, com certeza, mas você está substituindo completamente os campos do formulário gerados pelo modelo, o que também pode apagar opções dos campos do modelo, como help_text. A substituição de init altera apenas o widget e deixa o restante do campo do formulário como estava.
22711 Carl Meyer
11

Meu código principal para a versão 1.4 (algumas novas e outras removidas)

{% 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
fonte
3
impressionante . eu estava tentando isso a partir últimos 2 semanas e este ajudar-me muito obrigado
numerah
usar {{STATIC_URL}} ou {% load staticfiles%} com {% static 'admin / js / core.js'%} ... é necessário para o Django 1.4+, pois todo o espaço de nomes MEDIA_URL foi descontinuado e removido. o / admin / jsi18n resolve adequadamente se você tem ^ admin / mapeado no servidor / urls.py
jeberle
coisas legais, úteis no caso de Django 1.4
Ashish 10/09
10

A partir do Django 1.2 RC1, se você estiver usando o truque de seleção de data de administração do Django, o seguinte deverá ser adicionado ao seu modelo ou você verá o URL do ícone do calendário sendo referenciado por "/ 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
fonte
7

Complementando a resposta de Carl Meyer, gostaria de comentar que você precisa colocar esse cabeçalho em algum bloco válido (dentro do cabeçalho) dentro do seu modelo.

{% 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.
fonte
Também achei este link útil: groups.google.ca/group/django-users/msg/1a40cf5f153cd23e
Alex. S.
6

Para Django> = 2.0

Nota: Usar widgets de administrador para campos de data e hora não é uma boa ideia, pois as folhas de estilo do administrador podem entrar em conflito com as folhas de estilo do site, caso você esteja usando o bootstrap ou qualquer outra estrutura CSS. Se você estiver construindo seu site no bootstrap, use meu widget bootstrap-datepicker, django-bootstrap-datepicker-plus .

Etapa 1: adicione o javascript-catalogURL ao urls.pyarquivo do seu projeto (não do aplicativo) .

from django.views.i18n import JavaScriptCatalog

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

Etapa 2: adicione os recursos JavaScript / CSS necessários ao seu modelo.

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

Etapa 3: use os widgets de administrador para os campos de entrada de data e hora no seu 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
fonte
5

O abaixo também funcionará como último recurso se o acima falhar

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 a

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

    class Meta:
        model = Payments

coloque isso no seu forms.py from django.forms.extras.widgets import SelectDateWidget

Dennis Kioko
fonte
1
dá-me erro objeto 'DateField' tem nenhum atributo 'needs_multipart_form'
madeeha ameer
3

Que tal apenas atribuir uma classe ao seu widget e vinculá-la ao datepicker do 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'

E algum JavaScript para o modelo:

  $(".datepicker").datepicker();
trubliphone
fonte
1

Solução e solução alternativa atualizadas para SplitDateTime com 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.
fonte
Também preenchi um ticket para corrigir os widgets SplitDateTime code.djangoproject.com/ticket/12303
Sam A.
1

Eu uso isso, é ótimo, mas tenho 2 problemas com o modelo:

  1. Eu vejo os ícones do calendário duas vezes para cada modelo arquivado.
  2. E para o TimeField, tenho ' Digite uma data válida. "

    Aqui está uma captura de tela do formulário

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

No Django 10. myproject / urls.py: no início de urlpatterns

  from django.views.i18n import JavaScriptCatalog

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

No meu 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
fonte
0

Minha instalação do Django: 1.11 Bootstrap: 3.3.7

Como nenhuma das respostas é completamente clara, estou compartilhando meu código de modelo que não apresenta nenhum erro.

Metade superior do modelo:

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

Metade 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 %}
Arindam Roychowdhury
fonte
0

3 de junho de 2020 (todas as respostas não funcionaram, você pode tentar esta solução que eu usei. Apenas para TimeField)

Use Charfieldcampos simples para horário ( início e fim neste exemplo) em formulários.

forms.py

nós podemos usar Formou ModelFormaqui.

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

Converter entrada de string em objeto de tempo em visualizações.

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
fonte