Fork me on GitHub

Django 框架之 Models

1. 数据库配置

  • Django默认支持sqlite, mysql, oracle, postgresql 数据库:
    • Django默认使用sqlite数据库,引擎名称:django.db.backends.sqlite3
    • MySQL 引擎名称:django.db.backends.mysql
  • MySQL 驱动程序:
    • MySQLdb(mysql python)
    • mysqlclient
    • MySQL
    • PyMySQL(纯python的mysql驱动程序)
# 示例: 更改数据库
# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'books',    # 数据库名称
        'USER': 'root',     # 数据库用户名
        'PASSWORD': 'root', # 数据库密码
        'HOST': '',         # 数据库主机,留空默认为localhost
        'PORT': '3306',     # 数据库端口
    }
}

# 注意:
#   启动项目时,会报错: no module named MySQLdb
#   这是因为django默认导入的驱动是 MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是
#   PyMySQL
#解决方法:
#   __init__.py 中加入以下代码:
#   import pymysql
#   pymysql.install_as_MySQLdb()

2. ORM(对象关系映射)

  • 表的创建:
    • 作者模型: 一个作者有姓名;
    • 作者详细模型: 把作者的详情放到详情表,包含性别, email地址和出生日期。作者详情模型和作者模型之间
      是一对一的关系(one-to-noe),在大多数情况下,我们没有必要将他们拆分成两张表,这里只是引出一对一的概念。
    • 出版商模型: 出版商有名称,地址,所在城市,省,国家和网站;
    • 书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和数据的关系
      就是多对多的关联关系(many-to-many),一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系
      (one-to-many),也被称作外键。
# 实体类
# 项目名(orm)/app01/models.py
from django.db import models
class Publisher(models.Model):
    name = models.CharField(max_length=30, verbose_name='名称')
    address = models.CharField('地址', max_length=50)
    city = models.CharField('城市', max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

    class Meta:
        verbose_name = '出版商'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=30)

    def __str__(self):
        return self.name

class AuthroDetail(models.Model):
    sex = models.BooleanField(max_length=1, choices=((0, '男'), (1, '女'),))
    email = models.EmailField()
    address = models.CharField(max_length=50)
    birthday = models.DateField()
    author = models.OneToOneField(Author, on_delete=models.CASCADE)

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher, null=True, on_delete=models.SET_NULL)
    publication_date = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2, default=10)

    def __str__(self):
        return self.title

# 创建表
# iTerm 中进入 项目名(orm) 下
python3 manage.py makemigrations
python3 manage.py migrate

# 备注:
# 1. on_delete 属性说明
#       CASCADE: 级联删除
#       PROTECT: 保护模式,如果采用该选项,删除的时候,会抛出 ProtectedError 错误;
#       SET_NULL: 置空模式,删除的时候, 外键自动被设置为空, 前提就是 blank=True, null=True;
#       SET_DEFAULT: 删除的时候,外键自动设置为默认值。定义外键的时候,需要加上一个默认值;
#       SET(): 自定义一个值,该值当然只能是对应的实体;

# 2. 每个数据模型都是 django.db.models.Model 的子类, 它的父类Model包含了所有必要的和数据库交互的方法,
#   并提供了一个简洁漂亮的定义数据库字段的语法;

2.1 模型常用的字段类型参数

# CharField
    # 字符串字段,用于较短的字符串
    # CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数

# InterField: 用于保存一个整数
# FloatField: 一个浮点数, 必须提供两个参数:
    # max_digits: 总位数(不包括小数点和符号)
    # decimal_places: 小数点位数
    # 例如,要保存最大值为 999(小数点后保存2位)
    #   models.FloatField(..., max_digits=5, decimal_palces=2)

# AutoField
    # 一个 IntegerField, 添加记录时,会自动增长,通常不需要直接使用这个字段
    # 自定义一个主键: my_id=models.AutoField(primary_key=True)
    # 如果不指定主键的话,系统会自动添加一个主键字段到你的 model

