- 给表单字段指定class
- django 后台参数传给select下拉框
- admin新增页面的外键下拉框返回筛选后的值
- admin新增页面的外键下拉框可搜索
- 更改model在admin中的名字(后台管理表名称改中文)
- admin界面无法搜索外键字段
- error at line 0 错误| no such column:表名称.resource
- django多对多标签渲染、多选 select2
- django多对多表单的保存
- ListView实现搜索和搜索后正常分页|ListView使用context|get_queryset使用request
- django禁用formset,form表单集和表单的字段
- django/js 增加表单只复制元素,不复制值
- django获取数据表中某一列的值并转为list
- django使用多个不同formset,动态增加表单
- ListView中使用django自带的分页器paginator、排序
- django 有外键关系的表单集formset保存,内联表单集
- 外键关系的表单保存
- ‘A’ must be a ‘B’ instance
给表单字段指定class
1.从模型生成的表单
from django.forms import ModelForm,TextInput,CheckboxInput
class ContactForm(ModelForm):
class Meta:
model = Contact
exclude = ['supplier']
widgets = {
'name': TextInput(attrs={'class': 'form-control', 'placeholder': "联系人"}),
'manage': CheckboxInput(attrs={'class': 'form-check-input'}),
}
2.自定义的表单
from django.forms import Form,TextInput
class UserForm(Form):
username = forms.CharField(label="联系人",widget=TextInput(attrs={'class': 'form-control'}))
3.类似form.DELETE这种自动生成的部件
只能用js来修改
<script>
$(document).ready(function() {
let deleteForms = document.querySelectorAll("input[id$='-DELETE']")
for(i = 0; i < deleteForms.length; i++){
deleteForms[i].setAttribute('class','form-check-input')
}
});
</script>
4.其他
class ContactForm(ModelForm):
class Meta:
model = Contact
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['name'].readonly = True
self.fields['industries'].widget.attrs.update({'class':"js-example-basic-multiple"})
django 后台参数传给select下拉框
<select name="industry" class="js-example-basic-multiple" multiple="multiple">
{% for obj in industries %}
<option value="{{ obj.pk }}">{{obj}}</option>
{% endfor %}
admin新增页面的外键下拉框返回筛选后的值
class AuthorityAdmin(admin.ModelAdmin):
model = Authority
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
# 替换下拉框中的值
context['adminform'].form.fields['user'].queryset = Users.objects.filter()
return super(AuthorityAdmin, self).render_change_form(request, context,
add, change, form_url, obj)
def formfield_for_foreignkey(self,db_field,request,**kwargs):
# 或者用这个,两个都能用
try:
if db_field.name == 'user':
kwargs['queryset'] = Users.objects.filter()
return super(AuthorityAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
except Exception as e:
print(e)
不能筛选后的值可搜索的原因:
https://stackoverflow.com/questions/62682725/filter-queryset-for-foreign-key-in-django-autocomplete-fields
autocomplete_fields会调用外键的get_queryset(),get_search_results(),必须重写get_search_results()才能实现,但是这样外键的搜索就会被破坏。
admin新增页面的外键下拉框可搜索
设置autocomplete_fields为外键字段,同时外键还需要设置search_fields
models.py
class Authority(models.Model):
#https://docs.djangoproject.com/zh-hans/3.1/topics/db/examples/one_to_one/
user = models.OneToOneField("home.Users",to_field="user_ip", on_delete=models.CASCADE,unique=True)
add = models.BooleanField(verbose_name='增',default=False)
admin.py
@admin.register(Users)
class UserAdmin(admin.ModelAdmin):
search_fields = ["username"]
@admin.register(Authority)
class AuthorityAdmin(admin.ModelAdmin):
autocomplete_fields = ['user']
更改model在admin中的名字(后台管理表名称改中文)
class Industry(models.Model):
class Meta:
verbose_name_plural='行业'
admin界面无法搜索外键字段
报错:Related Field got invalid lookup: icontains
search_fields中使用双下划线
class AuthorityAdmin(admin.ModelAdmin):
model = Authority
search_fields = ['user__username']
admin.site.register(Authority,AuthorityAdmin)
error at line 0 错误| no such column:表名称.resource
django多对多标签渲染、多选 select2
forms.py
也包括保存
class SupplierForm(ModelForm):
tags = ModelMultipleChoiceField(queryset=Tag.objects.all(),label='物料范围',required=False)
class Meta:
model = Supplier
fields = '__all__'
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
initial = kwargs.setdefault('initial',{})
initial['tags'] = [t.pk for t in kwargs['instance'].tag_set.all()]
ModelForm.__init__(self,*args,**kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['tags'].widget.attrs.update({'class':"js-example-basic-multiple"})
def save(self, commit=True):
instance = ModelForm.save(self,False)
old_save_m2m = self.save_m2m
def save_m2m():
instance.tag_set.clear()
instance.tag_set.add(*self.cleaned_data['tags'])
self.save_m2m = save_m2m
if commit:
instance.save()
self.save_m2m()
return instance
html
<head>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.8/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.8/js/select2.min.js"></script>
<script>
$(document).ready(function() {
$('.js-example-basic-multiple').select2();
});
</script>
</head>
django多对多表单的保存
https://stackoverflow.com/questions/2216974/django-modelform-for-many-to-many-fields
重写了save方法,没有理解透,就不分析了。
主要功能:一个供应商增减多个标签。不含标签本身的增减。
ListView实现搜索和搜索后正常分页|ListView使用context|get_queryset使用request
主界面是供应商的列表,用了ListView,然后还想实现搜索
from django.views import generic
from .models import Supplier
class DetailView(generic.ListView):
template_name = 'supplier/overview.html'
context_object_name = 'supplier'
paginate_by = 20
def get_queryset(self):
if self.request:
search_str = self.request.GET.get('search')
else:
search_str = False
if search_str:
sp_list = Supplier.objects.filter(supplier_name__contains=search_str)
return sp_list
else:
return Supplier.objects.all()
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
if self.request:
search_str = self.request.GET.get('search')
context['search'] = search_str
return context
搜索后的分页,用了一个土办法
{% if search %}
<li>
<a href="{{ request.path }}?page=1&search={{search}}">首页</a>
{% if page_obj.has_previous %}
<a href="{{ request.path }}?page={{ page_obj.previous_page_number }}&search={{search}}">上一页</a></li>
{% else %}
<a>上一页</a>
{% endif %}
{% for i in page_obj.paginator.page_range %}
<a href="{{ request.path }}?page={{ i }}&search={{search}}">{{ i }}</a>
{% endfor %}
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}&search={{search}}">下一页</a>
{% else %}
<a>下一页</a>
{% endif %}
<a href="{{ request.path }}?page={{ page_obj.paginator.num_pages }}&search={{search}}">尾页</a>
</li>
{% else %}
<li>
<a href="{{ request.path }}?page=1">首页</a>
{% if page_obj.has_previous %}
<a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">上一页</a></li>
{% else %}
<a>上一页</a>
{% endif %}
{% for i in page_obj.paginator.page_range %}
<a href="{{ request.path }}?page={{ i }}">{{ i }}</a>
{% endfor %}
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}">下一页</a>
{% else %}
<a>下一页</a>
{% endif %}
<a href="{{ request.path }}?page={{ page_obj.paginator.num_pages }}">尾页</a>
</li>
{% endif %}
django禁用formset,form表单集和表单的字段
新增供应商的时候不需要禁用表单的字段,但是修改的时候,有的东西就不能随便改了
对单个表单form禁用字段field
forms.py
from .models import Supplier
class SupplierForm(ModelForm):
class Meta:
model = Supplier
fields = '__all__'
def disable_field(self):
self.fields['unique_id'].widget.attrs['readonly'] = True
也可以使用self.fields['unique_id'].disabled = True
,但是设置disabled = True
的话,尽管修改时能够正常显示,但是提交表单的时候不会提交disabled = True
的字段,如果字段本身必填就会报错。
渲染页面前需要调用一下这个方法
views.py
def edit_sth(request,sp_id):
sp = Supplier.objects.get(pk=sp_id)
if request.method == 'GET':
sp_form = SupplierForm(instance=sp)
sp_form.disable_field()
return render(request, 'supplier/edit.html', {'sp_form': sp_form})
对表单集formset禁用字段field
表单集禁用有两个思路,定义一个表单form,一个是在初始化的时候就禁用相应的字段,然后在定义表单集的时候引用这个form。另一个是给这个form定义一个类似上文的disable_field
方法在传递表单集formset之前循环调用。
补充:还是得初始化时就禁用,否则,表单填错的时候返回的页面会导致禁用失效
法1 form初始化时禁用
form.py
from .models import Contact
from django.forms import ModelForm
class ContactForm(ModelForm):
class Meta:
model = Contact
exclude = ['supplier'] #supplier是外键,随便,这里不影响
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['name'].disabled = True
views.py
from django.forms import inlineformset_factory
def edit_sth(request,sp_id):
sp = Supplier.objects.get(pk=sp_id)
ContactFormSet = inlineformset_factory(parent_model=Supplier,model=Contact,
form=ContactForm, fields='__all__',max_num=10,extra=1)
#都需要指定表单
if request.method == 'GET':
ct_formset = ContactFormSet(instance=sp,prefix='contacts')
return render(request, 'supplier/edit.html', {'ct_formset': ct_formset,'sp_form': sp_form})
法2 循环调用方法
form.py
class ContactForm(ModelForm):
class Meta:
model = Contact
exclude = ['supplier']
def disable_sth(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['mail'].disabled = True
self.fields['phone'].widget.attrs['readonly'] = True
views.py
from django.forms import inlineformset_factory
def edit_sth(request,sp_id):
sp = Supplier.objects.get(pk=sp_id)
ContactFormSet = inlineformset_factory(parent_model=Supplier,model=Contact,
form=ContactForm, fields='__all__',max_num=10,extra=1)
#都需要指定表单
if request.method == 'GET':
ct_formset = ContactFormSet(instance=sp,prefix='contacts')
for fo in ct_formset:
fo.disable_sth()
return render(request, 'supplier/edit.html', {'ct_formset': ct_formset})
django/js 增加表单只复制元素,不复制值
做修改界面时发现的一个问题,django生成的form表单中的标签都设置好了特定的值,用js动态增加表单,原理是先复制一个现有的表单,这样会连表单的值一起复制出来。例如一个标签<input type="text" name="contacts-0-name" value="Q1" maxlength="20" id="id_contacts-0-name">
,复制后会保留value="Q1"
这个属性
一个方法是在views.py中设置extra
属性,数量不定,在复制的时候选取倒数第一个表单即可,即使倒数的表单填写了,也不会增加value
属性。
另一个方法就是把所有新增的显示的input标签找出来,删除value
属性。
还有一个需要注意的问题,多对一关系进行修改,django生成的html页面会包含隐藏的input
标签,这个标签的值是多对一关系的主键,例如,李四是供应商A的联系人,在数据库中保存这个联系人关系的主键id是23,那么会有一个隐藏的标签长这样:<input type="hidden" name="contacts-0-id" value="23" id="id_contacts-0-id">
,如果初始复制的是这条记录对应的表单,那么等于关系也被复制过去,点击保存就会报错。解决方法就是把这个input
用getElementsByName
抓出来,setAttribute
把value
的值删掉即可。
django获取数据表中某一列的值并转为list
查了好久,其实只要一个list就能解决读取出来的是QuerySet的问题
tag_list = list(Tag.objects.values_list('tag_name',flat=True))
django使用多个不同formset,动态增加表单
(1) 使用单个formset参考:https://www.brennantymrak.com/articles/django-dynamic-formsets-javascript
(2) views.py中使用多个formset:https://docs.djangoproject.com/zh-hans/3.1/topics/forms/formsets/#using-more-than-one-formset-in-a-view
使用参数
prefix
来给formset中表单的字段附上前缀,以避免多个formset的数据传到同一个视图而引起名称冲突
由于使用了多个不同的formset,首先在views.py中给表单集formset添加前缀prefix。
views.py
from django.forms import modelformset_factory
from .models import Contact,BankAccount
from .forms import SupplierForm
def add_formset(request):
ContactFormSet = modelformset_factory(Contact, exclude=('supplier',),max_num=10,extra=1)
BankAccountFormSet = modelformset_factory(BankAccount, exclude=('supplier',),max_num=10,extra=1)
if request.method == 'POST':
sp_form = SupplierForm(request.POST)
ct_formset = ContactFormSet(request.POST, request.FILES,prefix='contacts')
ba_formset = BankAccountFormSet(request.POST, request.FILES,prefix='bankaccounts')
state = all([sp_form.is_valid(),ct_formset.is_valid(),ba_formset.is_valid()])
if state:
q = sp_form.save()
formsets = ct_formset.save(commit=False)
for formset in formsets:
formset.supplier = q
formset.save()
ba_formsets = ba_formset.save(commit=False)
for formset in ba_formsets:
formset.supplier = q
formset.save()
return HttpResponse('done!')
else:
return render(request, 'supplier/add.html', {'ct_formset': ct_formset,
'ba_formset':ba_formset,
'sp_form': sp_form})
else:
sp_form = SupplierForm()
ct_formset = ContactFormSet(queryset=Contact.objects.none(),prefix='contacts')
ba_formset = BankAccountFormSet(queryset=BankAccount.objects.none(),prefix='bankaccounts')
return render(request, 'supplier/add.html', {'ct_formset': ct_formset,
'ba_formset':ba_formset,
'sp_form': sp_form})
这里直接在add_formset函数中定义了两个formset,也可以在项目的forms.py中定义。
传入传出时都需要添加prefix以区分这两个formset。
add.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>新增供应商</title>
</head>
<body>
<form id="form-container" method="POST">{% csrf_token %}
<div>
{{sp_form}}
</div>
{{ct_formset.management_form}}
{% for form in ct_formset %}
<div class="contact-form">
{{form.as_p}}
</div>
{% endfor %}
<button id="add-form" type="button">Add Another Contactor</button>
{{ba_formset.management_form}}
{% for baform in ba_formset %}
<div class="ba-form">
{{baform.as_p}}
</div>
{% endfor %}
<button id="add-ba-form" type="button">Add Another BankAccount</button>
<input type="submit" value="Save">
<script type="text/javascript">
let birdForm = document.querySelectorAll(".contact-form")
let container = document.querySelector("#form-container")
let addButton = document.querySelector("#add-form")
let totalForms = document.querySelector("#id_contacts-TOTAL_FORMS")
let formNum = birdForm.length
addButton.addEventListener('click', addForm)
function addForm(e){
e.preventDefault()
let newForm = birdForm[formNum-1].cloneNode(true)
let formRegex = RegExp(`contacts-(\\d){1}-`,'g')
newForm.innerHTML = newForm.innerHTML.replace(formRegex, `contacts-${formNum}-`)
container.insertBefore(newForm, addButton)
totalForms.setAttribute('value', `${formNum+1}`)
ct_id = document.getElementsByName(`contacts-${formNum}-id`)
ct_id[0].setAttribute('value','')
}
let baForm = document.querySelectorAll(".ba-form")
let add_baButton = document.querySelector("#add-ba-form")
let totalbaForms = document.querySelector("#id_bankaccounts-TOTAL_FORMS")
let baformNum = baForm.length
add_baButton.addEventListener('click', add_baForm)
function add_baForm(e){
e.preventDefault()
let newbaForm = baForm[baformNum-1].cloneNode(true)
let baformRegex = RegExp(`bankaccounts-(\\d){1}-`,'g')
newbaForm.innerHTML = newbaForm.innerHTML.replace(baformRegex, `bankaccounts-${baformNum}-`)
container.insertBefore(newbaForm, add_baButton)
totalbaForms.setAttribute('value', `${baformNum+1}`)
ba_id = document.getElementsByName(`bankaccounts-${baformNum}-id`)
ba_id[0].setAttribute('value','')
}
</script>
</form>
</body>
</html>
- 首先
{{xxformset.management_form}}
是必需添加的,这部分会在网页中渲染出四个隐藏的input标签,只需要用到其中一个id_prefix-TOTAL_FORMS
,里面的prefix
就是先前设置的前缀。 - 由于两个formset都需要点击增加表单,
<script>
这里要复制一下,改下变量名。整个function的大概逻辑就是,从现有的表单中复制一个新的表单,正则匹配id含有prefix的标签,由于新的表单连id都完全相同,所以把数量标志加1后替换,比如原先是id="prefix-0-title"
,现在变成id="prefix-1-title"
。最后把这个新表单插入到按钮的上方。 - 还有一个问题就是,这种方法会导致formset预设的max_num不起作用,只要一直点就能一直增加,并且符合格式的填写都会写入数据库。
- 还有一个问题,id是隐藏的,但提交的时候还会照常提交,并且复制的时候还会把id相关的隐藏input一起复制过来,所以新增的输入框还要把id值去掉,不然会有bug。具体就是找到
prefix-${baformNum}-id
这个元素,然后把值设为0ListView中使用django自带的分页器paginator、排序
views.py
```python from django.views import generic
class DetailView(generic.ListView): template_name = ‘supplier/overview.html’ context_object_name = ‘supplier’ #模型数据传入模板的名称 paginate_by = 10 #10条一页 ordering = [‘-id’] def get_queryset(self): return Supplier.objects.all()
<a name="ku39y"></a>
### overview.html
```html
<ul >
<li>
<a href="{{ request.path }}?page=1">首页</a>
{% if page_obj.has_previous %}
<a href="{{ request.path }}?&page={{ page_obj.previous_page_number }}">上一页</a></li>
{% else %}
<a>上一页</a>
{% endif %}
{% for i in page_obj.paginator.page_range %}
<a href="{{ request.path }}?page={{ i }}">{{ i }}</a>
{% endfor %}
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}">下一页</a>
{% else %}
<a>下一页</a>
{% endif %}
<a href="{{ request.path }}?page={{ page_obj.paginator.num_pages }}">尾页</a>
</li>
</ul>
django 有外键关系的表单集formset保存,内联表单集
处理这类有外键关系的模型有个专门的方法,不多赘叙
https://docs.djangoproject.com/zh-hans/3.1/topics/forms/modelforms/#inline-formsets
views.py
from django.forms import inlineformset_factory
def edit_sth(request,sp_id):
sp = Supplier.objects.get(pk=sp_id)
ContactFormSet = inlineformset_factory(parent_model=Supplier,model=Contact,
fields='__all__',max_num=10,extra=1)
#有外键关系的formset初始化不太一样
if request.method == 'GET':
sp_form = SupplierForm(instance=sp)
ct_formset = ContactFormSet(instance=sp,prefix='contacts')
return render(request, 'supplier/edit.html', {'ct_formset': ct_formset,'sp_form': sp_form})
if request.method == 'POST':
sp_form = SupplierForm(request.POST,instance=sp)
ct_formset = ContactFormSet(request.POST, request.FILES,instance=sp,prefix='contacts')
state = all([sp_form.is_valid(),ct_formset.is_valid()])
if state:
sp_form.save()
ct_formset.save()
return HttpResponse('done!')
else:
return render(request, 'supplier/edit.html', {'ct_formset': ct_formset,'sp_form': sp_form})
外键关系的表单保存
‘A’ must be a ‘B’ instance
概述
在模型中定义了两张表,一张是描述供应商的Supplier,一张是描述联系人的Contact,存在多对一的关系,即多个联系人对应一个供应商。在表单提交的时候会有保存的问题。
models.py
from django.db import models
from django.core.validators import MinLengthValidator
class Supplier(models.Model):
unique_id = models.CharField(verbose_name='社会统一信用代码',max_length=18,validators=[MinLengthValidator(18)],unique=True)
supplier_name = models.CharField(verbose_name='供应商名',max_length=100)
short_name = models.CharField(verbose_name='简称',max_length=20,blank=True,null=True)
address = models.CharField(verbose_name='地址',max_length=200,blank=True,null=True)
remarks = models.CharField(verbose_name='备注',max_length=200,blank=True,null=True)
def __str__(self):
return self.supplier_name
class Contact(models.Model):
#https://docs.djangoproject.com/zh-hans/3.1/topics/db/examples/many_to_one/
name = models.CharField(verbose_name='联系人',max_length=20)
supplier = models.ForeignKey(Supplier,to_field="unique_id", on_delete=models.CASCADE)
phone = models.CharField(verbose_name='电话',max_length=20)
forms.py
from .models import Supplier,Contact,Tag,BankAccount
from django.forms import ModelForm
class SupplierForm(ModelForm):
class Meta:
model = Supplier
fields = '__all__'
class ContactForm(ModelForm):
class Meta:
model = Contact
exclude = ['supplier']
ContactForm
剔除了supplier
字段(exclude = ['supplier']
),因为supplier
字段是外键,包含进去的话,在html中会生成一个元素来选择对应的供应商,这里会有问题,在新增供应商时需要对应新增联系人,但表单是一起提交的,只有保存表单后,新增的供应商才会存到数据库内,从而被访问选择。并且,新增的时候肯定是指定了增加这个新增供应商对应的联系人,而不是其他供应商的,所以剔除了supplier
。
两个表单都是从模型生成的,参考:https://docs.djangoproject.com/zh-hans/3.1/topics/forms/modelforms/
views.py
from django.shortcuts import render
from django.http import HttpResponse
from .forms import SupplierForm,ContactForm
def add_sth(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
sp_form = SupplierForm(request.POST)
ct_form = ContactForm(request.POST)
if sp_form.is_valid():
q = sp_form.save() #此时q就是Supplier类型的实例
print(type(q)) #<class 'supplier.models.Supplier'>
print(type(sp_form)) #<class 'supplier.forms.SupplierForm'>
print(type(sp_form.cleaned_data)) #<class 'dict'>
print(type(sp_form.cleaned_data['unique_id'])) #<class 'str'>
ct_temp = ct_form.save(commit=False)
ct_temp.supplier = sp_form.cleaned_data['unique_id'] #此处报错
ct_temp.supplier = q #这样就没问题了
ct_temp.save()
return HttpResponse('finish!')
# if a GET (or any other method) we'll create a blank form
else:
sp_form = SupplierForm()
ct_form = ContactForm()
return render(request, 'supplier/add.html', {'sp_form': sp_form,'ct_form':ct_form})
由于模型定义中,外键supplier是必需的,所以表单保存时如果缺少外键,写入数据库时会报错。因此要设置save()
方法的属性commit=False
,设置后ct_form
会保存到ct_temp
,但不会写入数据库。
此时对ct_temp
的supplier
字段赋值,由于模型定义时绑定的是unique_id
字段,所以从sp_form
中取出unique_id
。
报错
逻辑上好像也没啥问题,但还是报错了Cannot assign "'914300007607233766'": "Contact.supplier" must be a "Supplier" instance.
914300137607233766
是随便填的unique_id
,由于报错时是一对双引号套了一对单引号,我一度以为是取出的unique_id
类型有问题,毕竟正常显示话应该只有一对单引号,但实际上这里没问题,报错的格式是"赋值内容"
,由于unique_id
是str类型,所以双引号套个单引号没问题。"Contact.supplier" must be a "Supplier" instance.
此处才是问题所在,赋值给ct_temp.supplier
的应该是一个Supplier
实例。归根到底这里的表单不是直接写入数据库的,所以unique_id
是不行的。
add.html
<form method="POST">{% csrf_token %}
<div>
{{sp_form}}
</div>
<div>
{{ct_form}}
</div>
<input type="submit" value="Save">
</form>
两个表单sp_form
和ct_form
得放在<form>
标签内,否则不能提交