Django中Modelform组件
Modelforms组件主要作用
- 数据校验
- 生成html标签
- 前端页面显示错误信息
原因:使用forms组件时,需要在类中自己写字段,而这些字段与models.py中已定义字段等重复了,因此完全没有必要重新写一遍字段。Modelform组件就是把model和form组合起来。
关于label
页面中展示的label内容,有4种展示方式,并且优先级不同:
- 模型类中字段没有添加verbose_name参数,且modelform类也没有指定labels属性,那么前端页面默认显示字段的首字母大写的字段名。这个优先级最低。
- 模型类中字段有verbose_name参数,前端默认显示verbose_name参数值。优先级适中。
- modelform类中定义了labels属性,那么它的优先级高。
- 如果重写了某个字段,然后在字段的配置中的label属性优先级最高。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Author(models.Model): user = models.CharField(max_length=32, verbose_name='用户名') class AuthorModelForm(forms.ModelForm): # 例如重写user字段 user = forms.CharField( label='用户名', # 这里的label优先级最高 widget=forms.widgets.PasswordInput(), # 这里是为了让页面输入是密文 ) class Meta: model = Author fields = "__all__" labels = { "user": "第二优先级高的label" }
empty_lable
通常对于外键字段,都会有个下拉框,没有设置默认值的话,就会默认显示empty_lable值,也就是你看到的:
修改默认empty_lable
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'tian' __data__ = '2023/4/12 16:02' # software: PyCharm from django import forms from apps.web import models class BookModelForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" exclude = ['pub_date'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 方式1 # for field in self.fields.values(): # 所有字段有相同属性 # print(field) # field.widget.attrs.update({"class": "form-control"}) # 方式2 for field in iter(self.fields): print(field) self.fields[field].widget.attrs.update({"class": "form-control"}) self.fields["publish"].empty_label = "请选择"
修改后:
设置指定字段不可修改(disable)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class BookModelForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" widgets = { # 方法1 出版社字段禁止修改 "publish":forms.widgets.Select(attrs={"disabled":"disabled"}) } exclude = ["pub_date"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in iter(self.fields): self.fields[field].widget.attrs.update({"class": "form-control"}) # 法2 title字段禁止修改 self.fields['title'].widget.attrs.update({"disabled": "disabled"})
禁止修改字段:
禁止修改字段指定默认值
视图中结合initial设置默认值,不然页面中input框中值为空
views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add_book(request): pub_obj = Publish.objects.filter(pk=1) form_obj = bookmodelform.BookModelForm(initial={"title": "book1", 'publish': pub_obj.first()}) if request.method == "POST": form_obj = bookmodelform.BookModelForm(request.POST) if form_obj.is_valid(): form_obj.save() return HttpResponse("注册成功") return render(request, "add_model_book.html", {"form_obj": form_obj})
效果
关于help_text
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class BookModelForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" exclude = ["pub_date"] help_texts = { "title":"书籍名称", } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs.update({"class":"form-control"}) # 禁用每个字段的help_text field.help_text = ''
initial
在视图类中,为input框设置初始值:
def add_book(request): pub_obj = Publish.objects.filter(pk=1) form_obj = bookmodelform.BookModelForm(initial={"title": "book1", 'publish': pub_obj.first()})
modelform处理下拉菜单
外键字段显示如下规则:
- 指定显示某个出版社
- 固定显示某个出版社
- 不显示某个出版社,但保存是要能正常保存
默认显示某个出版社
需求:用户不能选择其它出版社,只能是指定出版社
方法1:forms处理,指定city=北京的出版社
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class BookModelForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" exclude = ["pub_date"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"}) field.error_messages.update({"required": "该字段不能为空"}) # 固定在前端显示哪些字段 self.fields['publish'].queryset = models.Publish.objects.filter(city="北京") # 如果你不想看到那个提示的 '---------' 就给下面的值设置为None self.fields['publish'].empty_label = None # 如果你不想让前端标签被选中,直接加disabled属性即可 self.fields['publish'].widget.attrs.update({"disabled": "disabled"})
方法2:views.py视图处理,接收到书籍对应出版社
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add(request): # 例如我们这里接收到传来的书籍对应的出版社,也就是添加的这本书必须属于某个出版社,或者让前端默认显示某个出版社 pub_pk = 3 pub_obj = Publish.objects.filter(pk=pub_pk) # initial参数实现指定出版社 form_obj = bookmodelform.BookModelForm(initial={'publish': pub_obj.first()}) if request.method == 'GET': return render(request, 'add.html', {"form_obj": form_obj}) else: form_obj = bookmodelform.BookModelForm(request.POST) if form_obj.is_valid(): # 为页面中没有填值的字段,这里额外的添加上也可以 form_obj.instance.pub_date = datetime.datetime.now().strftime('%Y-%m-%d') form_obj.save() return HttpResponse("OK") else: return render(request, 'add.html', {"form_obj": form_obj})
默认显示指定(多个)出版社
外键字段很多,但是我在视图中要根据条件,展示某些记录,就可以这么做。
views.py中调整
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add(request): # 例如我们这里接收到传来的书籍对应的出版社名字,也就是添加的这本书必须属于某几个出版社,然后让前端某人显示某几个出版社 pub_address = '华山' pub_obj = Publish.objects.filter(city=pub_address) form_obj = bookmodelform.BookModelForm(initial={'publish': pub_obj}) if request.method == 'GET': return render(request, 'add.html', {"form_obj": form_obj}) else: form_obj = bookmodelform.BookModelForm(request.POST) if form_obj.is_valid(): # 为页面中没有填值的字段,这里额外的添加上也可以 form_obj.instance.pub_date = datetime.datetime.now().strftime('%Y-%m-%d') form_obj.save() return HttpResponse("OK") else: return render(request, 'add.html', {"form_obj": form_obj})
默认不显示出版社,在保存时处理
参考处理日期字段的套路即可,就是页面压根不显示出版社信息,在后端保存书籍时,直接根据需求绑定对应的出版社。
views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add(request): form_obj = BookModelForm() if request.method == 'GET': return render(request, 'add.html', {"form_obj": form_obj}) else: form_obj = BookModelForm(request.POST) if form_obj.is_valid(): # 为页面中没有填值的字段,这里额外的添加上也可以 form_obj.instance.pub_date = datetime.datetime.now().strftime('%Y-%m-%d') # 获取从别处传来的出版社id pub_pk = 1 form_obj.instance.publish = Publisher.objects.filter(pk=pub_pk).first() form_obj.save() return HttpResponse("OK") else: return render(request, 'add.html', {"form_obj": form_obj})
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class BookModelForm(forms.ModelForm): class Meta: model = Book fields = '__all__' # widgets = { # "pub_date": forms.widgets.DateInput(attrs={"type": 'date'}) # } # 这里的日期每次都要选择,比较麻烦,又不是咱们这里重点,我就给它排除了,前端不展示了 # 数据录入时单独处理 exclude = ['pub_date', 'publish'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 为所有的字段添加相同的属性 for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"}) field.error_messages.update({"required": "该字段不能为空"})
modelform处理radio
解决modelform在处理radio时,显示------问题
方式1:forms.py中重写指定字段,例如leve1
models.py
class TestDemo(models.Model): name = models.CharField(max_length=32, unique=True) level = models.IntegerField(choices=((0, "普通会员"), (1, '黄金会员'), (2, "白金会员")), verbose_name='vip等级')
views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add(request): form_obj = bookmodelform.TestModelForm() if request.method == 'GET': return render(request, 'add.html', {"form_obj": form_obj}) else: form_obj = bookmodelform.TestModelForm(request.POST) if form_obj.is_valid(): form_obj.cleaned_data.pop('level2') form_obj.save() return HttpResponse("OK") else: return render(request, 'add.html', {"form_obj": form_obj})
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class TestModelForm(forms.ModelForm): # 重写leve1字段解决显示---问题 level = forms.CharField(widget=forms.RadioSelect(choices=((0, "普通会员"), (1, "黄金会员"), (2, "白金会员")))) class Meta: model = models.TestDemo fields = ["name", "level"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"}) field.error_messages.update({"required": "该字段不能为空"}) # 下面是控制页面标签的渲染效果 self.fields['level'].widget.attrs.update({"class": "form-radio"})
方式2:
models.py中指定default参数指
models.py
class TestDemo(models.Model): name = models.CharField(max_length=32, unique=True) level = models.IntegerField(choices=((0, "普通会员"), (1, '黄金会员'), (2, "白金会员")), verbose_name='vip等级',default=1) # 指定黄金会员为默认值
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class TestModelForm(forms.ModelForm): # level = forms.CharField(widget=forms.RadioSelect(choices=((0, "普通会员"), (1, "黄金会员"), (2, "白金会员")))) class Meta: model = models.TestDemo fields = ["name", "level"] widgets = { "level": forms.RadioSelect( attrs={"class": "form-radio"}, ) } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"}) field.error_messages.update({"required": "该字段不能为空"}) # 下面是控制页面标签的渲染效果 self.fields['level'].widget.attrs.update({"class": "form-radio"})
modelform处理日期时间字段
modelform对于日期时间字段的处理,要用到一个叫小组件,其实就是自定义一个类
示例:
models.py
from django.db import models class TestDataDemo(models.Model): create_date = models.DateField(verbose_name="创建日期") update_date = models.DateTimeField(verbose_name="更新日期")
urls.py
from django.urls import path, re_path from . import views urlpatterns = [ path('add1/', views.add1), re_path('edit1/(?P<pk>\d+)/$', views.edit1), ]
views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add_date(request): form_obj = bookmodelform.Test1ModelForm() if request.method == 'GET': return render(request, 'add.html', {"form_obj": form_obj}) else: form_obj = bookmodelform.Test1ModelForm(request.POST) if form_obj.is_valid(): form_obj.save() return HttpResponse("OK") else: return render(request, 'add.html', {"form_obj": form_obj}) def edit_date(request, pk): obj = TestDataDemo.objects.filter(id=pk).first() form_obj = bookmodelform.Test1ModelForm(instance=obj) if request.method == 'GET': return render(request, 'add.html', {"form_obj": form_obj}) else: form_obj = bookmodelform.Test1ModelForm(request.POST, instance=obj) if form_obj.is_valid(): form_obj.save() return HttpResponse("OK") else: return render(request, 'add.html', {"form_obj": form_obj})
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import forms from web import models class DateInput(forms.DateInput): """ 自定义日期类 """ input_type = "date" # 日期 2022/9/2 class DateTimeInput(forms.DateTimeInput): """ 自定义日期时间类 """ input_type = "datetime-local" # 日期时间 2022/9/2 12:12 class Test1ModelForm(forms.ModelForm): create_date = forms.DateField( widget=DateInput(format='%Y-%m-%d'), label="日期" ) update_date = forms.DateTimeField( widget=DateTimeInput(format="%Y-%m-%d %H:%M:%S"), label="日期时间" ) class Meta: model = models.TestDataDemo fields = ['create_date', 'update_date'] # widgets = { # "create_date":forms.DateTimeInput(attrs={"type":"date"}), # "update_date":forms.DateTimeInput(attrs={"type":"date"}) # } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 为所有的字段添加相同的属性 for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"})
add.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static 'plugin/bootstrap/css/bootstrap.min.css' %}"> {# <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">#} <style> input, ul, label { margin-top: 10px; } ul { padding: 0; display: flex; flex-direction: row; } ul li { list-style: none; padding: 5px; } .errorlist li { color: red; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-md-8 off-set-2"> <form action='' method="post" novalidate> {% csrf_token %} {{ form_obj }} <input type="submit" class="btn btn-danger" /> </form> </div> </div> </div> </body> </html>
bootstrap-datepicker日期控件
引入文件:
# 日期组件样式 bootstrap-datepicker/css/bootstrap-datepicker.min.css # 图标 font-awesome/css/font-awesome.min.css # 日期组件js bootstrap-datepicker/js/bootstrap-datepicker.min.js bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js
示例:
add_book.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="{% static 'plugin/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet"> <!--日期插件css样式--> <link rel="stylesheet" href="{% static 'plugin/bootstrap-datepicker/css/bootstrap-datepicker.min.css' %}"> <link rel="stylesheet" href="{% static 'plugin/font-awesome/css/font-awesome.min.css' %}"> <!--图标--> </head> <body> <div class="container"> <div class="row"> <div class="col-md-8 off-set-2"> <form action='' method="post" novalidate> {% csrf_token %} <div class="form-group"> <span> <i class="fa fa-calendar" aria-hidden="true"></i> </span> <label for="{{ form_obj.create_date.id_for_label}}">{{ form_obj.create_date.label }}</label> {{ form_obj.create_date }} </div> <div class="form-group"> <span> <i class="fa fa-calendar" aria-hidden="true"></i> </span> <label for="{{ form_obj.update_date.id_for_label}}">{{ form_obj.update_date.label }}</label> {{ form_obj.update_date }} </div> <input type="submit" class="btn btn-danger"/> </form> </div> </div> </div> <script src="{% static 'js/jquery-3.4.1.min.js' %}"></script> <script src="{% static 'plugin/bootstrap/js/bootstrap.js' %}"></script> <!--日期插件js--> <script src="{% static 'plugin/bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script> <script src="{% static 'plugin/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js' %}"></script> <script> $(function () { initDatePicker(); //初始化 }); /* 时间插件initDatePicker */ function initDatePicker() { $("#id_create_date,#id_update_date").datepicker({ format: 'yyyy-mm-dd', minView: 'month', startDate: '0', language: "zh-CN", autoclose: true, }); } </script> </body> </html>
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Test2ModelForm(forms.ModelForm): class Meta: model = models.TestDataDemo fields = "__all__" labels = { "create_date":"创建日期", "update_date":"更新日期", } # 添加属性 widgets = { "create_date": forms.DateTimeInput(attrs={"autocomplete": "off"}), "update_date": forms.DateTimeInput(attrs={"autocomplete": "off"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"}) field.widget.attrs['placeholder'] = "请输入{0}".format(field.label)
views不变
ModelForm文件上传
modelform中怎么限制file字段上传文件的后缀?比如说一定要上传zip或者rar的压缩文件
示例:
首先项目根目录下创建media目录,然后在settings.py
# 上传文件 MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
models.py
重点在这里,在validators列表中进行文件类型限制
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.db import models from django.core import validators class Asset(models.Model): user = models.CharField(max_length=32, verbose_name='用户名') avatar = models.ImageField( upload_to='avatars/', verbose_name='用户头像', default='avatars/xxx.png', validators=[validators.FileExtensionValidator(['jpg', 'png', 'jpeg'])] ) file = models.FileField( upload_to='files/', verbose_name='用户文件', default='files/xxx.txt', validators=[validators.FileExtensionValidator(['pdf', 'zip', 'rar'])] ) def __str__(self): return self.user
forms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'tian' __data__ = '2022/9/28 17:16' # software: PyCharm from django import forms from web import models class FileModelForm(forms.ModelForm): class Meta: model = models.Asset fields = ["user", "avatar", "file"] error_messages = { "file": {"invalid_extension": "必须上传文件, 且上传文件的类型必须是 pdf zip rar"}, "avatar": {"invalid_extension": "必须上传图片,且上传文件的类型必须是 jpg png jpeg"}, } labels = { "file": "文件上传", "avatar": "图片上传" } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 为所有字段添加相同属性 for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"}) field.error_messages.update({"required": "该字段不能为空"})
views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add_file(request): """添加用户信息""" if request.method == "GET": form_obj = filemodel.FileModelForm() return render(request, "add_user.html", {"form_obj": form_obj}) else: form_obj = filemodel.FileModelForm(request.POST,request.FILES) if form_obj.is_valid(): form_obj.save() return HttpResponse("OK") else: return render(request,"add_user.html",{"form_obj": form_obj})
html页面
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <style> ul li { color: red; list-style: none; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h3>文件上传</h3> <form action="" method="post" enctype="multipart/form-data" novalidate> {% csrf_token %} {{ form_obj }} <button type="submit" class="btn btn-danger">提交</button> </form> </div> </div> </div> </body> <script src="{% static 'js/jquery-3.4.1.min.js' %}"></script> <script src="{% static 'plugin/bootstrap/js/bootstrap.min.js' %}"></script> <script></script> </html>