Django models

注明

本笔记主要参考《Django应用开发实战》《Django企业开发实战》,这两本书前者详细,后者精炼。学习之后真的是感觉自己进步了很多。值得一读


文章目录

  • 注明
  • 前言
  • 一、Model的定义与数据迁移
    • ORM的概念
    • 定义模型
    • 自定义方法
    • 数据的迁移
  • QuerySet的使用
    • 常用的QuerySet接口【其返回值是QuerSet,支持链式调用】
    • 数据库的事务
  • 补充:
    • 链式调用:执行一个对象的方法之后得到的结果还是该对象,这样可以继续执行该对象上的其他方法。
    • Markdown第三方库
      • 如何使用?


前言

所谓Model就是数据模型,通过model的创建使用内置的ORM可以实现对数据库的操作。model从何而来呢?来自我们需求中整理出来的,以学员管理系统为例:用户有三级后台管理人员、教师、学生。教师的注册需要后台工作人员审核。从上面的需求我们不难整理出Model来需要有name、性别、profession身份、审核状态、创建时间、或其他个人信息等字段。对于内容或者数据驱动的项目而言,设计好的模型是成功的一半。但智者千虑必有一失,好的模型并不是一次设计好的,可以调整模型,增加字段时,要设置default或可以为空防止出现冲突。关于模型应该放在哪个App下的问题,没有具体的划分标准,可以根据业务性质、也可以放在一个app但我们的原则是易维护、易扩展。如blog项目我们可以把所有Model划分为三类:blog相关、配置相关、评论相关(便于独立维护各个模块,也便于在开发时分配任务)。还有我们应始终记住model是一个类。


一、Model的定义与数据迁移

ORM的概念

ORM即对象关系映射,代码层面对于数据库表和关系得一种抽象。Django的Model就是ORM的一个具体实现方式。我们通过Model可以定义数据库表和操作数据库表。

定义模型

from django.db import models
from user.models import MyUser
class Post(models.Model):
    STATUS_BLOCKING=2
    STATUS_NORMAL=1
    STATUS_DELETE=0
    STATUS_ITEMS=(
        (STATUS_BLOCKING, '审核中'),
        (STATUS_NORMAL,'正常'),
        (STATUS_DELETE,'删除')
    )
    title=models.CharField(max_length=255,verbose_name='文章名称')
    desc=models.CharField(max_length=1024,blank=True,null=True,verbose_name='Z摘要')
    status=models.PositiveIntegerField(choices=STATUS_ITEMS,default=STATUS_BLOCKING,verbose_name='文章状态')
    
    owner=models.ForeignKey(MyUser,verbose_name='作者',on_delete=models.CASCADE)
    created_time=models.DateTimeField(auto_now_add=True,verbose_name='创建时间')
    class Meta:
        verbose_name=verbose_name_plural='文章'
    def __str__(self):
        return self.title

Model中的字段类型跟Mysql中的字段类型相对应是ORM的基本原则。下面将简要介绍一些常用字段:

1.	数值型
AutoField() int(11)自增主键,默认提供。
BooleanField() tinyint(1)布尔类型字段,一般用来记录状态。
IntegerField() int(11) 整数类型
PositiveIntegerField int(11) 正整数类型
SmallIntegerField 小整数时会用到
2.	字符型
CharField() varchar 基础的字符类型
URLField() 继承自CharField,实现类对URL的特殊处理,即存储URL格式的字符。
UUIDField() char(32) 除了在PostgreSQL中使用uuid类型外,其他数据库中均是固定长度的char(32),用来存放唯一id
EmailField()继承CharField,实现了对Email的特殊处理。
FileField()继承自CharFiled,实现了对文件的特殊处理。使用此字段,admin会展示一个文件上传按钮。
TextField() longtext 一般用来存放大量文本.
ImageField() 继承FileField,用来处理图片相关的数据。
3.	日期型
DateField:日期类型
DateTimeField:日期时间类型
TimeField:时间类型
4.	关系型
ForeignKey一对多
OneToOneField一对一
ManyToMany多对多

