django07--模型层:开启事务、常用字段及参数、多对多三种创建方式、查询优化、数据库三大范式

1 django中如何开启事务

"""
事务
  特性:ACID
    A原子性   不可分割的最小单位
    C一致性   事务间的操作是一致的,同时成功或失败  跟原子性是相辅相成的
    I隔离性   事物之间互不干扰
    D持久性   事物一旦确认永久生效

  事务的回滚 
    rollback
  事务的确认
    commit
"""

# 开启事物有2中方式:with上下文的形式、装饰器的形式,
# 事务  transaction   n. 交易;事务;

# eg:以with上下文的形式为例

from django.db import transaction
try:
    with transaction.atomic():
        # sql1
        # sql2
        ...
        # 在with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
    print(e)
print('执行其他操作')
transaction.rollback()  # 回滚
transaction.commit()  # 提交

2 orm中常用字段及参数

2.1 常用字段

# 更多字段
直接参考博客:http://www.liuqingzheng.top/python/Django%E6%A1%86%E6%9E%B6/10-%E6%A8%A1%E5%9E%8B%E5%B1%82-%E5%B8%B8%E7%94%A8%E9%9D%9E%E5%B8%B8%E7%94%A8%E5%AD%97%E6%AE%B5%E5%92%8C%E5%8F%82%E6%95%B0/


# AutoField  主键字段
  int自增列  必须填入参数 primary_key=True
    当model中如果没有自增列,则自动会创建一个列名为id的列
  

# CharField	 -varchar
  verbose_name	字段的注释
  max_length	字符长度
  
    
# IntegerField	  -int
  整数类型,范围在 -2147483648 to 2147483647
# BigIntegerField    bigint


# DecimalField
  max_digits=8
  decimal_places=2


# EmailFiled	-varchar(254)


# DateField	-date
  日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例
# DateTimeField		datetime
  格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例
    
  auto_now:每次修改数据的时候都会自动更新当前时间
  auto_now_add:只在创建数据的时候记录创建时间后续不会自动修改了
    
    
# BooleanField	 - 布尔值类型
  该字段传布尔值(False/True) 	数据库里面存0/1

    
# TextField	- 文本类型
  该字段可以用来存大段内容(文章、博客...)  没有字数限制   eg:文章内容


# FileField	- 字符串类型
  upload_to = "/data"
  给该字段传一个文件对象,会自动将文件保存到/data目录下,然后将文件路径保存到数据库中
     eg: /data/a.txt

2.2 自定义字段 (了解)

# 自定义字段

# Django中CharField对应MySQL的varchar类型,没有设置对应char类型的字段

# eg:自定义对应于数据库的char类型
class MyCharField(models.Field):
    def __init__(self,max_length,*args,**kwargs):
        self.max_length = max_length
        # 调用父类的init方法
        super().__init__(max_length=max_length,*args,**kwargs)  # 一定要是关键字的形式传入

    def db_type(self, connection):
        """
        返回真正的数据类型及各种约束条件
        """
        return 'char(%s)'%self.max_length

    
# 自定义字段使用
myfield = MyCharField(max_length=16,null=True)

2.3 字段参数

# null   是否为空
  null=True 该字段可以为空

    
# unique  是否唯一
  unique=True 该字段为唯一值,不能重复
    
  ForeignKey(unique=True)   ===	OneToOneField()
  # 你在用前面字段创建一对一 orm会有一个提示信息 orm推荐你使用后者 但是前者也能用
  

# db_index  
  db_index=True 则代表着为此字段设置索引
	
    
# default
  为该字段设置默认值。default=None

    
# DateField和DateTimeField的参数
# auto_now_add
  创建数据记录的时候会把当前时间添加到数据库

# auto_now
  每次更新数据记录的时候会更新该字段 

2.4 关系字段及参数

##### ForeignKey关系字段
ForeignKey  
  外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 '一对多'中'多'的一方
  可以和其他表做关联关系,同时也可以和自身做关联关系
    
# 字段参数
# to
  to='表名' 或 表名  设置要关联的表  # 推荐字符串的形式