# BooleanField
    # admin 用 checkbox 来表示此类字段

# TextField
    # 一个容量很大的文本字段
    # admin 用一个 <textarea> 表示该字段数据

# EmailField
    # 一个带有检查Email合法性的 CharField, 不接受 maxlength 参数

# DateField
    # 一个日期字段, 共有下列额外的可选参数:
    # Argument  描述
    # auto_now  当对象被保存时,自动将该字段的值设置为当前时间, 通常用于表示"last-modified"时间戳
    # auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间

# DateTimeField
    # 一个日期时间字段, 类似 DateField 支持同样的附加选项

# FileField
    # 一个文件上传字段
    # 要求一个必须有的参数, upload_to:用于保存上传文件的本地文件系统路径,这个路径必须包含 strftime
    # formatting: 格式化上传文件的 date/time

# ImageField
    # 类似 FileField, 不过要校验上传对象是否是一个合法图片;
    # 两个可选参数: height_field, width_field
    # 如果提供这两个参数,则图片将按提供的高度和宽度保存

# 注意: 在 model 中使用 FileField 或 ImageField, 需要以下步骤,
#   1) settings.py 中定义一个完整路径给 MEDIA_ROOT,以便让Django在此处保存上传的文件;
#   (出于性能考虑,这些文件并不保存到数据库),定义 MEDIA_URL,作为该目录的公共 URL,要确保该目录对WEB服务器
#   用户帐号是可写的
#   2) 在model添加 FileField 或 ImageField,需要确保定义了 upload_to 选项,以告诉 Django 使用
#   MEDIA_ROOT 的哪个子目录保存上传文件,数据库要保存的只是文件的路径(相对于 MEDIA_ROOT)
#   3) 可以使用Django提供的 get_<#fieldname>_url 函数,获取图像的绝对路径:
#   例如,ImageField 叫作 mug_shot, 就可以在模板中 {{ object.#get_mug_shot_url }} 获取图像

# URLField
    #   用于保存 URL, 若 veriry_exists 参数为 True(默认), 给定的 URL 会预先检查是否存在(即URL 是否
    #   被有效装入且没有返回404)

# NullBooleanField
    #   类似 BooleanField, 不过,允许NULL作为其中一个选项,推荐使用这个字段,而不要用 BooleanField 加
    #   null = True 选项
    # damin 用一个选择框 <select> (三个选择的值: "Unknow", "Yes" 和 "No") 来表示这种字段数据

# SlugField
    # "Slug" 是一个报纸术语, slug 是某个东西的小小标记(短签),只包含字母,数字,下划线和连字符;
    # 它们通常用于 URLs;
    # maxlength 默认值为 50

# XMLField
    # 校验值是否为合法XML的 TextField,
    # 必须提供参数: schema_path
    # 它是一个用来校验文本的 RelaxNGschema 的文件系统路径

# FilePathField
    # 某个特定目录下的文件名,支持三个特殊的参数,其中第一个是必须提供的.
    # path: 必需参数,一个目录的绝对文件系统路径.
    # match: 可选参数, 一个正则表达式,作为一个字符串, FilePathField 将使用它过滤文件名.
    # 这个正则表达式只会应用到 base filename 而不是路径全名
    # recursive 可选参数, 要么True,要么False, 默认值是 False. 是否包括 path 下面的全部子目录.

# IPAddressField
    # 一个字符串形式的 IP 地址, 例如 '127.0.0.1'

# CommaSeparatedIntegerField
    # 用于存放逗号分隔的整数值, 必须要有 maxlength 参数;

2.2 Field 重要参数

null: 数据库中字段是否可以是空

blank: django 的 Admin 中添加数据时,是否允许空值

default: 设定缺省值

editable: 如果为假, admin 模式下将不能改写, 缺省为真;

primary_key: 设置主键, 如果没有设置,Django 创建表时,会自动加上;

unique: 数据唯一;

verbose_name: Admin 中字段的显示名称;

validator_list: 有效性检查, 非有效产生 django.core.validators.ValidationError 错误

