给表单字段指定class

1.从模型生成的表单

  1. from django.forms import ModelForm,TextInput,CheckboxInput
  2. class ContactForm(ModelForm):
  3. class Meta:
  4. model = Contact
  5. exclude = ['supplier']
  6. widgets = {
  7. 'name': TextInput(attrs={'class': 'form-control', 'placeholder': "联系人"}),
  8. 'manage': CheckboxInput(attrs={'class': 'form-check-input'}),
  9. }

2.自定义的表单

  1. from django.forms import Form,TextInput
  2. class UserForm(Form):
  3. username = forms.CharField(label="联系人",widget=TextInput(attrs={'class': 'form-control'}))

3.类似form.DELETE这种自动生成的部件

只能用js来修改

  1. <script>
  2. $(document).ready(function() {
  3. let deleteForms = document.querySelectorAll("input[id$='-DELETE']")
  4. for(i = 0; i < deleteForms.length; i++){
  5. deleteForms[i].setAttribute('class','form-check-input')
  6. }
  7. });
  8. </script>

4.其他

  1. class ContactForm(ModelForm):
  2. class Meta:
  3. model = Contact
  4. def __init__(self, *args, **kwargs):
  5. super(ContactForm, self).__init__(*args, **kwargs)
  6. instance = getattr(self, 'instance', None)
  7. if instance and instance.pk:
  8. self.fields['name'].readonly = True
  9. self.fields['industries'].widget.attrs.update({'class':"js-example-basic-multiple"})

django 后台参数传给select下拉框

  1. <select name="industry" class="js-example-basic-multiple" multiple="multiple">
  2. {% for obj in industries %}
  3. <option value="{{ obj.pk }}">{{obj}}</option>
  4. {% endfor %}