# to_field
  to_field='关联表的字段名'
  设置要关联表的字段  默认不写 关联的就是另外一张的主键字段

# related_name  给外键字段起别名 用于反向
  反向操作时,使用的字段名,用于代替原反向查询时的'小写表名_set'

# related_query_name
  反向查询操作时,使用的连接前缀,用于替换表名
        
# on_delete
  级联关系,当删除关联表中的数据时,当前表与其关联表的行为。
  # django1.X 默认是级联更新和级联删除,删除一个表数据,关联表的数据也删除
  # django2.X及以上版本 需要你自己指定外键字段的级联关系
    
  # 参数:
    models.CASCADE  级联更新和级联删除 
    	# eg: 作者没了,详情也没  (实际工作中,基本不会级联更新和级联删除,慎用!!!)
    models.DO_NOTHING  删除关联数据,什么都不做 (会引发错误IntegrityError,id值不存在了,什么都不做,报完整性错误)
    	# eg: 出版社没了,书还是那个出版社出版
    models.SET_NULL    删除关联数据,与之关联的值设置为null(前提FK字段需要设置可为空)  
    	# eg: 部门没了,员工没有部门(空部门)   null=True,
    models.SET_DEFAULT  删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)     
    	# eg: 部门没了,员工进入默认部门(默认值)  default=0, 
    
# db_constraint     # constraint n. 约束
  是否在数据库中创建外键约束,默认为True
	为False时, 表明外键关系只存在逻辑上的关联,没有实质的外键联系,增删时不会受外键影响,orm查询操作也不影响
    
    # 表之间 断关联
    1.表之间没有外键关联,但是有外键逻辑上关联(有充当外键的字段)
    2.断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(因为不需要做外键表的校验),且不影响增删改查操作
    3.断关联一定要通过逻辑,保证表之间数据的安全,不要出现脏数据,自己代码控制
    
    # 实际工作中,通常外键表之间设置db_constraint=False,因为若有外键约束的存在,关联数据增删改时有约束限制
    
       
##### OneToOneField关系字段
OneToOneField  一对一字段
  通常一对一字段用来扩展已有字段
  多用在当一张表的不同字段查询频次差距过大的情况下
  将本可以存储在一张表的字段拆开放置在两张表中,然后将两张表建立一对一的关联关系
  
# 字段参数
  同ForeignKey参数基本一样


   
##### ManyToManyField关系字段
ManyToManyField
  用于表示多对多的关联关系。在数据库中通过第三张表来建立关联关系
    
# 字段参数
  其他同ForeignKey参数基本一样

# symmetrical
  仅用于多对多自关联时,指定内部是否创建反向操作的字段。默认为True。

# through
  在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。

  但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。

# through_fields
  设置关联的字段

# db_table
  默认创建第三张表时,数据库中表的名称

2.5 元信息

ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息

# 主要字段如下:

# db_table
  ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名

# index_together
  联合索引

# unique_together
  联合唯一索引

# ordering
  指定默认按什么字段排序

  只有设置了该属性,我们查询到的结果才可以被reverse()

# verbose_name  admin中显示的表名称
  verbose_name='哈哈'

# verbose_name加s
  verbose_name_plural=verbose_name

2.6 choices参数

"""
用户表	
    性别
    学历
    工作经验
    是否结婚
    是否生子
    客户来源
    ...
针对某个可以列举完全的可能性字段,我们应该如何存储

格式:
    字段_choices = (
        (),
        (),
        ()
    )

只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数
"""

# models.py 

class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    
    # 性别_选项
    gender_choices = (
        (1,'男'),
        (2,'女'),
        (3,'其他'),
    )
    gender = models.IntegerField(choices=gender_choices)
    
    """
    该gender字段存的还是数字 但是如果存的数字在上面元祖列举的范围之内
    那么可以非常轻松的获取到数字对应的真正的内容
    """
   
    score_choices = (
        ('A','优秀'),
        ('B','良好'),
        ('C','及格'),
        ('D','不合格'),
    )
    # 保证字段类型跟列举出来的元祖第一个数据类型一致即可(******)
    score = models.CharField(choices=score_choices,null=True)
    
    
 # test.py

