在自定义表单中使用Django时间/日期控件

184 投票
18 回答
139017 浏览
提问于 2025-04-11 09:16

我想知道怎么在我的自定义视图中使用默认管理界面里那些很酷的JavaScript日期和时间小工具。

我查阅了Django表单的文档,里面简要提到了django.contrib.admin.widgets,但我不知道该怎么用。

这是我想应用这些小工具的模板。

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

另外,我想说明一下,我其实还没有自己写过这个表单的视图,我是在用一个通用视图。这里是url.py中的相关内容:

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

而且我对Django/MVC/MTV这些东西还比较陌生,所以请多多包涵……

18 个回答

14

我发现自己经常引用这篇帖子,并且发现文档中定义了一种稍微不那么复杂的方法来覆盖默认的控件。

(不需要覆盖ModelForm的__init__方法)

不过,正如Carl提到的,你仍然需要正确地连接你的JS和CSS。

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

可以参考字段类型来找到默认的表单字段。

67

因为这个解决办法有点不太正规,我觉得用你自己的日期/时间小工具加上一些JavaScript会更实际一些。

169

这个答案随着时间的推移变得越来越复杂,而且需要很多小技巧,这可能会让你考虑是否真的要这样做。它依赖于管理员的内部实现细节,这些细节没有文档说明,未来的Django版本可能会再次出问题,而且实现起来也不比找一个其他的JS日历组件简单。

不过,如果你还是决定要这样做,下面是你需要做的事情:

  1. 为你的模型定义一个自己的 ModelForm 子类(最好把它放在你应用的 forms.py 文件里),并告诉它使用 AdminDateWidget / AdminTimeWidget / AdminSplitDateTime(把'mydate'等替换成你模型中的正确字段名):

     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. 修改你的 URL 配置,将 'form_class': ProductForm 传递给通用的 create_object 视图,而不是 'model': Product(这意味着你需要 from my_app.forms import ProductForm 而不是 from my_app.models import Product)。

  3. 在你的模板的头部,加入 {{ form.media }} 来输出 JavaScript 文件的链接。

  4. 还有一个小技巧:管理员的日期/时间组件假设 i18n JS 的内容已经加载,并且还需要 core.js,但这两个都不会自动提供。所以在你的模板中 {{ form.media }} 之前,你需要:

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

你可能还想使用以下的管理员 CSS(感谢 Alex 提到这个):

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

这意味着 Django 的管理员媒体(ADMIN_MEDIA_PREFIX)在 /media/admin/ - 你可以根据你的设置进行更改。理想情况下,你应该使用上下文处理器将这些值传递给你的模板,而不是硬编码,但这超出了这个问题的范围。

这还要求 URL /my_admin/jsi18n/ 手动连接到 django.views.i18n.javascript_catalog 视图(如果你不使用 I18N,则连接到 null_javascript_catalog)。你必须自己完成这个,而不是通过管理员应用程序,这样无论你是否登录管理员都能访问(感谢 Jeremy 指出这一点)。你的 URL 配置的示例代码:

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

最后,如果你使用的是 Django 1.2 或更高版本,你需要在模板中添加一些额外的代码,以帮助组件找到它们的媒体:

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

感谢 lupefiasco 的补充。

撰写回答