ORM
在Django的MVT架构中,Model(模型)负责数据的存储和检索。模型定义了数据的结构和行为,通常是与数据库交互的部分。
📌 字段类型
🚁 AutoField
未显式的指定主键时,默认会生成一个自增的id字段,在库表里是bigint类型。
或者指定主键UUIDField
/ShortUUIDField
。
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
🚁 CharField
不能超过255个字符;长文本时需使用TextField
,在库表里是varchar类型。
EmailField
/URLField
,本质也是varchar。
属性 | 说明 |
---|---|
max_length | 最大长度 |
validators=[MinLengthValidator(6)] | 最小长度,min_length需要搭配验证器使用 |
unique=True | 唯一字段 |
null=True | 可空字段 |
blank=True | 仅表单提交验证时可为空 |
default=1 | 默认值,支持传函数,不支持lambda或者推导式 |
username = models.CharField("用户名", max_length=20)
password = models.CharField("密码", max_length=20, validators=[MinLengthValidator(6)])
email = models.CharField("邮箱", max_length=50, unique=True, blank=True)
🚁 DateTimeField
DateTimeField
:年月日时分秒DateField
:年月日TimeFiel
:时分秒- 属性
auto_now_add
与auto_now
,前者仅记录首次入库时间
createtime = models.DateTimeField("创建时间", auto_now_add=True) # 年月日时分秒
updatetime = models.DateTimeField("更新时间", auto_now=True)
birthday = models.DateField("生日")
🚁 IntegerField
其他的整型:BigIntegerField、PositiveIntergerField、SamllIntegerField、PositiveSamllIntergerField
# 通过IntegerField实现枚举字段
state = models.IntegerField("状态", choices=[
(0, '失效'),
(1, '生效'),
]
🚁 BooleanField
布尔类型,默认值为None。NullBooleanField则是可空类型。
补充
创建或修改表模型,需要执行migrate同步到库表。
python manage.py makemigrations # 创建数据库迁移脚本
python manage.py migrate # 执行迁移脚本,迁移应用的数据库表定义(更新表模型)
📌 外键
- 一对多
- 多对一
- 多对多,会建中间表存储关联信息
class LearningUser(models.Model):
"""
用户表
"""
# 不指定主键,默认会生成一个自增的id字段(在库表里是bigint类型)
# 或者指定主键UUIDField/ShortUUIDField
username = models.CharField("用户名", max_length=20) # CharField不能超过255个字符;长文本使用TextField
# Model中定义min_length需要搭配验证器使用
password = models.CharField("密码", max_length=20, validators=[MinLengthValidator(6)])
# blank=True,仅表单提交时可为空
email = models.CharField("邮箱", max_length=50, unique=True, blank=True) # 有EmailField,但本质也是varchar;URLField与之类似
# auto_now_add仅首次入库时记录时间
createtime = models.DateTimeField("创建时间", auto_now_add=True) # 年月日时分秒
# updatetime = models.DateTimeField("更新时间", auto_now=True)
# birthday = models.DateField("生日") # 年月日,TimeField则是时分秒
state = models.IntegerField("状态", choices=[
(0, '失效'),
(1, '生效'),
], default=1) # default支持传函数,不支持lambda或者推导式
# 其他类似的整型还有:BigIntegerField、PositiveIntergerField、SamllIntegerField、PositiveSamllIntergerField
# is_active = models.BooleanField() # 默认值为None;NullBooleanField则是可空类型
class Meta:
db_table = "leaning_user" # 指定表名
ordering = ["-createtime"] # 指定表默认排序
class Article(models.Model):
"""
文章表
"""
title = models.CharField("标题", max_length=50)
content = models.TextField("内容")
# 设置的外键关联如果是其他APP的,则指定app名,appname:modelname
# 当外键关联自身,则使用self
author = models.ForeignKey(LearningUser, on_delete=models.CASCADE, related_name="articles") # 外键,也是一对多
"""
on_delete可选值:
CASCADE, 级联
PROTECT, 受保护,被外键引用时不能被删除
SET_NULL, 同步设置null=True
SET_DEFAULT, 同步设置default=''
SET(), 通过set()方法指定值或方法
DO_NOTHING
"""
tags = models.ManyToManyField("Tag", related_name="articles") # 多对多,会建中间表存储关联信息
class UserExtend(models.Model):
"""
扩展信息表,一对一
"""
birthday = models.DateField("生日") # 年月日,TimeField则是时分秒
updatetime = models.DateTimeField("更新时间", auto_now=True)
user = models.OneToOneField(LearningUser, on_delete=models.CASCADE, related_name="extend")
def foreign_key_example(request):
user = LearningUser.objects.get(username='whm')
article = Article(title='测试文章', content='测试内容', author=user)
article.save()
# uas = user.article_set.all() # 若指定了related_name,则使用related_name也可以
uas = user.articles.filter(title__contains='测试文章')
tmp = ''.join([f"<p>标题:{ua.title}, 内容:{ua.content}, 作者:{ua.author.username}</p>" for ua in uas])
return HttpResponse(tmp)
📌 查询
- 查询单体:
Model.objects.get(username='whm')
- 查询所有:
Model.objects.all()
- 查询带排序:
Model.objects.order_by("-createtime")
,代表降序;或者在建表时Meta类中指定ordering,根据什么字段进行默认排序 - 反向查询,即
!=
:Model.objects.exclude(username='whm')
- 查询带条件:
Model.objects.filter(username='whm')
def query_example(request):
user = LearningUser.objects.filter(username='whm') # 精确查询,与__exact等价
user = LearningUser.objects.filter(username__ne='whm') # 反向查找(!=),但使用exclude更直观
user = LearningUser.objects.filter(username__iexact='WHM') # 精确查询但忽略大小写
user = LearningUser.objects.filter(username__regex=r'^wh') # 正则查询,大小写敏感,同理i-xx时忽略大小写
user = LearningUser.objects.filter(username__contains='h') # 大小写敏感,同理icontains时忽略大小写
user = LearningUser.objects.filter(username__startswith='wh') # 大小写敏感,同理i-xx时忽略大小写;类似的还有endswith
user = LearningUser.objects.filter(username__in=['whm', 'whm2'])
user = LearningUser.objects.filter(username__isnull=False) # 查非空
"""
还有__gt-大于,__gte-大于等于,__lt-小于,__lte-小于等于
查询指定某天;类似的还有:__year-年,__month-月,__day-日,__week-周,__week_day-星期几,__quarter-季度,
__time-时间,__hour-小时,__minute-分钟,__second-秒
或者搭配__gte,如__year__gte=2024
"""
user = LearningUser.objects.filter(createtime__date=date(2024, 7, 5))
# 如果设置TIME_ZONE = False,则无需make_aware
start = make_aware(datetime.now().replace(year=2024, month=7, day=1))
end = make_aware(datetime(year=2024, month=7, day=5, hour=23, minute=59, second=59))
user = LearningUser.objects.filter(createtime__range=(start, end)) # between ... and ...
# 关联查询
user = LearningUser.objects.filter(articles__title__contains='测试')
for u in user:
print(u.username, u.state, u.email, u.createtime)
return HttpResponse(user.query) # 打印执行的SQL
🚁 聚合与分组
- 仅聚合:
Model.objects.aggregate()
- 聚合并分组:
Model.objects.annotate()
def aggregate_example(request):
# res = LearningUser.objects.aggregate(models.Count('username', distinct=True))
# 其他聚合函数:Avg、Max、Min、Sum等
# select count(username) from LearningUser where username='whm';
res = LearningUser.objects.filter(username='whm').aggregate(models.Count('username'))
return HttpResponse(res['username__count'])
def group_by_example(request):
# select username, sum(id) total from LearningUser group by username;
res = LearningUser.objects.annotate(total=models.Sum('id')).values("username", "total")
return HttpResponse(res)
🚁 Q表达式
搭配filter()使用,支持逻辑运算符“与”、“或”、“非”,以进行复合查询。
def q_expression_example(request):
# &-与 |-或 ~-非
user = LearningUser.objects.filter(models.Q(username='whm') | models.Q(username='whm2'))
tmp = ''.join([f"<p>{u.username}, {u.state}, {u.email}, {u.createtime}</p>" for u in user])
return HttpResponse(tmp)
📌 F表达式
- 通过F表达式,在数据库层面执行字段的计算、更新或比较,而无需将数据加载到Python内存中。
- 搭配update()使用如
update(stock=F('stock') - quantity)
,确保原子性,避免竞态条件。
def f_expression_example(request):
# author_id = LearningUser.objects.get(username='whm').id
# article = Article.objects.filter(author_id=author_id)
# 以上可简化为__语法引用外键
# article = Article.objects.filter(author__username='whm')
# 优化查询性能用select_related(),预先从数据库中获取关联表的数据,以减少数据库查询的次数,从而提高查询性能
article = Article.objects.select_related('author').filter(author__username='whm')
# 使用F表达式批量拼接字符串
article.update(content=Concat(models.F('content'), models.Value('F表达式批量修改')))
tmp = ''.join([f"<p>标题:{ua.title}, 内容:{ua.content}, 作者:{ua.author.username}</p>" for ua in article])
# F表达式也可放在filter,如filter(name=F("username")),判断俩字段内容是否一致
return HttpResponse(tmp)
# 找到所有column1比column2大的记录
results = Model.objects.filter(column1__gt=F('column2'))
from django.db.models import F, DecimalField
# 相乘可能超长
total_price = Model.objects.aggregate(total=Sum(F('price') * F('quantity'), output_field=DecimalField()))
📌 表单
Form的字段类型基本与Model一致,但有些属性不相同。
class RegisterForm(forms.Form):
username = forms.CharField(label="用户名")
password = forms.CharField(label="密码", min_length=6, error_messages={"min_length": "密码不能少于6位"},
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
confirm_pw = forms.CharField(label="确认密码", min_length=6, error_messages={"min_length": "密码不能少于6位"},
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
email = forms.EmailField(label="邮箱")
"""
其他字段类型还有FloatField、IntegerField、DecimalField、FileField、ImageField、URLField、IPAddressField、SlugField、ChoiceField、
MultipleChoiceField、ModelChoiceField、ModelMultipleChoiceField、FilePathField、DateField、DateTimeField、TimeField、
DurationField、GenericIPAddressField、SplitDateTimeField、ComboField、MultiValueField、IPAddressField、FileField、ImageField、URLField
"""
# 使用正则验证
phone = forms.CharField(label="手机号", validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确')])
# 针对某个字段自定义验证
def clean_phone(self):
phone = self.cleaned_data.get("phone")
# 伪代码,数据库重复校验
if phone == "12345678910":
raise forms.ValidationError("手机号重复")
return phone
# 对多个字段进行校验
def clean(self):
cleaned_data = super().clean()
pwd1 = cleaned_data.get("password")
pwd2 = cleaned_data.get("confirm_pw")
if pwd1 != pwd2:
raise forms.ValidationError("两次密码不一致")
return cleaned_data
@require_http_methods(['GET', 'POST'])
def form_example(request):
if request.method == 'GET':
form = RegisterForm()
return render(request, 'form_example.html', context={'form': form})
else:
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
email = form.cleaned_data['email']
phone = form.cleaned_data['phone']
return HttpResponse(f"用户名:{username}, 密码:{password}, 邮箱:{email}, 手机号:{phone}")
else:
# form.errors 带html标签
# form.errors.get_json_data() 转为字典,json.loads()
# form.errors.as_json() 转为字符串,json.dumps()
errors = form.errors.get_json_data()
errormsg = ";".join([f"{key}: {value[0].get('message')}" for key, value in errors.items()])
return HttpResponse('校验不通过,' + errormsg)
<form action="" method="POST">
{{ form }}
{% csrf_token %} <!-- 403 csrf验证失败 -->
<input type="submit" value="提交">
</form>
🚁 ModelForm
通过继承ModelForm,实现表单与模型之间的自动绑定。
class RegisterModelForm(forms.ModelForm):
class Meta:
model = LearningUser
# 继承所有字段
# fields = "__all__"
# 只需要部分字段
fields = ["username", "password", "email"]
# 或者忽略部分字段
exclude = ["createtime"]
# 指定错误提示
error_messages = {
"username": {
"required": "用户名不能为空"
}
}
参考资料:
1.完整项目代码