from app01 import models


# 验证:
# 1.若gender字段存的数字,不在上述元祖列举的范围内容,会如何?
    :没有列举出来的数字也能存(范围还是按照字段类型决定)

# 2.如果在 如何获取对应的中文信息?
    :固定写法 get_字段名_display()

# 存
models.User.objects.create(username='jason',age=18,gender=1)
models.User.objects.create(username='egon',age=85,gender=2)
models.User.objects.create(username='tank',age=40,gender=3)
# 存的时候 没有列举出来的数字也能存(范围还是按照字段类型决定)
models.User.objects.create(username='tony',age=45,gender=4)

# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender)   # 1
# 只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()
print(user_obj.get_gender_display())  # 男

user_obj = models.User.objects.filter(pk=4).first()
# 如果没有对应关系 那么字段是什么还是展示什么
print(user_obj.get_gender_display())  # 4
    
 

# 实际项目案例
# CRM相关内部表
class School(models.Model):
    """
    校区表
    如:
        北京沙河校区
        上海校区

    """
    title = models.CharField(verbose_name='校区名称', max_length=32)

    def __str__(self):
        return self.title

class Course(models.Model):
    """
    课程表
    如:
        Linux基础
        Linux架构师
        Python自动化开发精英班
        Python自动化开发架构师班
        Python基础班
        go基础班
    """
    name = models.CharField(verbose_name='课程名称', max_length=32)

    def __str__(self):
        return self.name

class Department(models.Model):
    """
    部门表
    市场部     1000
    销售       1001

    """
    title = models.CharField(verbose_name='部门名称', max_length=16)
    code = models.IntegerField(verbose_name='部门编号', unique=True, null=False)

    def __str__(self):
        return self.title

class UserInfo(models.Model):
    """
    员工表
    """

    name = models.CharField(verbose_name='员工姓名', max_length=16)
    email = models.EmailField(verbose_name='邮箱', max_length=64)
    depart = models.ForeignKey(verbose_name='部门', to="Department",to_field="code")
    user=models.OneToOneField("User",default=1)
    def __str__(self):
        return self.name

class ClassList(models.Model):
    """
    班级表
    如:
        Python全栈  面授班  5期  10000  2017-11-11  2018-5-11
    """
    school = models.ForeignKey(verbose_name='校区', to='School')
    course = models.ForeignKey(verbose_name='课程名称', to='Course')
    semester = models.IntegerField(verbose_name="班级(期)")


    price = models.IntegerField(verbose_name="学费")
    start_date = models.DateField(verbose_name="开班日期")
    graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)
    memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True, )

    teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo',limit_choices_to={'depart':1002})
    tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo',related_name="class_list",limit_choices_to={'depart':1006})


    def __str__(self):
        return "{0}({1}期)".format(self.course.name, self.semester)


