Django如何实现多对一数据的添加

例如:新增一个文章时,需要"新增另一个"的按钮
关键词:django、inlineform、form

目标效果:

Django如何实现多对一数据的添加

参考链接:

具体实现

  • 安装两个第三方的包:

    pip install django-crispy-forms django-dynamic-formsets
    
  • models.py

    class Articles(models.Model):
        user = models.ForeignKey(Client,on_delete=models.CASCADE,verbose_name="所属用户")
        content = models.CharField(max_length=200,verbose_name="正文")
        create_time = models.DateTimeField(auto_now_add=True,verbose_name="创建时间")
        last_edit_time = models.DateTimeField(auto_now=True,verbose_name="最后一次编辑时间")
        is_public = models.CharField(max_length=5,choices=(("T","发布"),("F","仅自己可见")),verbose_name="是否发布")
        view = models.PositiveSmallIntegerField(default=0,verbose_name="阅读数")
        like = models.PositiveSmallIntegerField(default=0,verbose_name="点赞数")
    
        class Meta:
            verbose_name = "文章"
            verbose_name_plural = verbose_name
            ordering = ["-create_time"]
    
    
    
    class Images(models.Model):
        img = models.FileField(upload_to="articles_img",verbose_name="图片")
        articles = models.ForeignKey(Articles,on_delete=models.CASCADE,verbose_name="所属文章")
        create_time = models.DateTimeField(auto_now_add=True,verbose_name="上传时间")
    
        class Meta:
            verbose_name = "文章图片"
            verbose_name_plural = verbose_name
            ordering = ["-create_time"]
    
  • urls.py

    from django.urls import path
    from main_app import views
    
    app_name = 'main_app'
    
    urlpatterns = [
        path('create_articles/',views.ArticlesCreate.as_view(),name="create_articles"),
        ...
    ]
    
  • views.py

    from main_app import models
    from main_app import forms
    
    class ArticlesCreate(LoginRequiredMixin,CreateView):
        model = models.Articles
        template_name = "articles_add.html"
        form_class = forms.ArticlesForm
        success_url = None
    
        def get_context_data(self, **kwargs):
            data = super(ArticlesCreate, self).get_context_data(**kwargs)
            if self.request.POST:
                data['images_formset'] = accounts_forms.ImagesFormSet(self.request.POST,self.request.FILES)
            else:
                data['images_formset'] = accounts_forms.ImagesFormSet()
            return data
    
        def form_valid(self, form):
            context = self.get_context_data()
            images_formset = context['images_formset']
            with transaction.atomic():
                form.instance.user = self.request.user.client
                self.object = form.save()
                if images_formset.is_valid():
                    images_formset.instance = self.object
                    images_formset.save()
            return super(ArticlesCreate, self).form_valid(form)
    
        def get_success_url(self):
            return reverse_lazy('main_app:index')
    
    
  • forms.py

    from django import forms
    from django.forms.models import inlineformset_factory
    
    from crispy_forms.helper import FormHelper
    from crispy_forms.layout import Layout, Field, Fieldset, Div, HTML, ButtonHolder, Submit
    
    from main_app import models
    from main_app.custom_layout_object import Formset
    
    class ImagesForms(forms.ModelForm):
        class Meta:
            model = models.Images
            fields = ("img",)
    
    ImagesFormSet = inlineformset_factory(
        models.Articles,
        models.Images,
        form=ImagesForms,
        extra=1,
        can_delete=True
    )
    
    class ArticlesForm(forms.ModelForm):
        content = CustomCharField(label="正文",widget=forms.Textarea(attrs={"rows":3}))
        class Meta:
            model = models.Articles
            fields = ("content","is_public")
    
        def __init__(self, *args, **kwargs):
            super(ArticlesForm, self).__init__(*args, **kwargs)
            self.helper = FormHelper()
            self.helper.form_tag = True
            self.helper.form_class = ''
            self.helper.layout = Layout(
                Div(
                    Field('content'),
                    Field('is_public'),
                    Fieldset('添加图片',
                             Formset('titles')),
                    ButtonHolder(Submit('submit', '保存')),
                )
            )
    
    
  • main_app/custom_layout_object.py

    from crispy_forms.layout import LayoutObject, TEMPLATE_PACK
    from django.shortcuts import render
    from django.template.loader import render_to_string
    
    
    class Formset(LayoutObject):
        template = "formset.html"
    
        def __init__(self, formset_name_in_context, template=None):
            self.formset_name_in_context = formset_name_in_context
            self.fields = []
            if template:
                self.template = template
    
    
        def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
            formset = context[self.formset_name_in_context]
            return render_to_string(self.template, {'formset': formset})
    
  • formset.html

    这里我新增了一些js代码用于隐藏不需要的按钮

    {% load crispy_forms_tags %}
    {% load static %}
    {% load crispy_forms_tags %}
    <table class="col-md-9" style="margin-left: 10px;">
    {{ formset.management_form|crispy }}
        {% for form in formset.forms %}
            <tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
                {% for field in form.visible_fields %}
                <td>
                    {# Include the hidden fields in the form #}
                    {% if forloop.first %}
                        {% for hidden in form.hidden_fields %}
                            {{ hidden }}
                        {% endfor %}
                    {% endif %}
                    {{ field.errors.as_ul }}
                    {{ field|as_crispy_field }}
                </td>
                {% endfor %}
            </tr>
        {% endfor %}
    
    </table>
    <br>
    <script src="{% static 'bootstrap/jquery-3.6.0.min.js' %}"></script>
    <script src='{% static "dynamic_formsets/jquery.formset.js" %}'></script>
    <script type="text/javascript">
        $('.formset_row-{{ formset.prefix }}').formset({
            addText: '<i class="bi bi-plus"></i>新增另一个',
            deleteText: '<i class="bi bi-trash"></i>删除',
            prefix: '{{ formset.prefix }}',
        });
        $("input[type='checkbox']").each(hiddenLabel)
        function hiddenLabel(index,element){
            console.log(element)
            $(element).attr("hidden",true)
            $($(element)[0].labels[0]).attr("hidden",true)
        }
    
    </script>
    
  • articles_add.html

    {% extends "base.html" %}
    {% load crispy_forms_tags %}
    {% block title %}编辑文章{% endblock %}
    {% block css %}
        <style>
            .asteriskField{
                color: red;
            }
            .col-md-9 label{
                color: grey !important;
            }
            .delete-row{
                color: red !important;
                font-size: 1rem !important;
            }
        </style>
    {% endblock %}
    {% block content %}
    <div class="container">
        <div class="card">
            <div class="card-header">
                编辑文章
            </div>
            <div class="card-body">
                 {% crispy form %}
            </div>
        </div>
    </div>
    {% endblock content %}
    {% block js %}
        <script>
            $("form").attr("enctype","multipart/form-data")
        </script>
    {% endblock %}
    
上一篇:child


下一篇:小程序-wx.request POST请求,请求参数需要form-data形式