Django基础之formset和modelformset
1. formset
顾名思义就是把多个form对象,合并在一起进行批量操作。我在批量分发权限使用过。
举个例子:
第一步:创建form
class MultiAddPermissionForm(forms.Form): title = forms.CharField( widget=forms.TextInput(attrs={'class': 'form-control'}) ) url = forms.CharField( widget=forms.TextInput(attrs={'class': 'form-control'}) ) name = forms.CharField( widget=forms.TextInput(attrs={'class': 'form-control'}) ) menu_id = forms.ChoiceField( choices=[(None, '----')], widget=forms.Select(attrs={'class': 'form-control'}), required=False ) pid_id = forms.ChoiceField( choices=[(None, '----')], widget=forms.Select(attrs={'class': 'form-control'}), required=False ) def __init__(self, *args, **kwargs): super(MultiAddPermissionForm, self).__init__(*args, **kwargs) self.fields['menu_id'].choices += models.Menu.objects.values_list('id', 'title') self.fields['pid_id'].choices += models.Permission.objects.filter(pid_id__isnull=True).\ exclude(menu_id__isnull=True).values_list('id','title')
第二步:实例化formset_factory对象,把我们写的form传进去
generate_formset_class = formset_factory(MultiAddPermissionForm, extra=0)
第三步:通过formset_factory对象,根据请求的不同,进行传值。
如果是GET,就可以通过initial参数,传一个列表生成式包裹的字典,字典里含的值就是form中定义的字段。
如果是POST,就通过data进行赋值,比如把request.POST赋值给他。
generate_name_list = router_name_set - permissions_name_set generate_formset = generate_formset_class( initial=[row_dict for name, row_dict in all_url_dict.items() if name in generate_name_list] )
第四步:进行is_valid判断后的赋值
if request.method == 'POST' and post_type == 'generate': formset = generate_formset_class(data=request.POST) if formset.is_valid(): object_list = [] post_row_dict = formset.cleaned_data has_error = False for i in range(0, formset.total_form_count()): row_dict = post_row_dict[i] try: new_object = models.Permission(**row_dict) new_object.validate_unique() object_list.append(new_object) except Exception as e: formset.errors[i].update(e) generate_formset = formset has_error = True if not has_error: models.Permission.objects.bulk_create(object_list, batch_size=100) else: generate_formset = formset
第五步: HTML页面中循环generate_formset对象
注意: {{ generate_formset.management_form }} 这句话一定要加上,否则增加不了数据。
<form action="?type=generate" method="post"> {% csrf_token %} {{ generate_formset.management_form }} <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true"></i> 待新建权限列表 <button href="#" class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;"> <i class="fa fa-save" aria-hidden="true"></i> 保存 </button> </div> <!-- Table --> <table class="table"> <thead> <tr> <th>序号</th> <th>名称</th> <th>URL</th> <th>别名</th> <th>菜单</th> <th>父权限</th> </tr> </thead> <tbody> {% for form in generate_formset %} <tr> <td>{{ forloop.counter }}</td> {% for field in form %} <td>{{ field }}<span style="color: red;">{{ field.errors.0 }}</span></td> {% endfor %} </tr> {% endfor %} </tbody> </table> </div> </form>
formset特别注意:
如果是更新操作时,formset怎么知道是哪一个form该更新?
就需要在定义form时,增加一个字段:隐藏的id字段,用于formset校验时,弹出这个id,以此判断是属于哪一个form的数据。
id = forms.IntegerField(
widget=forms.HiddenInput()
)
关于更新操作的代码:
if request.method == 'POST' and post_type == 'update': formset = update_formset_class(data=request.POST) if formset.is_valid(): post_row_dict = formset.cleaned_data for i in range(0, formset.total_form_count()): row_dict = post_row_dict[i] permission_id = row_dict.pop('id') try: row_object = models.Permission.objects.filter(pk=permission_id).first() for k,v in row_dict.items(): setattr(row_object, k, v) row_object.validate_unique() row_object.save() except Exception as e: formset.errors[i].update(e) update_formset = formset else: update_formset =formset
<form action="?type=update" method="post"> {% csrf_token %} {{ update_formset.management_form }} <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true"></i> 待更新权限列表 <button href="#" class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;"> <i class="fa fa-save" aria-hidden="true"></i> 保存 </button> </div> <!-- Table --> <table class="table"> <thead> <tr> <th>序号</th> <th>名称</th> <th>URL</th> <th>别名</th> <th>菜单</th> <th>父权限</th> </tr> </thead> <tbody> {% for form in update_formset %} <tr> <td>{{ forloop.counter }}</td> {% for field in form %} {% if forloop.first %} {{ field }} {% else %} <td>{{ field }}<span style="color: red;">{{ field.errors.0 }}</span></td> {% endif %} {% endfor %} </tr> {% endfor %} </tbody> </table> </div> </form>
2. modelformset
顾名思义就是把model和多个form集合,进行批量操作
第一步:创建modelform
class StudyRecordModelForm(StarkModelForm): class Meta: model = models.StudyRecord fields = ['record', ]
第二步:初始化modelformset对象:
把我们相应的model和自定义的modelform传进去
第三步:根据不同的请求(GET或者是POST),传不同的值
举例:
def attendance_view(self, request, course_record_id, *args, **kwargs): """ 考勤的批量操作 :param request: :param course_record_id: :param args: :param kwargs: :return: """ study_record_object_list = models.StudyRecord.objects.filter(course_record_id=course_record_id) study_model_formset = modelformset_factory(models.StudyRecord, form=StudyRecordModelForm, extra=0) if request.method == 'POST': formset = study_model_formset(queryset=study_record_object_list, data=request.POST) if formset.is_valid(): formset.save() return render(request, 'attendance.html', {'formset': formset}) formset = study_model_formset(queryset=study_record_object_list) return render(request, 'attendance.html', {'formset': formset})
第四步:HTML页面使用
{% extends 'layout.html' %} {% block content %} <div class="luffy-container"> <form method="post"> {% csrf_token %} {{ formset.management_form }} <div style="margin: 5px 0"> <input type="submit" value="保存" class="btn btn-success"> </div> <table class="table table-bordered"> <thead> <tr> <th>姓名</th> <th>考勤</th> </tr> </thead> <tbody> {% for form in formset %} <tr> {{ form.id }} <td>{{ form.instance.student }}</td> <td>{{ form.record }} <span style="color: red">{{ form.record.errors.0 }}</span></td> </tr> {% endfor %} </tbody> </table> </form> </div> {% endblock %}
特别注意:
1.循环每一个form对象的时候,一定要加上{{ form.id }}