class Customer(models.Model):
    """
    客户表
    """
    qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ号必须唯一')

    name = models.CharField(verbose_name='学生姓名', max_length=16)
    gender_choices = ((1, '男'), (2, '女'))
    gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)

    education_choices = (
        (1, '重点大学'),
        (2, '普通本科'),
        (3, '独立院校'),
        (4, '民办本科'),
        (5, '大专'),
        (6, '民办专科'),
        (7, '高中'),
        (8, '其他')
    )
    education = models.IntegerField(verbose_name='学历', choices=education_choices, blank=True, null=True, )
    graduation_school = models.CharField(verbose_name='毕业学校', max_length=64, blank=True, null=True)
    major = models.CharField(verbose_name='所学专业', max_length=64, blank=True, null=True)

    experience_choices = [
        (1, '在校生'),
        (2, '应届毕业'),
        (3, '半年以内'),
        (4, '半年至一年'),
        (5, '一年至三年'),
        (6, '三年至五年'),
        (7, '五年以上'),
    ]
    experience = models.IntegerField(verbose_name='工作经验', blank=True, null=True, choices=experience_choices)
    work_status_choices = [
        (1, '在职'),
        (2, '无业')
    ]
    work_status = models.IntegerField(verbose_name="职业状态", choices=work_status_choices, default=1, blank=True,
                                      null=True)
    company = models.CharField(verbose_name="目前就职公司", max_length=64, blank=True, null=True)
    salary = models.CharField(verbose_name="当前薪资", max_length=64, blank=True, null=True)

    source_choices = [
        (1, "qq群"),
        (2, "内部转介绍"),
        (3, "官方网站"),
        (4, "百度推广"),
        (5, "360推广"),
        (6, "搜狗推广"),
        (7, "腾讯课堂"),
        (8, "广点通"),
        (9, "高校宣讲"),
        (10, "渠道代理"),
        (11, "51cto"),
        (12, "智汇推"),
        (13, "网盟"),
        (14, "DSP"),
        (15, "SEO"),
        (16, "其它"),
    ]
    source = models.SmallIntegerField('客户来源', choices=source_choices, default=1)
    referral_from = models.ForeignKey(
        'self',
        blank=True,
        null=True,
        verbose_name="转介绍自学员",
        help_text="若此客户是转介绍自内部学员,请在此处选择内部学员姓名",
        related_name="internal_referral"
    )
    course = models.ManyToManyField(verbose_name="咨询课程", to="Course")

    status_choices = [
        (1, "已报名"),
        (2, "未报名")
    ]
    status = models.IntegerField(
        verbose_name="状态",
        choices=status_choices,
        default=2,
        help_text=u"选择客户此时的状态"
    )

    consultant = models.ForeignKey(verbose_name="课程顾问", to='UserInfo', related_name='consultanter',limit_choices_to={'depart':1001})

    date = models.DateField(verbose_name="咨询日期", auto_now_add=True)
    recv_date = models.DateField(verbose_name="当前课程顾问的接单日期", null=True)
    last_consult_date = models.DateField(verbose_name="最后跟进日期", )

    def __str__(self):
        return self.name

class ConsultRecord(models.Model):
    """
    客户跟进记录
    """
    customer = models.ForeignKey(verbose_name="所咨询客户", to='Customer')
    consultant = models.ForeignKey(verbose_name="跟踪人", to='UserInfo',limit_choices_to={'depart':1001})
    date = models.DateField(verbose_name="跟进日期", auto_now_add=True)
    note = models.TextField(verbose_name="跟进内容...")

    def __str__(self):
        return self.customer.name + ":" + self.consultant.name

class Student(models.Model):
    """
    学生表(已报名)
    """
    customer = models.OneToOneField(verbose_name='客户信息', to='Customer')
    class_list = models.ManyToManyField(verbose_name="已报班级", to='ClassList', blank=True)

    emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='紧急联系人')
    company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True)
    location = models.CharField(max_length=64, verbose_name='所在区域', blank=True, null=True)
    position = models.CharField(verbose_name='岗位', max_length=64, blank=True, null=True)
    salary = models.IntegerField(verbose_name='薪资', blank=True, null=True)
    welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True)
    date = models.DateField(verbose_name='入职时间', help_text='格式yyyy-mm-dd', blank=True, null=True)
    memo = models.CharField(verbose_name='备注', max_length=256, blank=True, null=True)

    def __str__(self):
        return self.customer.name

class ClassStudyRecord(models.Model):
    """
    上课记录表 (班级记录)
    """
    class_obj = models.ForeignKey(verbose_name="班级", to="ClassList")
    day_num = models.IntegerField(verbose_name="节次", help_text=u"此处填写第几节课或第几天课程...,必须为数字")
    teacher = models.ForeignKey(verbose_name="讲师", to='UserInfo',limit_choices_to={'depart':1002})
    date = models.DateField(verbose_name="上课日期", auto_now_add=True)

    course_title = models.CharField(verbose_name='本节课程标题', max_length=64, blank=True, null=True)
    course_memo = models.TextField(verbose_name='本节课程内容概要', blank=True, null=True)
    has_homework = models.BooleanField(default=True, verbose_name="本节有作业")
    homework_title = models.CharField(verbose_name='本节作业标题', max_length=64, blank=True, null=True)
    homework_memo = models.TextField(verbose_name='作业描述', max_length=500, blank=True, null=True)
    exam = models.TextField(verbose_name='踩分点', max_length=300, blank=True, null=True)

    def __str__(self):
        return "{0} day{1}".format(self.class_obj, self.day_num)