db_column, db_index: 如果为真,将为此字段创建索引

choices: 一个用来选择值的二维数组,第一个值是实际存储的值, 第二个用来备选;

3. 表的操作

3.1 增(create, save)

from app01.models import *

    # create 方式一: Author.objects.create(name='Jame')

    # create 方式二: Author.objects.create(**{'name':'Jame'})
    # 以字典的方式插入,前面需要添加两个星号

    # save 方式一:
        author = Author(name='Jame')
        author.save()

    # save 方式二:
        author=Author()
        author.name='Jame'
        author.save()


# 如何处理外键关系的字段,如一对多的publisher和多对多的authors

# 一对多(ForeignKey):
    # 方式一: 由于绑定一对多的字段,例如publisher,存到数据库中的字段为 publisher_id,
    # 所以我们可以直接给这个字段设定对应值:
    Book.objects.create(title='Python 入门',
                        publisher_id=2,
                        publication_date='2015-4-18',
                        price=98)

    # 方式二:
    #      首先,获取绑定的 Publisher对象
    pub_obj = Publisher(name='河北教育出版社',address='保定',city='保定',
              state_province='河北',country='China',website='http://www.edu.com')
    或
    pub_obj=Publisher.objects.get(id=1)

    # 将 publisher_id=2 改为 publisher=pub_obj

# 多对多(ManyToManyField):

    author1 = models.Author.objects.get(id=1)
    author2 = models.Author.objects.filter(name='zhangsan')[0]
    book = models.Book.objects.get(id=1)
    book.author.add(author1, author2)       # book.author 表示book绑定的 author 对象集合

    # 等同于
    ----------- 正向查询(从拥有外键的表开始查找)
    book.author.add(*[author1, author2])
    book.author.remove(*[author1, author2])

    ----------- 反向查询
    book=models.Book.objects.filter(id__gt=1)       # id__gt 表示 id大于
    authors=models.Author.objects.filter(id=1)[0]
    authors.book_set.add(*book)         # Author 类里面没有与 book 相关的属性
    authors.book_set.remove(*book)


# 注意: 如果第三张表是通过 models.ManyToManyField() 自动创建的, 那么绑定关系只有上面一种方式
#      如果第三张表是自己创建的:
    class Book2Author(models.Model):
        author = models.ForeignKey("Author")
        book = models.ForeignKey("Book")

        # 绑定联合唯一索引
        class Meta:
            unique_together=["author", "book"]


    # 保存数据
    s = models.Book2Author.objects.create(author_id = 1, Book_id = 2)
    s.save()

3.2 删除(delete)

# 单表:
Book.objects.filter(id=1).delete()

# 备注: 表面上删除了一条信息,实际却删除了三条,因为我们删除的这本书在 Book_authors 表中有两条
#  相关信息,这种删除方式就是 django 默认的级联删除。


# 多对多关系: remove() 和 clear() 方法
# 正向
book = models.Book.objects.filter(id=1)

# 删除第三张表中和id=1关联的所有关联信息
book.author.clear()     # 清除与book中 id=1 关联的所有数据
book.author.remove(*[1, 2, 3, 4])


# 反向
author = models.Author.objects.filter(id=1)
author.book_set.clear()     # 清除与book中 id=1 关联的所有数据

3.3 修改(update 和 save)

# 单表:
# 方式一: update 方法直接设定对应属性
    Book.objects.filter(id=2).update(title='Python')    # 不能用 get(id=2)

    # SQL
    #   UPDATE 'app01_book' SET 'title' = 'Python' WHERE 'app01_book'.'id'=2; args=('Python', 2)

    # 不能用get的原因是: update 是 QuerySet对象的方法, get返回的是一个model对象,它没有 update 方法,
    # 而 filter 返回的是一个 QuerySet 对象;


# 方式二: save 方法会将所有属性重新设定一遍, 效率低
    obj=models.Book.objects.filter(id=2)[0]
    obj.title='Python'
    obj.save()

