Two knowledge points are needed here:
- formset
- automatically discover the URL in the project
1. What is a formset:
form component or ModelForm component in Django, used for Do a form validation. Receive the data in the front-end form and verify it. And it can also be used for the rendering of forms. (It is to loop the form object directly, and each field will be rendered into a label. And placed in the form label.)
Note: I mentioned that it is the validation of the “one” form. That is to say, the front-end data comes, whether it is addition, deletion, modification, inspection, it is the processing of a “line” record in the database, a piece of data. They are all single.
ok, that is the case, but when I want to perform batch operations, when verifying “multiple” forms. Just use the formset! It can be understood that he embeds a bunch of form tags into a formset, and then verifies them together.
2. So how do you use it?
Start a new project first. Use it first, and then integrate it into my rbac project.
First migrate the two tables and use them. Because I don’t want to write anymore! So copy it directly from the project.
Menu table, first manually enter two pieces of information.
The Permission table is still empty, so try Try formset to add to this table in batches.
Write the form class:
< div class="code" onclick="code_show('4665aea9-74f4-49cb-bbbc-ea0dc839f418')">
class MultiPermissionForm(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( # ChoiceField and choices represent the data source
choices=[(None, "--------- span>")],
widget=forms.Select(attrs={"class": "form- control"}),
required=False
)
pid_id = forms.ChoiceField(
choices=[(None, "------- --")],
widget=forms.Select(attrs={"class": "form- control"}),
required=False
)
# Because menu_id and pid_id should be a selectable drop-down box Form, so the __init__ initialization function is rewritten.
# Let these two fields in the fields object match the results of the query in the database For splicing
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["menu_id"].choices += models.Menu.objects.values_list("mid", "title")
self.fields["pid_id"].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
menu__isnull=True).values_list("id< span style="color: #800000;">", "title")
# Filter out pid==Null which can be used as secondary menu , Exclude exclude menu==Null, which does not belong to any first-level menu. All permission records
Note that the inherited form.form is no longer forms.ModelForm
menu_id and pid_id are foreign keys, one-to-many relationship. So we need to make the page show the style of a select drop-down box.
So the forms.ChoiceField parameter choices=[(None, “———“)] span>This is a default value. When the user chooses nothing, the page is displayed as “—- —–“The database is stored as None.
But in the end, if you want to, display the data in the table associated with the foreign key So here, the __init__ initialization method has been rewritten. Take out the data from the database and concatenate it.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["menu_id"].choices += models.Menu.objects.values_list("mid", "title")
self.fields["pid_id"].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
menu__isnull=True).values_list("id", "title")
Because it rewrites the method of the parent class. So it is super. Each object has its own fields object. This object contains all the fields written in our class. So here is the menu_id field and the pid_id field. Make splicing.
because we are splicing the choices parameter. He receives the list format, so use values_list to retrieve the values in the database. Not all of them are taken out, because what we need is, 1. For the foreign key relationship saved in the database, just have a primary key id. 2. Then there is the title field that needs to be displayed on the page.
As for what you need, according to your needs: What I need for pid_id here is. The pid field in the Permission table is Null, and these fields can be used as secondary menus.
exclude excludes; what is excluded is that the menu field in the Permission table is empty, which means that it does not belong to any of these fields of the first-level menu.
OK So far, the form of a form is done!
Then you want to do batches, add them. A built-in Django module is needed:
from django.forms import formset_factory
formset_factory accepts two parameters, one is what we just wrote The MultiPermissionForm is then extra.
The first parameter is the form I want to render. The second parameter is how many times to render.
Look at the code:
def multi_add(request):
‘‘‘
Batch add
:param request:
:return:
‘‘‘
# Use formset_factory to make it internal and help me generate extra specified number of form objects< /span>
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == "POST":
formset = formset_class(data=request.POST)
if formset.is_valid():
print(formset.cleaned_data)
return HttpResponse("Submitted successfully")
return render(request, "multi_add.html", locals())
# Then instantiate the formset_class
formset = formset_class()
return render(request, "multi_add.html", locals())
views
Then, the rendering of the front-end page:
DOCTYPE html>
<html lang="en">
<head >
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title >Titletitle>
head>
<body >
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
<table border="1">
<thead >
<tr >
<th >Titleth>
<th >urlth>
<th >Codeth>
<th >Menuth>
<th >Permissionsth>
tr>
thead>
<tbody >
{% for form in formset %}
<tr>
{% for field in form %}
<td>{{ field }} <span>{{ field.errors.0 }}span>td>
{% endfor %}
tr>
{% endfor %}
tbody>
table>
<input type="submit" value="Submit">
form>
body>
html>
templates
In addition to what Django needs {%csre_token%} There is one more that must be written{{ formset .management_form }}. If you do not write, you may encounter upload data loss errors.
Then take a look at the effect of the front-end page:
A stroke of low, but let’s do it!
so Let’s take a look, how is the effect of the operation?
1. For the first time, I don’t write anything and submit it directly.
Fuck it, did you succeed? What is the situation, the verification is good? Take a look at the result of print(formset.cleaned_data) :
[{}, {}]
emmmm ok, although after passing, formset.is_valid() returns, what does it mean to be an empty list?
Explain: If I don’t write anything when I submit it, the form component won’t help me verify it. But after entering, I wrote something before verification.
It’s better to look at a picture to explain it more clearly:
This is very clear. For the first row of data, I wrote a title. He verified it for me, and then returned the corresponding error message where it failed.
But the second line is the data. I didn’t write anything, the form component did not give me verification. So there is no verification, there is no error, right:
Take a look at print(formset.cleaned_data) Result:
He did not execute this time, because if formset.is_valid( ): Failed to verify.
What if you only write one line? [{‘title’:’This is a title’,’url’:’/costomer/list/’,’name’:’costomer_list’,’menu_id’: ‘2’,’pid_id’:”}, { }]
The second one is still empty.
OK Understood, what is returned is a list, each row of data in dictionary format in the list. Let’s see how to save it to the database:
It must be a for loop.
def multi_add(request):
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == "POST":
formset = formset_class(data=request.POST)
if formset.is_valid():
print(formset.cleaned_data)
for row in formset.cleaned_data:
# models.Permission.objects.create(**row)
obj = models.Permission(**row)
obj.save()
return HttpResponse("Submitted successfully")
return render(request, "multi_add.html", locals())
formset = formset_class()# Then instantiate formset_class
return render(request, "multi_add.html", locals())
There are two ways to save.
1. models.Permission.objects.create(**row) Use directly. ORM method. Will row this dictionary. Pass it to create() to save it.
2. You can also generate a model object first. Then this object executes the save() method. All can be saved.
OK to see the effect:
< p>
Then the last question is, about the only The sexual problem is that the field of unipue=Ture is added to the database:
So we have to catch this error, and then deal with it:
Then there is a big problem, how can we let The only error in this field is displayed on this field accordingly?
1. We know that every form object has two attributes, its fields and its error message: What is a formset?
[ form(field, error), form(field, error), form(field, error), form(field, error)] This is probably what it looks like.
2. What about the data returned from the front end? What is it like?
[ {}, {}, {}, {}] It’s like this.
The only place they have practice is that the first form object corresponds to the index of the first {}.
So when I do the loop next, I can’t follow the above method to loop again, but it should be.
for i in range(0, formset.total_form_count()): # formset.total_form_count() This is the test How many form objects are in the formset. If there are 5, then loop 0, 1, 2, 3, 4,
formset.cleaned_data[i] # cleaned_data is the return from the front end [{}, {}, {}, {}]
Formset.errors[i] # errors are all error messages.
When fetching, follow the index and fetch the data. Misjudged in the middle. If there is an error, it is added to the errors list under the current index. Then return to the front end for rendering.
Here is a small detail, formset.cleaned_data[i] and formset.errors[i] These two sentences are mutually exclusive, that is, if an error occurs in cleaned_data, you will not get the data NS.
formset.cleaned_data works like this: Check if there is no error message in the formset, then get the data submitted by the user. He does not get the data in every form object all at once. Instead, once
is executed, the data in the form object is obtained once.
What do you mean? In other words, if an error occurs the first time in the loop, you won’t get the data afterwards. but way?
Because formset.errors[i] This is equivalent to artificially adding an error to the form; when formset.cleaned_data is checked, this error will be detected, and then you will not think about the following data
SO look at the code
def multi_add(request):
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == "POST":
formset = formset_class(data=request.POST)
if formset.is_valid():
flag = True
post_row_date = formset.cleaned_data # Get all the data submitted by the user first, and assign it to a variable to prevent errors in the middle and the remaining data cannot be obtained.
for i in range(0, formset.total_form_count()):
row = post_row_date[i]
if not row:
# If it is empty data, no need to continue.
try:
obj = models.Permission(**row)
obj.validate_unique()# Check whether the current object has a unique exception in the database. If there is he will throw an exception
obj.save()
except Exception as e:# When this exception is caught, e is the prompt of the error message. Dictionary, and then update() to the dictionary
formset.errors[i].update(e)
flag = False
if flag:
return HttpResponse("Submitted successfully")
else:
return render(request, "multi_add.html", {"formset": formset})
return render(request, "multi_add.html", {"formset": formset})
formset = formset_class() # then instantiate formset_class
return render(request, "multi_add.html", {"formset": formset})
ok it’s done. When the batch increases, there are no other problems. Then there is batch modification: annoying. . . . . . . . . . . .
class MultiPermissionForm(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( # ChoiceField and choices represent the data source
choices=[(None, "--------- span>")],
widget=forms.Select(attrs={"class": "form- control"}),
required=False
)
pid_id = forms.ChoiceField(
choices=[(None, "------- --")],
widget=forms.Select(attrs={"class": "form- control"}),
required=False
)
# Because menu_id and pid_id should be a selectable drop-down box Form, so the __init__ initialization function is rewritten.
# Let these two fields in the fields object match the results of the query in the database For splicing
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["menu_id"].choices += models.Menu.objects.values_list("mid", "title")
self.fields["pid_id"].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
menu__isnull=True).values_list("id", "title")
# 过滤出 pid==Null的 可以做二级菜单的, exclude排除 menu==Null,不属于任何一个一级菜单的。所有的权限记录
注意继承的是 forms.form 不再是 forms.ModelForm
class MultiPermissionForm(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( # ChoiceField 和 choices 就表示数据源
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
)
# 因为 menu_id 和 pid_id ,应该是一个可以选择的下拉框形式, 所以重写了 __init__初始化函数。
# 让fields对象中的这两个字段, 与数据库中查询出的结果,进行拼接
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["menu_id"].choices += models.Menu.objects.values_list("mid", "title")
self.fields["pid_id"].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
menu__isnull=True).values_list("id", "title")
# 过滤出 pid==Null的 可以做二级菜单的, exclude排除 menu==Null,不属于任何一个一级菜单的。所有的权限记录
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["menu_id"].choices += models.Menu.objects.values_list("mid", "title")
self.fields["pid_id"].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
menu__isnull=True).values_list("id", "title")
def multi_add(request):
‘‘‘
批量添加
:param request:
:return:
‘‘‘
# 使用formset_factory 让它在内部,帮我生成 extra 指定数量的 form对象
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == "POST":
formset = formset_class(data=request.POST)
if formset.is_valid():
print(formset.cleaned_data)
return HttpResponse("提交成功")
return render(request, "multi_add.html", locals())
# 然后将formset_class 进行实例化
formset = formset_class()
return render(request, "multi_add.html", locals())
views
def multi_add(request):
‘‘‘
批量添加
:param request:
:return:
‘‘‘
# 使用formset_factory 让它在内部,帮我生成 extra 指定数量的 form对象
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == "POST":
formset = formset_class(data=request.POST)
if formset.is_valid():
print(formset.cleaned_data)
return HttpResponse("提交成功")
return render(request, "multi_add.html", locals())
# 然后将formset_class 进行实例化
formset = formset_class()
return render(request, "multi_add.html", locals())
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Titletitle>
head>
<body>
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
<table border="1">
<thead>
<tr>
<th>标题th>
<th>urlth>
<th>Codeth>
<th>Menuth>
<th>权限th>
tr>
thead>
<tbody>
{% for form in formset %}
<tr>
{% for field in form %}
<td>{{ field }} <span>{{ field.errors.0 }}span>td>
{% endfor %}
tr>
{% endfor %}
tbody>
table>
<input type="submit" value="提交">
form>
body>
html>
templates
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Titletitle>
head>
<body>
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
<table border="1">
<thead>
<tr>
<th>标题th>
<th>urlth>
<th>Codeth>
<th>Menuth>
<th>权限th>
tr>
thead>
<tbody>
{% for form in formset %}
<tr>
{% for field in form %}
<td>{{ field }} <span>{{ field.errors.0 }}span>td>
{% endfor %}
tr>
{% endfor %}
tbody>
table>
<input type="submit" value="提交">
form>
body>
html>
def multi_add(request):
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == "POST":
formset = formset_class(data=request.POST)
if formset.is_valid():
print(formset.cleaned_data)
for row in formset.cleaned_data:
# models.Permission.objects.create(**row)
obj = models.Permission(**row)
obj.save()
return HttpResponse("提交成功")
return render(request, "multi_add.html", locals())
formset = formset_class()# 然后将formset_class 进行实例化
return render(request, "multi_add.html", locals())
for i in range(0, formset.total_form_count()): # formset.total_form_count() 这个是检测formset中有多少 form对象。如果有5个, 就循环0,1,2,3,4,
formset.cleaned_data[i] # cleaned_data就是前端返回来的 [ {}, {}, {}, {} ]
formset.errors[i] # errors 就是所有的错误信息,
取出的时候, 就按照索引进行,取出数据。 中间进行判断错误。 如果有错误,就加到当前索引下的 errors 列表中。 然后返回前端进行渲染。
def multi_add(request):
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == "POST":
formset = formset_class(data=request.POST)
if formset.is_valid():
flag = True
post_row_date = formset.cleaned_data # 先一步将用户提交的数据,全部获取到,并赋值给一个变量,防止中途发生错误,剩余数据取不到。
for i in range(0, formset.total_form_count()):
row = post_row_date[i]
if not row:
continue # 如果是空的数据, 下面就不用走了。
try:
obj = models.Permission(**row)
obj.validate_unique()# 检测当前对象在数据库是否存在 唯一的异常。如果有他就会抛出一个异常
obj.save()
except Exception as e:# 捕获到这个异常, e就是错误信息的提示
# 为什么是 update(e) 因为 errors是这样子的 [{"title":[]}] 在这里就是获取到其中一个字典, 然后给字典 update()
formset.errors[i].update(e)
flag = False
if flag:
return HttpResponse("提交成功")
else:
return render(request, "multi_add.html", {"formset": formset})
return render(request, "multi_add.html", {"formset": formset})
formset = formset_class() # 然后将formset_class 进行实例化
return render(request, "multi_add.html", {"formset": formset})