class StudentStudyRecord(models.Model):
    '''
    学生学习记录
    '''
    classstudyrecord = models.ForeignKey(verbose_name="第几天课程", to="ClassStudyRecord")
    student = models.ForeignKey(verbose_name="学员", to='Student')

    record_choices = (('checked', "已签到"),
                      ('vacate', "请假"),
                      ('late', "迟到"),
                      ('noshow', "缺勤"),
                      ('leave_early', "早退"),
                      )
    record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
    score_choices = ((100, 'A+'),
                     (90, 'A'),
                     (85, 'B+'),
                     (80, 'B'),
                     (70, 'B-'),
                     (60, 'C+'),
                     (50, 'C'),
                     (40, 'C-'),
                     (0, ' D'),
                     (-1, 'N/A'),
                     (-100, 'COPY'),
                     (-1000, 'FAIL'),
                     )
    score = models.IntegerField("本节成绩", choices=score_choices, default=-1)
    homework_note = models.CharField(verbose_name='作业评语', max_length=255, blank=True, null=True)
    note = models.CharField(verbose_name="备注", max_length=255, blank=True, null=True)

    homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)
    stu_memo = models.TextField(verbose_name='学员备注', blank=True, null=True)
    date = models.DateTimeField(verbose_name='提交作业日期', auto_now_add=True)

    def __str__(self):
        return "{0}-{1}".format(self.classstudyrecord, self.student)
         
"""
chocies参数使用场景是非常广泛的
"""

3 多对多创建三种方式

# 全自动: 利用orm自动帮我们创建第三张关系表
class Book(models.Model):
    name = models.CharField(max_length=32)
    authors = models.ManyToManyField(to='Author')
class Author(models.Model):
    name = models.CharField(max_length=32)
    
"""
  优点:代码不需要你写 非常的方便 还支持orm提供操作第三张关系表的方法...
  不足之处:第三张关系表的扩展性极差(没有办法额外添加字段...)
"""
    
# 纯手动: 自行创建第三张表 分别通过外键关联书和作者
class Book(models.Model):
    name = models.CharField(max_length=32)
    
class Author(models.Model):
    name = models.CharField(max_length=32)
  
