如何在Django中添加新选项时刷新选择框?

时间:2017-10-19 20:36:05

标签: django forms foreign-keys

我有一个带有与外键相关的选择框的表单(例如,类别)。在同一页面上,我有另一个链接打开一个新页面来添加外键的新实例。添加新实例后,如何更新当前表单以添加新选项,并在文本字段中保留文本(就像管理页面的行为一样)?

以下是我的一些代码段:

update_post.html:

...
<form method="post" novalidate action='.'>
{% csrf_token %}
    {% include 'base_form.html' with form=form %}
    # button to add a category
    <a href="{% url 'blog:create_category' %}?next={{ request.path }}" class="btn btn-primary" role="button" target="_blank">{% trans "Add category" %}</a>
    <button type="submit" class="btn btn-primary" name="publish" value={% trans 'Publish' %}>{% trans 'Publish' %}</button>
</form>
...

create_category.html:

...
<form action="./{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" method="post" novalidate>
    {% csrf_token %}
    {% include 'base_form.html' with form=form %}
    <button type="submit" class="btn btn-primary" value={% trans 'Create' %}>{% trans 'Create' %}</button>
</form>
...

views.py:

...
class PostUpdate(UpdateView):
    template_name = 'update_post.html'
    success_url = '/'
    model = Post
    fields = ['title', 'body', 'category']

class CategoryCreate(CreateView):
    template_name = 'create_category.html'
    model = Category
    fields = ['name']
    def get_success_url(self):
        if 'next' in self.request.GET:
            return self.request.GET.get('next')
        else:
            return reverse('index')
...

我想要做的是,当添加新类别时,它会立即在update_post页面中显示,并保留对正文字段的任何更改。

1 个答案:

答案 0 :(得分:0)

昨天我做了这个启发django admin ForeignKey弹出窗口add.my条件是商品有goodcategory我可以在商品的添加/更新视图中添加/编辑/ delet goodscategory和结果添加/编辑/ delet goodcategory将同步到商品的添加/更新视图。这是一个演示,弹出窗口由layui支持。 enter image description here enter image description here 正如你所看到的,我可以添加\ change \ delete ForeignKey而不刷新父页面。

首先自定义一个新的Field to ForeignKey,它将接收add_url \ update_url \ delete_url:

class ForeignKeyWidget(Select):
    template_name = 'widgets/foreign_key_select.html'

    def __init__(self, url_template, *args, **kw):
        super(ForeignKeyWidget, self).__init__(*args, **kw)
        # Be careful that here "reverse" is not allowed
        self.url_template = url_template

    def get_context(self, name, value, attrs):
        context = super(ForeignKeyWidget, self).get_context(name, value, attrs)
        context['add_url'] = self.url_template
        context['update_url'] = self.url_template
        context['delete_url'] = self.url_template + 'lang_delete/'
        return context

第二个是为您的自定义字段自定义一个小部件,它可以弹出添加/更新类别窗口并使用ajax删除类别:

foreign_key_select.html:

{% include "django/forms/widgets/select.html" %}

    <style>
        #{{ widget.attrs.id }}_add, #{{ widget.attrs.id }}_change, #{{ widget.attrs.id }}_delete {
            margin-top: 10px;
            padding: 0 10px;
            height: 25px;
            line-height: 25px;
        }
    </style>

    <a class="layui-btn layui-btn-mini" id="{{ widget.attrs.id }}_add">
        add
    </a>
    <a class="layui-btn layui-btn-mini layui-btn-disabled" id="{{ widget.attrs.id }}_change">
        change
    </a><a class="layui-btn layui-btn-mini layui-btn-disabled" id="{{ widget.attrs.id }}_delete">
        delete
    </a>

    <script>
        $('#{{ widget.attrs.id }}_add').click(function () {
            var index = layui.layer.open({
                title: "add_category",
                type: 2,
                area: ['700px', '500px'],
                content: "{{ add_url }}" + '?popup=1&to_field={{ widget.attrs.id }}',
                success: function (layer, index) {

                }
            });
        });

        $("#{{ widget.attrs.id }}_change").click(function () {
            var id = $('#{{ widget.attrs.id }}').val();
            if (id) {
                var index = layui.layer.open({
                    title: "change_category",
                    type: 2,
                    area: ['700px', '500px'],
                    content: '{{ update_url }}' + id + '?popup=1&to_field={{ widget.attrs.id }}',
                    success: function (layer, index) {

                    }
                });
            }
        });

        $("#{{ widget.attrs.id }}_delete").click(function () {
            var id = $('#{{ widget.attrs.id }}').val();
            var value = $('#{{ widget.attrs.id }} option[value=' + id + ']').text();
            var indexGood = value.lastIndexOf('>');
            var valueN = indexGood > 0 ? value.substring(indexGood + 1, value.length) : value;
            if (id) {
                layer.confirm('corform delete' + valueN + ' ?', {icon: 3, title: 'delete'}, function (index) {
                    $.ajax({
                        type: "POST",
                        data: {},
                        url: '{{ delete_url }}' + id + '/',
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
                        },
                        success: function (data, textStatus) {
                            layer.close(index);
                            $('#{{ widget.attrs.id }} option[value=' + data.id + ']').remove();
                            $("#{{ widget.attrs.id }}_change,#{{ widget.attrs.id }}_delete").addClass('layui-btn-disabled');

                            return false;
                        },
                        error: function (XMLHttpRequest, textStatus, errorThrown) {
                            layer.alert('delete failed' + XMLHttpRequest.responseText)
                        }
                    });
                });
            }
        });

        function {{ widget.attrs.id }}_isDisabled() {
            if ($('#{{ widget.attrs.id }}').val()) {
                $("#{{ widget.attrs.id }}_change,#{{ widget.attrs.id }}_delete").removeClass('layui-btn-disabled');
            } else {
                $("#{{ widget.attrs.id }}_change,#{{ widget.attrs.id }}_delete").addClass('layui-btn-disabled');
            }
        }

        $('#{{ widget.attrs.id }}').change(function () {
            {{ widget.attrs.id }}_isDisabled();
        });

        {{ widget.attrs.id }}_isDisabled();
    </script>

第三个是在forms.py中使用自定义字段作为类别:

class GoodsForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(GoodsForm, self).__init__(*args, **kwargs)
        self.fields['category'].widget.attrs.update({'class': 'form-control'})
        self.fields['title'].widget.attrs.update({'class': 'form-control'})
        self.fields['content'].widget.attrs.update({'class': 'form-control'})

    class Meta:
        model = Goods
        fields = ['category', 'title', 'content']
        widgets = {
            'category': ForeignKeyWidget(url_template=reverse_lazy('goods_category_ajax_create')),
        }

和一个新的goodcategory表单是forms.py

class GoodsCategoryForm(TranslatableModelForm):
    def __init__(self, *args, **kwargs):
        super(GoodsCategoryForm, self).__init__(*args, **kwargs)
        self.fields['name'].widget.attrs.update({'class': 'form-control'})
        self.fields['cover'].widget.attrs.update({'class': 'form-control'})
        self.fields['parent'].widget.attrs.update({'class': 'form-control'})

    class Meta:
        model = GoodsCategory
        fields = ['name', 'cover', 'parent']

四是views.py中的句柄请求:

class GoodsCategoryAjaxCreateView(BaseContextMixin, IsStaffUserMixin, CreateView):
    form_class = GoodsCategoryForm
    template_name = 'goods_category_ajax/create.html'

    def get_context_data(self, **kwargs):
        if 'to_field' in self.request.GET:
            kwargs['to_field'] = self.request.GET['to_field']
        return super(GoodsCategoryAjaxCreateView, self).get_context_data(**kwargs)

    def form_valid(self, form):
        self.object = form.save()
        context = {'op': 'create', 'id': self.object.id, 'value': self.object.__str__()}
        if 'to_field' in self.request.GET:
            context['to_field'] = self.request.GET['to_field']
        return TemplateResponse(self.request, 'goods_category_ajax/success.html', context=context)


    class GoodsCategoryAjaxUpdateView(BaseContextMixin, IsStaffUserMixin, UpdateView):
        model = GoodsCategory
        form_class = GoodsCategoryForm
        slug_field = 'id'
        context_object_name = 'goods_category'
        template_name = 'goods_category_ajax/update.html'

        def get_context_data(self, **kwargs):
            if 'to_field' in self.request.GET:
                kwargs['to_field'] = self.request.GET['to_field']
            return super(GoodsCategoryAjaxUpdateView, self).get_context_data(**kwargs)

        def form_valid(self, form):
            self.object = form.save()
            context = {'op': 'create', 'id': self.object.id, 'value': self.object.__str__()}
            if 'to_field' in self.request.GET:
                context['update'] = self.request.GET['to_field']
            return TemplateResponse(self.request, 'goods_category_ajax/success.html', context=context)


    class GoodsCategoryAjaxLangDeleteView(BaseContextMixin, IsStaffUserMixin, FakeDeleteView):
        model = GoodsCategory
        slug_field = 'id'

        def delete(self, request, *args, **kwargs):
            self.object = self.get_object()
            data = {'op': 'delete', 'id': self.object.id, 'value': self.object.__str__()}
            self.object.delete()
            return JsonResponse(data=data)

urls.py:

url(r'^ajax/$', GoodsCategoryAjaxCreateView.as_view(), name='goods_category_ajax_create'),
url(r'^ajax/(?P<slug>\d+)/$', GoodsCategoryAjaxUpdateView.as_view(), name='goods_category_ajax_update'),
url(r'^ajax/lang_delete/(?P<slug>\d+)/$', GoodsCategoryAjaxLangDeleteView.as_view(),
    name='goods_category_ajax_lang_delete'),

五是你的添加弹出窗口将通过GoodsCategoryAjaxCreateView打开url句柄,返回模板是:

{% extends "manage/base.html" %}

{% block main %}

    <form id='goods_category_ajax_create' class="form-horizontal" enctype="multipart/form-data"
          action="{% url 'goods_category_ajax_create' %}{% if to_field %}?to_field={{ to_field }}{% endif %}"
          method="post">
        {% include 'manage/widgets/form.html' %}

        <div class="form-group">
            <div class="col-sm-offset-1 col-sm-10">
                <input class="layui-btn layui-btn-normal" type="submit" value="add_category"/>
            </div>
        </div>
    </form>
{% endblock %}

你提交了一个带有modelform和createview的新类别,当表单is_vaild时,templatesResponse会返回success.html(你可以在GoodsCategoryAjaxCreateView form_valid中看到),而point.html就是一个可以写的脚本关闭弹出窗口并向父窗口中的to_field元素插入新选项。其中有success.html:

{% extends "manage/base.html" %}

{% block main %}
    <script>
        var to_field = '#{{ to_field }}', op = '{{ op }}', id = '{{ id }}', value = '{{ value }}';
        if (to_field) {
            switch (op) {
                case 'create':
                    if (id) {
                        var index = parent.layer.getFrameIndex(window.name); //get current iFrame index
                        parent.layer.close(index); //close
                        $option = '<option value=' + id + ' selected>' + value + '</option>';
                        $(to_field, window.parent.document).append($option);
                        $(to_field + '_change,' + to_field + '_delete', window.parent.document).removeClass('layui-btn-disabled');
                    }
                    break;
                case 'update':
                    if (id) {
                        var index = parent.layer.getFrameIndex(window.name); 
                        parent.layer.close(index); 
                        $(to_field + ' option[value=' + id + ']', window.parent.document).html(value);
                    }
                    break;
            }
        }
    </script>
{% endblock %}