大部分字段共有参数

	verbose_name:默认为None在admin站点管理设置字段的显示名称。
	primary_key:默认为False,是否为主键
	max_length:默认为None,设置字段的最大长度。
	unquie:默认false,是否唯一
	blank:默认False,数据库是否可以存储空字符串
	db_index默认False,是否以该字段创建索引。
	default:默认值
	choices:设置可选值。
	help_text:默认为空字符串,用于设置表单的提示信息。
	error_messages:默认为None

上述字段参数适用于大部分字段下面来介绍部分某些字段的特殊参数

FileField的upload_to参数,表示文件的存储路径。必须是相对路径。
日期型的auto_now和auto_now_add参数:auto_now_add = True创建时添加的时间,修改数据时,不会发生改变。auto_now = True  修改数据的时间,每次修改都会有变动。两者不能同时使用。

关系型的特殊参数

1.	to:必选参数,关联模型的名称
2.on_delete:必选参数(manytomany不能选),设置数据的删除模式(当一个被外键关联的对象被删除时,执行的相应操作)。删除模式包括CASCADE 模拟SQL语言中ON DELETE CASCADE约束,将定义有外键的模型对象同时删除(django 1.11默认值),即A中被删除时,B也被删除 (关联的那些行)、PROTECT 阻止上面的删除操作,但是弹出ProtectedError异常、SET_NULL 将外键字段设为null,只有当字段设置了null=True时,才可以使用该参数、SET_DEFAULT 将外键字段设为默认值,只有当字段设置了default参数是,才可以使用该参数、DO_NOTHING 什么也不做、SET() 设置为一个传递给SET()的值火一个回调函数的返回值。
3.related_name:用于模型之间的关联查询
4.to_field:设置外键与其他模型字段的关联性,默认为关联主键,若要关联其他字段必选具有唯一性。
5.db_table:仅限于ManyToMany,为管理和存储多对多关系的数据表设置表名称。

Meta选项:

Meta选项共有19个属性,常见的属性如下:
	abstract:若为True,则该模型为抽象模型,不会在数据库中创建数据。
	app_label:属性值为字符串,将模型设置为指定的项目应用。
	ordering:属性值为列表,将模型数据以某个字段进行排序。
	unique_together:属性值为元组,多个字段的联合唯一
	verbose_name:属性值为字符串,设置模型直观可读的名称并以复数形式表示。
	verbose_name_plural:同上,以单数形式表示。

示例

class Post(models.Model):
    STATUS_BLOCKING=2
    STATUS_NORMAL=1
    STATUS_DELETE=0
    STATUS_ITEMS=(
        (STATUS_BLOCKING, '审核中'),
        (STATUS_NORMAL,'正常'),
        (STATUS_DELETE,'删除')
    )
    title=models.CharField(max_length=255,verbose_name='文章名称')
    desc=models.CharField(max_length=1024,blank=True,null=True,verbose_name='Z摘要')
    status=models.PositiveIntegerField(choices=STATUS_ITEMS,default=STATUS_BLOCKING,verbose_name='文章状态')
    Weightiness=models.PositiveIntegerField('权重')
    owner=models.ForeignKey(MyUser,verbose_name='作者',on_delete=models.CASCADE)
    created_time=models.DateTimeField(auto_now_add=True,verbose_name='创建时间')
    class Meta:
        verbose_name=verbose_name_plural='文章'
        unique_together=(('title','owner'))
    def __str__(self):
        return self.title

__str__方法

在定义模型时,一般情况下都会重写函数__str__方法。只允许返回字符类型的数据。这个方法的触发时机与类方法相同。如在admin的显示上

写了str的方法
在这里插入图片描述

未写__str__

在这里插入图片描述

自定义方法

model首先是类,其可以自定义方法。在定义模型时添加自定义的方法,来完成某些操作或简化操作。其添加的大部分是类方法,目的是为了简化视图函数的复杂程度(把视图函数的一些操作,放在对应的model中),我们一定要保证view函数或者类尽可能的瘦。示例在CBV一文中有展示。
如我们在某个model中定义个获取全部数据的方法

def get_all(self):
    return self.objects.all()

就可以在view中调用这个方法来获取此数据库表所有数据。

数据的迁移

在项目的一级目录下(有manage.py的那一层目录)下的命令提示符中输入:

python manage.py makemigrations
python manage.py migrate

QuerySet的使用