class Book2Author(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
    
'''
  优点:第三张表完全取决于你自己进行额外的扩展
  不足之处:需要写的代码较多,不能够再使用orm提供的简单的方法
  不建议你用该方式
'''


# 半自动: 自己创建第三张表,并通过ManyToManyField指定关联       ---> 中介模型
class Book(models.Model):
    name = models.CharField(max_length=32)
    authors = models.ManyToManyField(to='Author',
                                     through='Book2Author',
                                     through_fields=('book','author')
                                     )
class Author(models.Model):
    name = models.CharField(max_length=32)
    # books = models.ManyToManyField(to='Book',
    #                                  through='Book2Author',
    #                                  through_fields=('author','book')
    #                                  )
class Book2Author(models.Model):
    book = models.ForeignKey(to='Book')  # 外键会自动在 字段名 后加 '_id'
    author = models.ForeignKey(to='Author')

    
"""
中介模型:through_fields字段先后顺序
  判断的本质:
    通过第三张表查询对应的表 需要用到哪个字段就把哪个字段放前面
  你也可以简化判断
    当前表是谁 就把对应的关联字段放前面
        
        
半自动:可以使用orm的正反向查询 但是没法使用add,set,remove,clear这四个方法
"""

# 总结:
  你需要掌握的是全自动和半自动 
  为了扩展性更高 一般我们都会采用半自动 (写代码要给自己留一条后路)

4 数据库查询优化

# 一、only与defer
  目的:减缓查询复杂度,提升查询优化! 
    不会将所有字段全部都一次性执行查询,需要某个字段再执行相应的语句    # 通常单表
    
    
    
# 二、select_related与prefetch_related
  目的:减少执行sql语句的命令条数,提升查询优化!   # 通常多表
    

# 三、extra
  目的:在查询条件复杂的情况下,通过 'QuerySet生成的sql语句中添加新的条件子句' 方式,额外指定新的查询条件
    
  注意:
      extra方法的核心是 让你orm的语句,额外再加上 原生的sql语句  
      但若是项目需要切换不同的sql数据库或者引擎,会导致可移植性的问题,原生的sql语句并不一定完全通用嘛   # 尽量避免使用
    
    
    
# 四、queryset 查询集的特性:
  惰性查询,是惰性执行的。
    创建 查询集 不会带来任何数据库的访问。你可以将过滤器保持一整天,直到 查询集 需要求值时,Django才会真正运行这个查询
    
    如果你仅仅只是书写了orm语句 在后面根本没有用到该语句所查询出来的参数
    那么orm会自动识别 直接不执行
		
# eg:      
res = models.Book.objects.all()  # 返回的是queryset 查询集 对象,单独该命令,不会执行的
print(res)  # 确定要使用数据了,才会执行,走数据库命令


res = models.Book.objects.get(name="红楼梦")  # 返回的是具体的book对象,orm直接执行sql语句

4.1 only与defer

# only与defer    和 all、values 对比来看 

            
# 1 .all() :会将表的所有字段都一次性通过sql命令先查出来,后续再访问字段值,就不会执行sql命令了!

    # 想要获取书籍表中所有书的名字
    res = models.Book.objects.all()   # 会将表的所有字段都一次性通过sql命令先查出来
    # print(res)  # # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>]>  此时是 queryset对象,看作是 列表套对象 的格式
    
    for d in res:
        print(d.title)  # 后续再访问字段值,就不会执行sql命令了!
        
         
# 2 .values():会将括号内的字段都一次性通过sql命令先查出来,后续再访问字段值,就不会执行sql命令了!
	
    # 想要获取书籍表中所有书的名字
    res = models.Book.objects.values('title')  # 只将'title'字段,通过sql命令查出来
    # print(res)  # <QuerySet [{'title': '三国演义爆款'}, {'title': '三国演义爆款'}]>  此时是 queryset对象,看作是 列表套字典 的格式
    
    for d in res:
        print(d.get('title'))  
    
    # 测试结果
      :res对应的sql语句,也只将字段'title'拿出来了,没办法获取其他字段的值!
      :且后续不能直接通过 res.title 获取到值,需要 字典获取值 的方式 才能拿到值!!!  
        
        
# 3 .only('字段名') : 只会将only中的字段通过sql命令查出来,后续再访问该字段值,不会执行sql命令了
                   : 但若是访问表的其他字段值 (only括号内没有的字段), 就会重新走sql命令进行查询

    # 你给我实现获取到的是一个数据对象 然后点title就能够拿到书名 并且没有其他字段
    res = models.Book.objects.only('title')  # 此时sql语句,只会查表的 'title'字段,没有其他字段的信息
    # print(res)  # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>]>  此时也是 queryset对象,看作是 列表套对象 的格式
    			# 后续能直接通过 res.title 获取到值!!! 
    
    for i in res:
        print(i.title)  # 点击only括号内的字段 就不会再走数据库 
        print(i.price)  # 点击only括号内没有的字段 会重新走数据库查询而all不需要走了

        
# 4 .defer('字段名') : 会将除了defer中的字段,全都先通过sql命令查出来,后续再访问该字段值,会重新走sql命令进行查询
			        : 但若是访问表的其他字段值 (defer括号内没有的字段), 就不会走sql命令
    			    : defer  v. 延迟	

    res = models.Book.objects.defer('title')  # 对象除了没有title属性之外其他的都有
    for i in res:
        print(i.price)
        
    # defer与only刚好相反
      defer括号内放的字段不在查询出来的对象里面 查询该字段需要重新走数据库
      而如果查询的是非括号内的字段 则不需要走数据库了
# select_related与prefetch_related     跟跨表操作有关

# 1. 一般情况下 查询
    res = models.Book.objects.all()  # 一条查询book表的sql语句命令
    for i in res:
    	print(i.publish.name)  # 每循环一次就要走执行一次sql语句命令,查询publish表
        
        
# 2. select_related()  联表查询 
    res = models.Book.objects.select_related('publish')  # 执行的是一句sql命令 联表查询 INNER JOIN
    for i in res:
    	print(i.publish.name)  # 后续关于两个表相关的数据,都不会执行sql语句命令了!
    
    """
    select_related内部直接先将book与publish表连起来 然后一次性将大表里面的所有数据 全部封装给查询出来的对象
        这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询了
    
    注意:select_related括号内只能放外键字段 (外键关系:一对多 一对一)   多对多 不行   !!!!
    """
    
    
# 3. prefetch_related()  子查询   prefetch  v. 预先载入
    res = models.Book.objects.prefetch_related('publish')  # 执行的是两句sql命令 子查询 将第一条查询结果作为第二条查询的条件
    for i in res:
        print(i.publish.name)  #  后续关于相关的数据,都不会执行sql语句命令了!
        
    """
    prefetch_related该方法内部其实就是子查询 将子查询查询出来的所有结果 也给你封装到对象中
        给你的感觉好像也是一次性搞定的

    注意:prefetch_related 主要用于 多对多 一对多 
    """

4.3 extra

### 1 前提:
有些情况下,Django的ORM查询语法,难以简单的表达 复杂的sql语句
Django提供了 extra() 'QuerySet修改机制',它能在 QuerySet生成的SQL从句中,注入新的原生sql子句


### 2 注意:  尽量少使用
这种额外的方式,对不同的数据库引擎可能存在移植性问题
因为实际是 在orm语句的基础上,额外再加上  显式的书写SQL原生语句


### 3 extra 参数    至少一个
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)