# SQL:
# SELECT 'app01_book'.'id','app01_book'.'title','app01_book'.'price','app01_book'.'color',
# 'app01_book'.'page_num','app01_book'.'publisher_id' FROM 'app01_book'
# WHERE 'app01_book'.'id'=3 LIMIT 1;

# UPDATE 'app01_book' SET 'title'='Python','price'=22,'color'='red','page_num'=223,
# 'publisher_id'=1 WHERE 'app01_book'.'id'=3;

3.3.1 控制台输出SQL语句

# setting 加上日志记录部分
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level': 'DEBUG',
        },
    }
}

3.4 查询(filter, valued等)

  1. 查询相关API:
    • filter(**kwargs): 包含了与所给筛选条件相匹配的对象;
    • all(): 查询所有结果;
    • get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的
      对象超过一个或者没有都会抛出错误。
  2. 对查询的结果进行再处理(例如,objects.filter.values())
    • values(*field): 返回一个ValueQuerySet,一个特殊的QuerySet,运行后得到的并不是一系列
      model的实例化对象,而是一个可迭代的字典序列;
    • exclude(**kwargs): 包含了与所给筛选条件不匹配的对象;
    • order_by(*field): 对查询结果排序;
    • reverse(): 对查询结果反向排序;
    • distinct(): 从返回结果中剔除重复记录;
    • values_list(*field): 返回一个元组序列;
    • count(): 返回数据库中匹配查询(QuerySet)的对象数量;
    • first(): 返回第一条记录;
    • last(): 返回最后一条记录;
    • exists(): 如果QuerySet包含数据,就返回 True, 否则返回 False。
# 补充:
# 扩展查询,有时候Django的查询API不能方便的设置查询条件,提供了另外的扩展查询方法extra:
# extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

1) q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-12-29'"})
   q = q.extra(order_by=['-is_recent'])     # 负号表示逆序

2) Blog.objects.extra(
    select = SortedDict([('a', '%s'), ('b', '%s')]),
    select_params=('one', 'two'))

3) Entry.objects.extra(where=['headline=%s'], params=['Lennon'])

3.4.1 QuerySet

  1. 惰性机制: Publisher.objects.all()或者.filter()等都只是返回一个QuerySet(查询结果集对象),
    它并不会马上执行SQL, 而是当调用QuerySet的时候才执行。
  2. QuerySet的特点:
    • 可迭代的
    • 可切片
# 示例:
objs = models.Book.objects.all()    #[obj1, obj2, obj3...]

# 可迭代
    for obj in objs:        # 每一个obj,就是一个行对象
        print('obj:',obj)

# 可切片
    print(objs[1])
    print(objs[1:4])
    print(objs[::-1])


# QuerySet 是具有cache的
    # 当遍历QuerySet时,所有匹配的记录会从数据库获取,然后转换成Django的model,这被称为执行(evaluation)
    # 这些model会保存在QuerySet内置的cache中,如果再次遍历这个QuerySet,不需要再去数据库查询
    objs=models.Book.Objects.filter(id=3)

    for i in objs:
        prin(i)

    # SQL 语句只运行一次

    for i in objs:
        print(i)


# 当QuerySet非常巨大时,cache会成为问题
    # 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的QuerySet可能会锁住系统
    # 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生QuerySet cache, 可以使用 iterator()
    # 方法来获取数据,处理完数据就将其丢弃
    objs=Book.objects.all().iterator()      # 迭代器对象

    for obj in objs:
        print(obj.name)

    # 当然,使用iterator()方法来防止生成cache, 意味着遍历同一个QuerySet时,会重复执行查询。所以使用
    # iterator()的时候要当心,确保代码在操作一个大的QuerySet时,没有重复执行查询。

3.4.2 单表条件查询 多表条件关联查询

# 示例:了不起的双下划线(__)之单表条件查询

models.Tb1.objects.filter(id__lt=10, id__gt=1)      # 获取id大于1, 且小于10的值