在Django的Model中QuerySet是一个非常重要的概念,我们同数据库的所有交互都是通过它来完成的,在Model层中,Django通过个Model增加一个objects属性来提供数据操作接口。如获取某个数据库表Post所有的数据就可以这样写:Post.objects.all()。它生成的是QuerySet对象,QuerySet对象是一个懒加载对象,即你用到的时候才会他才会去产生数据库查询操作及返回结果数据,否则只会返回QuerySet对象,如循环querySet对象它才会生成数据。
示例:

pass
#自己打印xx..objects.all()并不会返回数据

常用的QuerySet接口【其返回值是QuerSet,支持链式调用】

  1. 单表or一对多的增删改
    增加:create(key=value)或者create.(**{key:value})
models.UserInfo.objects.create(
    username=user_name,sex=se_x, email=em_ail
)
models.Book.objects.create(title=” David Dubnitskiy 大卫·杜布尼茨基 复古人像”, publisher_id=1, price=12)
# publisher_id=1等价于publisher=publisher对象
author1=Author.objects.get(id=1)
book=Book.objects.get(id=1)
book.authors.add(author1)
============================增加补充=
models.UserInfo.objects.create(**{username:user_name,sex:se_x,email:em_ail})
:save方式增加数据:Blog为表名
 b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
	b2.save()
删除:delete()
Book.objects.filter(id=1).delete()#级联删除
修改:update()
方式一
b=Book.objects.get(author="oldboy")
b.price=120
b.save()
方式二
Book.objects.filter(author="yuan").update(price=999)
值得注意的是,使用save方法修改数据,出于原子性或者写入成本方面的考虑很少会用save形式修改数据。一般用update更好
  1. 多对多的增删改
增:
a = Author.objects.get(id=1)
b = Book.objects.get(id=50)
b.authors.add(a)
删:
b.authors.remove(a) 或者 
b.authors.filter(id=1).delete() 或者
b.authors.filter(id=1).clear()
注:自己创建的第三张表则
class Book_Author(models.Model):
	book=models.ForeignKey("Book")
		author=models.ForeignKey("Author")                    
         class Meta: 
unique_together = ((‘book ‘, ‘author ‘),) # 联合唯一索引
index_together =((‘book ‘, ‘author ‘),) # 联合索引(不唯一)
增加:Book_Author.objects.create(book_id=2,author_id=3):同上
  1. 查询查询相关API接口
filter(**kwargs) 它包含了与所给筛选条件相匹配的对象,返回QuerySet
all( ) 查询所有结果,返回QuerySet
get(**kwargs) 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。
  1. 对查询的结果再进行处理的接口
values( *fields) 返回一个ValuesQuerySet —— QuerySet 的一个子类,迭代时返回字典构成的列表而不是模型实例对象。每个字典表示一个对象,键对应于模型对象的属性名称。其中*fields 表示要指定的字段,默认为全指定。values_list为元组组成的列表
exclude(**kwargs) 它包含了与所给筛选条件不匹配的对象,只能在查询之后才能使用
order_by(*field):对查询结果排序、reverse()对查询结果反向排序【如果存在order_by,reverse则是倒序,如果多个排序则一一倒序】
distinct()从返回结果中剔除重复纪录
count():返回数据库中匹配查询(QuerySet)的对象数量。
first():返回第一条记录
	last():返回最后一条记录
	exists():如果QuerySet包含数据,就返回True,否则返回False

5.进阶操作

执行原生SQL
from django.db import connection, connections
cursor = connection.cursor() 默认对原数据库操作 # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone()
select_related(self, *fields):性能相关:表之间进行join连表操作,一次性获取关联的数据。*fields 参数select_related() 接受可变长参数,每个参数是需要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键需要使用两个下划线“__”来连接。
prefetch_related(self, *lookups):性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
model.tb.objects.all().select_related()
model.tb.objects.all().select_related('外键字段')
model.tb.objects.all().select_related('外键字段__外键字段')
models.UserInfo.objects.prefetch_related('外键字段')

raw( ) :raw() 执行原始sql,并返回模型实例对象。
可用于多表关联查询
返回结果必须包含实例对象对应数据库表的主键
虽然返回结果是实例对象,但是对于多表关联查询,返回结果可以包含非实例对象(关联查询表)的字段属性。
如果最终查询结果中有的字段不存在于任何实例对象或关联查询对象中则会报错[Cannot resolve keyword ‘XXX’ into field. Choices are: app_label, id, model…],即最终查询的字段必须在关联查询的表中,不能凭空捏造。