# 3.1 select参数
可在 原本SELECT从句中添加其他字段信息,是一个字典  eg: {'字段属性名': 'SQL从句'}

  # eg:
  queryResult = models.Article.objects
           .extra(select={'is_recent': "create_time > '2017-09-05'"})
        
  # 结果:
  结果集中每个Article对象都有一个额外的属性is_recent, 它是一个布尔值
  表示 Article对象的create_time 是否晚于2017-09-05



# 3.2 where 、tables 参数
可以使用 where  定义显式SQL的'WHERE'子句 
可以使用 tables 手动将表添加到SQL的'FROM'子句

where、tables 都接受字符串列表。所有 where 参数均为'与'的搜索条件

  # eg:
  queryResult = models.Article.objects
           .extra(where=['nid in (3,4) OR title like "py%" ','nid>2'])
        
  # 结果:
   相当于 在原本orm查询后的 select从句,再加了一个 sql
   '条件1:nid 为3或4,或者 title以py开头'、'条件2:nid 大于2'
   两个条件同时成立的 where子句

5 数据库设计三大范式

# 自我总结

为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则,在关系型数据库中这种规则就称为范式。

# 1.第一范式(确保每列保持原子性)
    每一列属性字段都是不可再分的属性值,确保每一列的原子性
    
    eg:个人信息表中,含有电话 或 地址字段 (不符合第一范式)
    因为,电话可以继续拆分:个人电话、家庭电话、工作电话等;地址也可以继续拆分:省、市、地区等

    
# 2.第二范式(确保表中的每列都和主键相关)
    属性字段完全依赖于主键  第二范式在第一范式的基础之上更进一层
    第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关
      主要针对联合主键而言,比如有两个主键,不能存在这样的属性,它只依赖于其中一个主键
        
    通俗解释:任意一个字段都只依赖表中的主键列,主键列与非主键列遵循完全函数依赖关系,也就是完全依赖
    
    eg:订单详细表中有(订单详细编码,订单详细名,订单编号,订单名,订单类型,商品编码,商品名,商品单价等)

  其中可以看出:
      订单详细名 → 订单详细编码
      订单名,订单类型 → 订单编号
      商品名,商品单价→ 商品编码

  其中 → 符号代表依赖,由于上述只有部分列依赖于主键,违背了第二范式 
   可以对其进行拆分成 订单详细表,订单表,商品表三个表,通过主外键进行相互关联

    