admin新增页面的外键下拉框返回筛选后的值

  1. class AuthorityAdmin(admin.ModelAdmin):
  2. model = Authority
  3. def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
  4. # 替换下拉框中的值
  5. context['adminform'].form.fields['user'].queryset = Users.objects.filter()
  6. return super(AuthorityAdmin, self).render_change_form(request, context,
  7. add, change, form_url, obj)
  8. def formfield_for_foreignkey(self,db_field,request,**kwargs):
  9. # 或者用这个,两个都能用
  10. try:
  11. if db_field.name == 'user':
  12. kwargs['queryset'] = Users.objects.filter()
  13. return super(AuthorityAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
  14. except Exception as e:
  15. 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
  1. class Authority(models.Model):
  2. #https://docs.djangoproject.com/zh-hans/3.1/topics/db/examples/one_to_one/
  3. user = models.OneToOneField("home.Users",to_field="user_ip", on_delete=models.CASCADE,unique=True)
  4. add = models.BooleanField(verbose_name='增',default=False)

admin.py
  1. @admin.register(Users)
  2. class UserAdmin(admin.ModelAdmin):
  3. search_fields = ["username"]
  4. @admin.register(Authority)
  5. class AuthorityAdmin(admin.ModelAdmin):
  6. autocomplete_fields = ['user']

更改model在admin中的名字(后台管理表名称改中文)

  1. class Industry(models.Model):
  2. class Meta:
  3. verbose_name_plural='行业'

admin界面无法搜索外键字段

报错:Related Field got invalid lookup: icontains

search_fields中使用双下划线

  1. class AuthorityAdmin(admin.ModelAdmin):
  2. model = Authority
  3. search_fields = ['user__username']
  4. admin.site.register(Authority,AuthorityAdmin)

error at line 0 错误| no such column:表名称.resource

修改model后,保存,没有migrate会引发此错误

django多对多标签渲染、多选 select2

forms.py

也包括保存

  1. class SupplierForm(ModelForm):
  2. tags = ModelMultipleChoiceField(queryset=Tag.objects.all(),label='物料范围',required=False)
  3. class Meta:
  4. model = Supplier
  5. fields = '__all__'
  6. def __init__(self, *args, **kwargs):
  7. if kwargs.get('instance'):
  8. initial = kwargs.setdefault('initial',{})
  9. initial['tags'] = [t.pk for t in kwargs['instance'].tag_set.all()]
  10. ModelForm.__init__(self,*args,**kwargs)
  11. instance = getattr(self, 'instance', None)
  12. if instance and instance.pk:
  13. self.fields['tags'].widget.attrs.update({'class':"js-example-basic-multiple"})
  14. def save(self, commit=True):
  15. instance = ModelForm.save(self,False)
  16. old_save_m2m = self.save_m2m
  17. def save_m2m():
  18. instance.tag_set.clear()
  19. instance.tag_set.add(*self.cleaned_data['tags'])
  20. self.save_m2m = save_m2m
  21. if commit:
  22. instance.save()
  23. self.save_m2m()
  24. return instance

html

  1. <head>
  2. <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  3. <link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.8/css/select2.min.css" rel="stylesheet" />
  4. <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.8/js/select2.min.js"></script>
  5. <script>
  6. $(document).ready(function() {
  7. $('.js-example-basic-multiple').select2();
  8. });
  9. </script>
  10. </head>

django多对多表单的保存

https://stackoverflow.com/questions/2216974/django-modelform-for-many-to-many-fields
重写了save方法,没有理解透,就不分析了。
主要功能:一个供应商增减多个标签。不含标签本身的增减。

ListView实现搜索和搜索后正常分页|ListView使用context|get_queryset使用request

主界面是供应商的列表,用了ListView,然后还想实现搜索

  1. from django.views import generic
  2. from .models import Supplier
  3. class DetailView(generic.ListView):
  4. template_name = 'supplier/overview.html'
  5. context_object_name = 'supplier'
  6. paginate_by = 20
  7. def get_queryset(self):
  8. if self.request:
  9. search_str = self.request.GET.get('search')
  10. else:
  11. search_str = False
  12. if search_str:
  13. sp_list = Supplier.objects.filter(supplier_name__contains=search_str)
  14. return sp_list
  15. else:
  16. return Supplier.objects.all()
  17. def get_context_data(self, **kwargs):
  18. context = super(DetailView, self).get_context_data(**kwargs)
  19. if self.request:
  20. search_str = self.request.GET.get('search')
  21. context['search'] = search_str
  22. return context

搜索后的分页,用了一个土办法

  1. {% if search %}
  2. <li>
  3. <a href="{{ request.path }}?page=1&search={{search}}">首页</a>
  4. {% if page_obj.has_previous %}
  5. <a href="{{ request.path }}?page={{ page_obj.previous_page_number }}&search={{search}}">上一页</a></li>
  6. {% else %}
  7. <a>上一页</a>
  8. {% endif %}
  9. {% for i in page_obj.paginator.page_range %}
  10. <a href="{{ request.path }}?page={{ i }}&search={{search}}">{{ i }}</a>
  11. {% endfor %}
  12. {% if page_obj.has_next %}
  13. <a href="{{ request.path }}?page={{ page_obj.next_page_number }}&search={{search}}">下一页</a>
  14. {% else %}
  15. <a>下一页</a>
  16. {% endif %}
  17. <a href="{{ request.path }}?page={{ page_obj.paginator.num_pages }}&search={{search}}">尾页</a>
  18. </li>
  19. {% else %}
  20. <li>
  21. <a href="{{ request.path }}?page=1">首页</a>
  22. {% if page_obj.has_previous %}
  23. <a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">上一页</a></li>
  24. {% else %}
  25. <a>上一页</a>
  26. {% endif %}
  27. {% for i in page_obj.paginator.page_range %}
  28. <a href="{{ request.path }}?page={{ i }}">{{ i }}</a>
  29. {% endfor %}
  30. {% if page_obj.has_next %}
  31. <a href="{{ request.path }}?page={{ page_obj.next_page_number }}">下一页</a>
  32. {% else %}
  33. <a>下一页</a>
  34. {% endif %}
  35. <a href="{{ request.path }}?page={{ page_obj.paginator.num_pages }}">尾页</a>
  36. </li>
  37. {% endif %}

django禁用formset,form表单集和表单的字段

新增供应商的时候不需要禁用表单的字段,但是修改的时候,有的东西就不能随便改了

对单个表单form禁用字段field

forms.py

  1. from .models import Supplier
  2. class SupplierForm(ModelForm):
  3. class Meta:
  4. model = Supplier
  5. fields = '__all__'
  6. def disable_field(self):
  7. self.fields['unique_id'].widget.attrs['readonly'] = True

也可以使用self.fields['unique_id'].disabled = True,但是设置disabled = True的话,尽管修改时能够正常显示,但是提交表单的时候不会提交disabled = True的字段,如果字段本身必填就会报错。
渲染页面前需要调用一下这个方法

views.py

  1. def edit_sth(request,sp_id):
  2. sp = Supplier.objects.get(pk=sp_id)
  3. if request.method == 'GET':
  4. sp_form = SupplierForm(instance=sp)
  5. sp_form.disable_field()
  6. return render(request, 'supplier/edit.html', {'sp_form': sp_form})

POST部分就不写了

对表单集formset禁用字段field

表单集禁用有两个思路,定义一个表单form,一个是在初始化的时候就禁用相应的字段,然后在定义表单集的时候引用这个form。另一个是给这个form定义一个类似上文的disable_field方法在传递表单集formset之前循环调用。
补充:还是得初始化时就禁用,否则,表单填错的时候返回的页面会导致禁用失效

法1 form初始化时禁用

form.py
  1. from .models import Contact
  2. from django.forms import ModelForm
  3. class ContactForm(ModelForm):
  4. class Meta:
  5. model = Contact
  6. exclude = ['supplier'] #supplier是外键,随便,这里不影响
  7. def __init__(self, *args, **kwargs):
  8. super(ContactForm, self).__init__(*args, **kwargs)
  9. instance = getattr(self, 'instance', None)
  10. if instance and instance.pk:
  11. self.fields['name'].disabled = True

views.py
  1. from django.forms import inlineformset_factory
  2. def edit_sth(request,sp_id):
  3. sp = Supplier.objects.get(pk=sp_id)
  4. ContactFormSet = inlineformset_factory(parent_model=Supplier,model=Contact,
  5. form=ContactForm, fields='__all__',max_num=10,extra=1)
  6. #都需要指定表单
  7. if request.method == 'GET':
  8. ct_formset = ContactFormSet(instance=sp,prefix='contacts')
  9. return render(request, 'supplier/edit.html', {'ct_formset': ct_formset,'sp_form': sp_form})

法2 循环调用方法

法2的好处在于不会影响其他需要对表单的调用

form.py
  1. class ContactForm(ModelForm):
  2. class Meta:
  3. model = Contact
  4. exclude = ['supplier']
  5. def disable_sth(self):
  6. instance = getattr(self, 'instance', None)
  7. if instance and instance.pk:
  8. self.fields['mail'].disabled = True
  9. self.fields['phone'].widget.attrs['readonly'] = True

views.py
  1. from django.forms import inlineformset_factory
  2. def edit_sth(request,sp_id):
  3. sp = Supplier.objects.get(pk=sp_id)
  4. ContactFormSet = inlineformset_factory(parent_model=Supplier,model=Contact,
  5. form=ContactForm, fields='__all__',max_num=10,extra=1)
  6. #都需要指定表单
  7. if request.method == 'GET':
  8. ct_formset = ContactFormSet(instance=sp,prefix='contacts')
  9. for fo in ct_formset:
  10. fo.disable_sth()
  11. 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">,如果初始复制的是这条记录对应的表单,那么等于关系也被复制过去,点击保存就会报错。解决方法就是把这个inputgetElementsByName抓出来,setAttributevalue的值删掉即可。

django获取数据表中某一列的值并转为list

查了好久,其实只要一个list就能解决读取出来的是QuerySet的问题

  1. 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

  1. from django.forms import modelformset_factory
  2. from .models import Contact,BankAccount
  3. from .forms import SupplierForm
  4. def add_formset(request):
  5. ContactFormSet = modelformset_factory(Contact, exclude=('supplier',),max_num=10,extra=1)
  6. BankAccountFormSet = modelformset_factory(BankAccount, exclude=('supplier',),max_num=10,extra=1)
  7. if request.method == 'POST':
  8. sp_form = SupplierForm(request.POST)
  9. ct_formset = ContactFormSet(request.POST, request.FILES,prefix='contacts')
  10. ba_formset = BankAccountFormSet(request.POST, request.FILES,prefix='bankaccounts')
  11. state = all([sp_form.is_valid(),ct_formset.is_valid(),ba_formset.is_valid()])
  12. if state:
  13. q = sp_form.save()
  14. formsets = ct_formset.save(commit=False)
  15. for formset in formsets:
  16. formset.supplier = q
  17. formset.save()
  18. ba_formsets = ba_formset.save(commit=False)
  19. for formset in ba_formsets:
  20. formset.supplier = q
  21. formset.save()
  22. return HttpResponse('done!')
  23. else:
  24. return render(request, 'supplier/add.html', {'ct_formset': ct_formset,
  25. 'ba_formset':ba_formset,
  26. 'sp_form': sp_form})
  27. else:
  28. sp_form = SupplierForm()
  29. ct_formset = ContactFormSet(queryset=Contact.objects.none(),prefix='contacts')
  30. ba_formset = BankAccountFormSet(queryset=BankAccount.objects.none(),prefix='bankaccounts')
  31. return render(request, 'supplier/add.html', {'ct_formset': ct_formset,
  32. 'ba_formset':ba_formset,
  33. 'sp_form': sp_form})

这里直接在add_formset函数中定义了两个formset,也可以在项目的forms.py中定义。
传入传出时都需要添加prefix以区分这两个formset。

add.html

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>新增供应商</title>
  7. </head>
  8. <body>
  9. <form id="form-container" method="POST">{% csrf_token %}
  10. <div>
  11. {{sp_form}}
  12. </div>
  13. {{ct_formset.management_form}}
  14. {% for form in ct_formset %}
  15. <div class="contact-form">
  16. {{form.as_p}}
  17. </div>
  18. {% endfor %}
  19. <button id="add-form" type="button">Add Another Contactor</button>
  20. {{ba_formset.management_form}}
  21. {% for baform in ba_formset %}
  22. <div class="ba-form">
  23. {{baform.as_p}}
  24. </div>
  25. {% endfor %}
  26. <button id="add-ba-form" type="button">Add Another BankAccount</button>
  27. <input type="submit" value="Save">
  28. <script type="text/javascript">
  29. let birdForm = document.querySelectorAll(".contact-form")
  30. let container = document.querySelector("#form-container")
  31. let addButton = document.querySelector("#add-form")
  32. let totalForms = document.querySelector("#id_contacts-TOTAL_FORMS")
  33. let formNum = birdForm.length
  34. addButton.addEventListener('click', addForm)
  35. function addForm(e){
  36. e.preventDefault()
  37. let newForm = birdForm[formNum-1].cloneNode(true)
  38. let formRegex = RegExp(`contacts-(\\d){1}-`,'g')
  39. newForm.innerHTML = newForm.innerHTML.replace(formRegex, `contacts-${formNum}-`)
  40. container.insertBefore(newForm, addButton)
  41. totalForms.setAttribute('value', `${formNum+1}`)
  42. ct_id = document.getElementsByName(`contacts-${formNum}-id`)
  43. ct_id[0].setAttribute('value','')
  44. }
  45. let baForm = document.querySelectorAll(".ba-form")
  46. let add_baButton = document.querySelector("#add-ba-form")
  47. let totalbaForms = document.querySelector("#id_bankaccounts-TOTAL_FORMS")
  48. let baformNum = baForm.length
  49. add_baButton.addEventListener('click', add_baForm)
  50. function add_baForm(e){
  51. e.preventDefault()
  52. let newbaForm = baForm[baformNum-1].cloneNode(true)
  53. let baformRegex = RegExp(`bankaccounts-(\\d){1}-`,'g')
  54. newbaForm.innerHTML = newbaForm.innerHTML.replace(baformRegex, `bankaccounts-${baformNum}-`)
  55. container.insertBefore(newbaForm, add_baButton)
  56. totalbaForms.setAttribute('value', `${baformNum+1}`)
  57. ba_id = document.getElementsByName(`bankaccounts-${baformNum}-id`)
  58. ba_id[0].setAttribute('value','')
  59. }
  60. </script>
  61. </form>
  62. </body>
  63. </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这个元素,然后把值设为0

    ListView中使用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()

  1. <a name="ku39y"></a>
  2. ### overview.html
  3. ```html
  4. <ul >
  5. <li>
  6. <a href="{{ request.path }}?page=1">首页</a>
  7. {% if page_obj.has_previous %}
  8. <a href="{{ request.path }}?&page={{ page_obj.previous_page_number }}">上一页</a></li>
  9. {% else %}
  10. <a>上一页</a>
  11. {% endif %}
  12. {% for i in page_obj.paginator.page_range %}
  13. <a href="{{ request.path }}?page={{ i }}">{{ i }}</a>
  14. {% endfor %}
  15. {% if page_obj.has_next %}
  16. <a href="{{ request.path }}?page={{ page_obj.next_page_number }}">下一页</a>
  17. {% else %}
  18. <a>下一页</a>
  19. {% endif %}
  20. <a href="{{ request.path }}?page={{ page_obj.paginator.num_pages }}">尾页</a>
  21. </li>
  22. </ul>

django 有外键关系的表单集formset保存,内联表单集

处理这类有外键关系的模型有个专门的方法,不多赘叙
https://docs.djangoproject.com/zh-hans/3.1/topics/forms/modelforms/#inline-formsets

views.py

  1. from django.forms import inlineformset_factory
  2. def edit_sth(request,sp_id):
  3. sp = Supplier.objects.get(pk=sp_id)
  4. ContactFormSet = inlineformset_factory(parent_model=Supplier,model=Contact,
  5. fields='__all__',max_num=10,extra=1)
  6. #有外键关系的formset初始化不太一样
  7. if request.method == 'GET':
  8. sp_form = SupplierForm(instance=sp)
  9. ct_formset = ContactFormSet(instance=sp,prefix='contacts')
  10. return render(request, 'supplier/edit.html', {'ct_formset': ct_formset,'sp_form': sp_form})
  11. if request.method == 'POST':
  12. sp_form = SupplierForm(request.POST,instance=sp)
  13. ct_formset = ContactFormSet(request.POST, request.FILES,instance=sp,prefix='contacts')
  14. state = all([sp_form.is_valid(),ct_formset.is_valid()])
  15. if state:
  16. sp_form.save()
  17. ct_formset.save()
  18. return HttpResponse('done!')
  19. else:
  20. return render(request, 'supplier/edit.html', {'ct_formset': ct_formset,'sp_form': sp_form})

外键关系的表单保存

‘A’ must be a ‘B’ instance

以上两个标题是一个问题

概述

在模型中定义了两张表,一张是描述供应商的Supplier,一张是描述联系人的Contact,存在多对一的关系,即多个联系人对应一个供应商。在表单提交的时候会有保存的问题。

models.py

  1. from django.db import models
  2. from django.core.validators import MinLengthValidator
  3. class Supplier(models.Model):
  4. unique_id = models.CharField(verbose_name='社会统一信用代码',max_length=18,validators=[MinLengthValidator(18)],unique=True)
  5. supplier_name = models.CharField(verbose_name='供应商名',max_length=100)
  6. short_name = models.CharField(verbose_name='简称',max_length=20,blank=True,null=True)
  7. address = models.CharField(verbose_name='地址',max_length=200,blank=True,null=True)
  8. remarks = models.CharField(verbose_name='备注',max_length=200,blank=True,null=True)
  9. def __str__(self):
  10. return self.supplier_name
  11. class Contact(models.Model):
  12. #https://docs.djangoproject.com/zh-hans/3.1/topics/db/examples/many_to_one/
  13. name = models.CharField(verbose_name='联系人',max_length=20)
  14. supplier = models.ForeignKey(Supplier,to_field="unique_id", on_delete=models.CASCADE)
  15. phone = models.CharField(verbose_name='电话',max_length=20)

forms.py

  1. from .models import Supplier,Contact,Tag,BankAccount
  2. from django.forms import ModelForm
  3. class SupplierForm(ModelForm):
  4. class Meta:
  5. model = Supplier
  6. fields = '__all__'
  7. class ContactForm(ModelForm):
  8. class Meta:
  9. model = Contact
  10. exclude = ['supplier']

ContactForm剔除了supplier字段(exclude = ['supplier']),因为supplier字段是外键,包含进去的话,在html中会生成一个元素来选择对应的供应商,这里会有问题,在新增供应商时需要对应新增联系人,但表单是一起提交的,只有保存表单后,新增的供应商才会存到数据库内,从而被访问选择。并且,新增的时候肯定是指定了增加这个新增供应商对应的联系人,而不是其他供应商的,所以剔除了supplier
两个表单都是从模型生成的,参考:https://docs.djangoproject.com/zh-hans/3.1/topics/forms/modelforms/

views.py

  1. from django.shortcuts import render
  2. from django.http import HttpResponse
  3. from .forms import SupplierForm,ContactForm
  4. def add_sth(request):
  5. # if this is a POST request we need to process the form data
  6. if request.method == 'POST':
  7. sp_form = SupplierForm(request.POST)
  8. ct_form = ContactForm(request.POST)
  9. if sp_form.is_valid():
  10. q = sp_form.save() #此时q就是Supplier类型的实例
  11. print(type(q)) #<class 'supplier.models.Supplier'>
  12. print(type(sp_form)) #<class 'supplier.forms.SupplierForm'>
  13. print(type(sp_form.cleaned_data)) #<class 'dict'>
  14. print(type(sp_form.cleaned_data['unique_id'])) #<class 'str'>
  15. ct_temp = ct_form.save(commit=False)
  16. ct_temp.supplier = sp_form.cleaned_data['unique_id'] #此处报错
  17. ct_temp.supplier = q #这样就没问题了
  18. ct_temp.save()
  19. return HttpResponse('finish!')
  20. # if a GET (or any other method) we'll create a blank form
  21. else:
  22. sp_form = SupplierForm()
  23. ct_form = ContactForm()
  24. return render(request, 'supplier/add.html', {'sp_form': sp_form,'ct_form':ct_form})

由于模型定义中,外键supplier是必需的,所以表单保存时如果缺少外键,写入数据库时会报错。因此要设置save()方法的属性commit=False,设置后ct_form会保存到ct_temp,但不会写入数据库。
此时对ct_tempsupplier字段赋值,由于模型定义时绑定的是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_formct_form得放在<form>标签内,否则不能提交