models.Tb1.objects.filter(id__in=[11, 22, 33])      # 获取id等于11, 22, 33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])     # not in

models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven")    # icontains 表示大小写不敏感

models.Tb1.objects.filter(id__range=[1, 2])         # 范围 between and

startswith, istartswith,  endswith,  iendswith


# 示例: 多表条件关联查询

# 正向条件查询
models.Book.objects.filter(title="Python").values("id")

# 一对多
models.Book.objects.filter(title="Python").values("publisher__city")

# 多对多
models.Book.objects.filter(title="Python").values("author__name")
models.Book.objects.filter(author__name="zhangsan").values("title")

3.4.3 聚合查询和分组查询

# aggregate(*args, **kwargs):

from django.db.models import Avg, Min, Sum, Max

# 查询图书价格的平均值
Book.objects.all().aggregate(Avg('price'))      # {'price__avg': 20}

# 查询图书价格的最大值,最小值
Book.objects.all().aggregate(Avg('price'), Max('price'), Min('price'))

# 输出: {'price__avg': 20, 'price__max':Decimal('81.20'), 'price__min': Decimal('12.99')}

# aggregate() 是 QuerySet 的一个终止子句,意思是说, 它返回一个包含一些键值对的字典。键的名称是聚合值
# 的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定
# 一个名称,可以向聚合子句提供
Book.objects.all().aggregate(average_price=Avg('price'))


# annotate(*args, **kwargs)    分组查询
# 查询各个作者出的书的总价格,这里就涉及到分组了, 分组条件是 authors__name
Book.objects.values("authors__name").annotte(Sum("price"))

# 查询各个出版社最便宜的书价是多少
Book.objects.values("publisher__name").annotate(Min("price"))

3.4.4 F 查询和 Q 查询

# 示例一: F 查询
# F 使用查询条件的值,专门取对象中某列值的操作, 例如,将书的价格增加 10
from django.db.models import F
models.Book.objects.update(price=F('price')+10)


# 示例二: Q 构建搜索条件
from django.db.models import Q

# Q 对象可以对关键字参数进行封装,从而更好地应用多个查询
models.Book.objects.filter(Q(title__startswith='P')).all()

# 可以组合使用 &, | 操作符,当一个操作符用于两个Q的对象,它产生一个新的Q对象
Q(title__startswith='P') | Q(title__startswith='J')

# Q 对象可以用 ~ 操作符放在前面表示否定, 也可允许否定与肯定形式的组合
Q(title__startswith='P') | ~Q(pub_update__year=2005)


models.Book.objects.get(
    Q(title__startswith='P'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 10, 1))
)

# Q对象可以与关键字参数查询一起使用,不过,一定要把Q对象放在关键字参数查询的前面:
# 正确:
models.Book.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 10, 1)),
    title__startswith='P')

# 错误:
models.Book.objects.get(
    title__startswith='P',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 10, 1))
)

4. admin 的配置

  • admin 是 Django 强大功能之一,它能从数据库中读取数据,呈现在页面上,进行管理。
  • 注册model类到admin的两种方式:
    • 使用register方法:admin.site.register(Book, MyAdmin)
    • 使用register的装饰器:@admin.register(Book)
# 示例:
# 一些常用的设置:
        # list_display: 指定要显示的字段
        # search_fields: 指定搜索的字段
        # list_filter: 指定列表过滤器
        # ordering: 指定排序字段

from django.contrib import admin
from app01.models import *

# 注册model
class MyAdmin(admin.ModelAdmin):
    list_display = ("title", "price", "publisher")
    search_fields = ("title", "publisher")
    list_filter = ("publisher",)
    ordering = ("price",)
    fieldsets = [
        (None,                {'fields': ['title']}),
        ('price information', {'fields': ['price', 'publisher'], 'classes': ['collapse']}),
    ]

admin.site.register(Book, Admin)
admin.site.register(Publish)
admin.site.register(Author)

参考资料:

posted @ 2018-04-02 07:08  小a的软件思考  阅读(266)  评论(0编辑  收藏  举报