# 3.第三范式(确保每列都和主键列直接相关,而不是间接相关)
    数据不能存在传递关系,即每个属性都跟主键有直接关系而不是间接关系
      像:a-->b-->c  属性之间含有这样的关系,是不符合第三范式的
        
    通俗来说:就是外键,必须是各自单独分开的表,不能直接在一起
    
    eg:学生信息表(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)
    
    其中可以看出:
    	学号--> 所在院校 --> (院校地址,院校电话)
    正确应该拆开来,如下:
    (学号,姓名,年龄,性别,所在院校)-->(所在院校,院校地址,院校电话)
    

# 总结:
    三大范式只是一般设计数据库的基本理念,可以建立冗余较小、结构合理的数据库
    如果有特殊情况,当然要特殊对待,数据库设计最重要的是看需求跟性能,需求>性能>表结构
    所以不能一味的去追求范式建立数据库

6 MTV与MVC模型

# MTV:Django号称是MTV模型
  M:models
  T:templates
  V:views
    
# MVC:其实django本质也是MVC
  M:models
  V:views
  C:controller (urls.py...做分发)
  
# vue框架:MVVM模型

7 图书管理系统

7.1 图书增删改查

# app01.view.py

from django.shortcuts import render,redirect,HttpResponse
from app01 import models
# Create your views here.

def home(request):
    return render(request,'home.html')


def book_list(request):
    # 先查询出所有的书籍信息 传递给html页面
    book_queryset = models.Book.objects.all()
    return render(request,'book_list.html',locals())


def book_add(request):
    if request.method == 'POST':
        # 获取前端提交过来的所有数据
        title = request.POST.get("title")
        price = request.POST.get("price")
        publish_date = request.POST.get("publish_date")
        publish_id = request.POST.get("publish")
        authors_list = request.POST.getlist("authors")  # [1,2,3,4,]
        # 操作数据库存储数据
        # 书籍表
        book_obj = models.Book.objects.create(title=title,price=price,publish_date=publish_date,publish_id=publish_id)
        # 书籍与作者的关系表
        book_obj.authors.add(*authors_list)
        # 跳转到书籍的展示页面
        """
        redirect括号内可以直接写url
        其实也可以直接写别名
        
        但是如果你的别名需要额外给参数的话,那么就必须使用reverse解析了
        """
        return redirect('book_list')


    # 先获取当前系统中所有的出版社信息和作者信息
    publish_queryset = models.Publish.objects.all()
    author_queryset = models.Author.objects.all()
    return render(request,'book_add.html',locals())


def book_edit(request,edit_id):
    # 获取当前用户想要编辑的书籍对象 展示给用户看
    edit_obj = models.Book.objects.filter(pk=edit_id).first()
    if request.method == 'POST':
        title = request.POST.get("title")
        price = request.POST.get("price")
        publish_date = request.POST.get("publish_date")
        publish_id = request.POST.get("publish")
        authors_list = request.POST.getlist("authors")  # [1,2,3,4,]
        models.Book.objects.filter(pk=edit_id).update(title=title,
                                                      price=price,
                                                      publish_date=publish_date,
                                                      publish_id=publish_id
                                                      )
        # 该第三张关系表
        edit_obj.authors.set(authors_list)
        return redirect('book_list')

    publish_queryset = models.Publish.objects.all()
    author_queryset = models.Author.objects.all()
    return render(request,'book_edit.html',locals())


def book_delete(request,delete_id):
    # 简单粗暴 直接删除
    models.Book.objects.filter(pk=delete_id).delete()
    # 直接跳转到展示页
    return redirect('book_list')
posted @ 2021-09-10 01:33  Edmond辉仔  阅读(35)  评论(0编辑  收藏  举报