models.UserInfo.objects.raw('select * from userinfo')
models.UserInfo.objects.raw('select id as nid from 其他表')
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])

基于对象的正向查询和反向查询:一方找多方,反向查,多方找一方,是正向查,正查还是反查,关键要看外键字段在哪里
#查询某本书对应的是哪一个出版社。书籍是有重复的,但是出版社是一样的,那么查询ID=
的那本书对应的出版社

b=Book.objects.filter(id=2)[0]
chubanshe = b.publisher.name
#反向,查询ID=2这个出版社有哪些书。既然是反向,出发点一定是从表出版社出发,查到表书籍。
p=Publish.objects.filter(id=2)[0]
ret=p.book_set.values("title").distinct()book_set是一个queryset集合

基于双下划线(__)的条件查询models.Tb1.objects.filter(id__lt=10, id__gt=1)

单表:__lt小于、__gt大于、__in在。。。中、__contains包含、startswith、endswith:以指定值开头或结尾、__isnull是否为null
双表:查询关联表内容,表达:联立字段__关联表属性

models.Book.objects.filter(title=‘Python’).values(‘publisher__city’)
聚合查询(在查询集上生成聚合)、分组查询(查询集的每一项生成聚合)

1.	aggregate(*args,**kwargs) 通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。Book.objects.all().aggregate(Avg('price')) 计算所有在售书的平均价钱。
2.	annotate(*args,**kwargs): 可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。查询各个作者出的书的总价格

F查询、Q查询

F 使用查询条件的值,专门取对象中某列数的操作,减少一些操作所需的数据库查询次数
		models.Tb1.objects.update(num=F('num')+1)
		Q 封装关键字查询,借此以实现或|条件,Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。
		Q查询的&|~
from django.db.models import Q
models.Author.objects.filter(Q(name="cox") | Q(name="Tom")) # 获取在Author表中,name等于cox和name等于cox的所有数据
models.Author.objects.filter(Q(name="cox") & Q(age=12))# 获取在Author表中,name等于cox并且age等于12的所有数据
	Q查询的传入条件查询
con = Q()
q1 = Q()
q1.connector = 'OR'
q1.children.append(('name', "cox"))
q1.children.append(('name', "Tom"))
q1.children.append(('name', "Jeck"))

q2 = Q()
q2.connector = 'OR'
q2.children.append(('age', 12))

con.add(q1, 'AND')
con.add(q2, 'AND')
models.Author.objects.filter(con) # 在Author表中,name等于cox/Tom/Jeck的 并且 满足age等于12 的所有数据

数据库的事务

@transaction.atomic在视图函数里使用事务
transactionsavepoint()开启事务
savepoint_rollback回滚事务
savepoint_commit()提交事务。

补充:

链式调用:执行一个对象的方法之后得到的结果还是该对象,这样可以继续执行该对象上的其他方法。

Markdown第三方库

Markdown一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档。其依赖于Python第三方库。其相关库有很多,这里介绍其中的一个mistune。
mistune这个库使用其来非常简单,只需要传入Markdown格式文本,就会返回格式化的HTML文本。

import mistune
html=mistune.Markdown(text_string)

如何使用?

在form中,写在clean验证中

def clean_content(self):
    content=self.cleaned_data.get('content')
    if len(content)<10:
        raise forms.ValidationError('太短,可不是一个好评论')
    content = mistune.markdown(content)
    return content

在models中

class Post(models.Model):
       content=models.TextField(verbose_name='正文',help_text='正文必须为MarkDown')
    content_html=models.TextField(verbose_name='正文',help_text='正文必须为MarkDown',blank=True,editable=False)

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        self.content_html=mistune.markdown(self.content)
        super().save()

在模板中使用

{% autoescape off %}
	…….
{% endautoescape %}
配置高亮代码
<link rel="stylesheet"
href="http://cdn.bootcss.com/highlight.js/8.0/styles/monokai_sublime.min.css">
<script src="https://cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

你可能感兴趣的:(Django,django,数据库,python)