例如:新增一个文章时,需要"新增另一个"的按钮
关键词:django、inlineform、form
目标效果:
参考链接:
-
https://dev.to/zxenia/django-inline-formsets-with-class-based-views-and-crispy-forms-14o6
-
代码案例(上面的链接)https://github.com/zxenia/example-inline-formsets
具体实现
-
安装两个第三方的包:
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 %}