Django模型基础

    一、ORM映射关系

    ORM是Object Relational Mapping的缩写,就是对象关系映射的意思。ORM的作用就是让程序员可以以编程的方式操作数据库而不用写原生的SQL语句,它的优点是兼容各种主流数据库,不同的数据库只需要在连接方式上做些调整而操作数据库的代码不需要改动,缺点是SQL语句的优化不能达到最佳,部分操作的执行效率达不到预期需要手写原生SQL语句才能达到最佳的执行效率。另外使用ORM需要事先创建好数据库,ORM只能操作表和表中的记录。

    ORM和数据库的映射关系如下:

#表名  <-------> 类名

#字段  <-------> 属性

#表记录 <------->类实例对象

 

    二、创建表

    1、创建单表

from django.db import models
class AuthorDetail(models.Model):    #每个模型的类都必须继承models模块中的Model类
    nid = models.AutoField(primary_key=True)  #一个属性对应一个字段,字段值的类型通过models模块中的类去定义
    birthday=models.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)
    
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()

 

    2、创建一对一关系表

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()
 
    # 与AuthorDetail建立一对一的关系
    authorDetail=models.OneToOneField(to="AuthorDetail")   #通过OneToOneField定义一对一关系,to参数指定关联的表

 

    3、创建一对多关系表

class Book(models.Model):
 
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    keepNum=models.IntegerField()
    commentNum=models.IntegerField()
 
    # 与Publish建立一对多的关系,外键字段建立在多的一方
    publish=models.ForeignKey(to="Publish",to_field="nid")  #通过ForeignKey创建一对多关系,to参数指定关联表,to_field参数指定关联的字段

 

    4、创建多对多关系表

    

class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    keepNum=models.IntegerField()
    commentNum=models.IntegerField()
 
    # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
    authors=models.ManyToManyField(to='Author') #通过ManyToManyField创建多对多关系,to参数指定关联的表 



class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    keepNum=models.IntegerField()
    commentNum=models.IntegerField()
    #手动创建第三张关系表,该字段只能查询对应第三张关系表中的序号
    authors=models.ManyToManyField(through=’BToA’)

class BToA(models.Model):  #Book与Author表的多对多关系表
    Bid=models.ForeignKey(to=‘Book’,to_field=’nid’)
    Aid=models.ForeignKey(to=‘Author’,to_field=’nid’)

 

    5、通过logging查看每次操作对应的SQL语句

   在settings.py中做如下配置,会将所有执行的SQL语句输出到屏幕,也可以设置输出到文件:

import logging
from django.utils import log
from logging import handlers

LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }  

 

    注意事项:

    5.1、Django自动生成的表名为应用名_类名,可以用如下方式自定义表名

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()
    
    class Meta:
        db_table='自定义的表名'   #通过修改元数据中的db_table属性可以使用自定义的表名而不使用Django默认的表名

    5.2、使用ForeignKey建立外键的字段会以属性名_id的名字创建列

    5.3、要使用对应应用下的模型需要在settings.py中注册应用,方式与自定义过滤器和标签一样

    5.4、外键字段有一个null参数,将它设置为True表示允许外键字段为空

 

    6、字段通用参数

    每个字段的类型选项都有单独的参数,比如CharField需要max_length参数来指定VARCHAR数据库字段的大小,但是也有一些通用的参数在任何类型选项中都可以使用,以下是最常用的一些通用参数:

(1)null

如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.

(1)blank

如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。

(2)default

字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。

(3)primary_key

如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。

(4)unique

如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的

(5)choices
由二元元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。

这是一个关于 choices 列表的例子:

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)
每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或 ModelChoiceField 中用作显示的内容。 在一个给定的 model 类的实例中,想得到某个 choices 字段的显示值,就调用 get_FOO_display 方法(这里的 FOO 就是 choices 字段的名称 )。例如:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)


>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'   

 

   三、添加表记录

   1、单表添加数据

方式1 实例化之后调用save()方法

publish_obj=Publish(name="人民出版社",city="北京",email="renMin@163.com")
publish_obj.save() # 将数据保存到数据库


方式2 使用objects的create方法,返回值publish_obj是添加的记录对象

publish_obj=Publish.objects.create(name="人民出版社",city="北京",email="renMin@163.com")

 

   2、一对一和一对多中有外键字段的表添加数据

方式1:获得外键记录对象,将记录对象赋值给外键字段
   
publish_obj=Publish.objects.get(nid=1)
Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish=publish_obj)
 
方式2:外键字段加_id然后将关联表的一条记录的id值赋值给外键字段

Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish_id=1)  

 

  3、多对多表添加记录

book_obj=Book.objects.create(title="追风筝的人",publishDate="2012-11-12",price=69,pageNum=314,publish_id=1)
 
author_yuan=Author.objects.create(name="yuan",age=23,authorDetail_id=1)
author_egon=Author.objects.create(name="egon",age=32,authorDetail_id=2)
 
book_obj.authors.add(author_egon,author_yuan)    #  将某个特定的 model 对象添加到被关联对象集合中,book_obj.authors指的是第三张关系表。   =======    book_obj.authors.add(*[])
 
book_obj.authors.create()      #创建并保存一个新的Book类的对象,然后将这个对象加到被关联对象的集合中,然后返回这个新对象。


book_obj.authors.remove()     # 将某个特定的对象从被关联对象集合中去除。    ======   book_obj.authors.remove(*[])
 book_obj.authors.clear()       #清空被关联对象集合。
 

 

   4、class RelatedManager关联管理器

   关联管理器在如下两种情况下可以使用:

   4.1、一对多关系中被关联的一方

from django.db import models
 
class Reporter(models.Model): #该类的关联管理器为reporter.article_set
    # ...
    pass
 
class Article(models.Model):
    reporter = models.ForeignKey(Reporter)

 

   4.2、多对多关系中双方

class Topping(models.Model): #该类的关联管理器为topping.pizza_set
    # ...
    pass
 
class Pizza(models.Model):  #该类的关联管理器为pizza.toppings
    toppings = models.ManyToManyField(Topping)

 

  关联管理器有以下方法:

  add(obj1,...):把指定的模型对象添加到关联对象集中。

  一对多

b = Blog.objects.get(id=1)
e = Entry.objects.get(id=234)
b.entry_set.add(e) # Associates Entry e with Blog b.

 

  多对多

t=Topping.objects.get(id=1)
p=Pizza.objects.get(id=234)
t.pizza_set.add(p)
p.pizza.toppings.add(t)

 

  create(属性名=属性值,.....):创建一个新的对象,保存对象,并将它添加到关联对象集之中。返回新创建的对象

b = Blog.objects.get(id=1)
e = b.entry_set.create(
     headline='Hello',
     body_text='Hi',
     pub_date=datetime.date(2005, 1, 1)
 )
#不需要给关系定义字段传参,比如该例中的blog就是Entry表中的外键字段,不需要给它传参 
# No need to call e.save() at this point -- it's already been saved.



这完全等价于(不过更加简洁于):
 b = Blog.objects.get(id=1)
 e = Entry(
    blog=b,
    headline='Hello',
    body_text='Hi',
    pub_date=datetime.date(2005, 1, 1)
 )
 e.save(force_insert=True)

 

   set([]):重新设置关联关系,相当于clear+add操作连用,参数必须是列表形式

#多对多反向操作,正向通过关联字段
author_obj=models.Author.objects.filter(nid=5).first()
book_obj=models.Book.objects.filter(nid=3).first()
book_obj2=models.Book.objects.filter(nid=2).first()
author_obj.book_set.set([book_obj,book_obj2])

 

   remove(obj,....):从关联对象集中移除执行的模型对象:

 b = Blog.objects.get(id=1)
 e = Entry.objects.get(id=234)
 b.entry_set.remove(e) # Disassociates Entry e from Blog b.
#对于一对多表只有当外键的null参数为True时才能使用该方法

 

   clear():从关联对象集中移除一切对象

 b = Blog.objects.get(id=1)
 b.entry_set.clear()
#一对多表中只有当null参数为True时才能使用该方法
#无论remove还是clear都不会删除表中的记录,只是删除关联关系

 

   需要注意的是关联管理器的所有方法都会立刻更新数据库,所以不需要再调用save()方法

 

   直接赋值

new_list = [obj1, obj2, obj3]
e.related_set = new_list
#处理调用关联管理器的add、create、remove、clear方法外还可以用related_set直接赋值,当null=True时会将之前的关联对象全部清除再添加,否则就在原来的基础上追加

 

  四、删除表记录

  删除表记录使用QuerySet中的delete()方法

  可以删除单个对象

Entry.objects.get(id=1).delete()

 

  可以删除对象集合

Entry.objects.filter(pub_date__year=2005).delete()

 

  可以删除所有记录

Entry.objects.all().delete()

 

  如果在删除一个集合时想要使用自定义的delete()方法,需要遍历这个集合,然后每个对象调用自己类内定义的delete()方法

 

  五、修改表记录

方式一:使用save()方法
author=Author.objects.get(id=6)
author.name='tenglan'
author.save()


方式二:使用update()方法
Publisher.objects.filter(id=2).update(name='American publisher')

 

  需要注意以下几点:

  1、get()方法返回的是model对象,它没有update()方法

  2、save()方法会更新所有列,而update只会更新指定的列

  3、update对任何QuerySet结果集都有效,意味着可以同时修改多条记录,update会返回一个整数表示受影响的记录数量

  4、update()方法的返回值不能使用query属性

 

 

  六、查询表记录

   0、总结

一、添加
1、单表直接用create方法,或者实例化对象之后调用save方法
2、一对一和一对多的关联关系添加,可以通过关联字段赋值被关联表的对象或者关联字段_id赋值被关联表对象的ID值,再或者通过关联关系管理器基于对象添加
3、多对多关联关系添加只能通过关联关系管理器基于对象添加

二、跨表查询
0、无限连只适用与具体对象之间的关联关系,如果一个对象关联了多个对象则无法使用无限连。 1、基于双下划线查询是在查询条件中使用,正向查询:关联字段__被关联表的普通字段或者被关联字段的关联字段__最终查询表的普通字段;反向查询:关联表名小写__关联表普通字段或者关联表的关联字段__关联表的被关联表的普通字段或者关联字段__最终查询表的普通字段 2、基于对象查询,正向查询:model对象.关联字段.被关联表的普通字段或者关联字段.最终查询表的普通字段;反向查询:关联表名_set(一对多和多对多)|关联表名(一对一).all(),然后循环取出单个对象再.关联表普通字段或关联字段
.all(),然后循环取出单个对象再.关联表普通字段
3、当关联表的关联字段设置了related_name参数,反向查询时使用该参数的值替换表名 三、关联关系管理器 1、一对一表的关联关系无法直接解除 2、关联表修改关联关系时,被关联表中已被关联的记录无法被关联表的其他记录关联 3、一对多接触关联关系需要关联表的关联字段设置null参数为True

 

  1、查询相关的方法

<1> all():                 查询所有结果QuerySet集合,其中为一个个的model对象,返回为 补充:可以在后面使用only指定取哪些列或者用defer指定不取哪些列
 
<2> filter(**kwargs):      它包含了与所给筛选条件相匹配的对象
 
<3> get(**kwargs):         返回与所给筛选条件相匹配的对象,返回结果有且只有一个,
                           如果符合筛选条件的对象超过一个或者没有都会抛出错误。
 
<5> exclude(**kwargs):     它包含了与所给筛选条件不匹配的对象
 
<4> values(*field):        返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列
                           model的实例化对象,而是一个可迭代的字典序列
 
<9> values_list(*field):   它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
 
<6> order_by(*field):      对查询结果排序
 
<7> reverse():             对查询结果反向排序
 
<8> distinct():            从返回结果中剔除重复纪录
 
<10> count():              返回数据库中匹配查询(QuerySet)的对象数量。
 
<11> first():              返回第一条记录
 
<12> last():               返回最后一条记录
 
<13> exists():             如果QuerySet包含数据,就返回True,否则返回False

 

   2、单表查询

   单表查询时可以使用以下查询条件:

__lt:小于
__gt:大于
__eq:等于
__gte:大于等于
__lte:小于等于
__in:查询条件的值在给定的值中,表示不在其中需要使用exclude()方法进行取反
__contains:查询条件的值包含给定的值
__icontains:不区分大小写
__range:等价于bettwen...and....

__startswith:以什么开头,相当于模糊匹配中使用%进行匹配
__istartswith:不区分大小写
__endswith:以什么结尾
__iendswith:不区分大小写

 

 

  3、基于双下划线的跨表查询

  Django支持使用双下划线来进行跨表查询,格式为正向查询:关联字段1[__关联字段2]__列名;反向查询:表名__列名,该方法会自动进行相关的连表操作,第一个表名为与当前表有关联关系的表,之后的表依次与它前面的表有关联关系,最后的列名是最后一个表中的列名,比如:

查询人民出版社出版过的所有书籍的名字以及作者的姓名


# 正向查询
queryResult=Book.objects
          .filter(publish__name="人民出版社")
          .values_list("title","authors__name")
# 反向查询
queryResult=Publish.objects
          .filter(name="人民出版社")
          .values_list("book__title","book__authors__age","book__authors__name")    #Publish表与Book表有关联关系,Book表与Authors表有关联,就可以按上面的格式进行链式连接进行跨表查询

 

   注意:当定义表结构时,外键字段设置了related_name参数,则在连表时需要使用related_name参数值替换对应的表名,比如

publish = ForeignKey(Blog, related_name='bookList')


查询人民出版社出版过的所有书籍的名字与价格(一对多)
 
 # 反向查询 不再按表名:book,而是related_name:bookList
 
 queryResult=Publish.objects
           .filter(name="人民出版社")
           .values_list("bookList__title","bookList__price")

 

 4、基于对象的跨表查询

 Django除了支持双下划线跨表查询还支持用model对象进行跨表查询,格式为model对象.关联表名.列名,在一对多和多对多中被关联表去查询关联表时,被关联表的表名应使用表名_set的格式。

 一对多查询

正向查询(按字段:publish):
# 查询nid=1的书籍的出版社所在的城市
book_obj=Book.objects.get(nid=1)
print(book_obj.publish.city) # book_obj.publish 是nid=1的书籍对象关联的出版社对象  

反向查询(按表名:book_set):
# 查询人民出版社出版过的所有书籍
 publish=Publish.objects.get(name="人民出版社")
 book_list=publish.book_set.all()  # 与人民出版社关联的所有书籍对象集合
 for book_obj in book_list:
    print(book_obj.title)
 

 

  一对一查询

正向查询(按字段:authorDetail):
# 查询egon作者的手机号
author_egon=Author.objects.get(name="egon")
 print(author_egon.authorDetail.telephone)

反向查询(按表名:author):
# 查询所有住址在北京的作者的姓名
authorDetail_list=AuthorDetail.objects.filter(addr="beijing")
for obj in authorDetail_list:
      print(obj.author.name)

 

  多对多查询

正向查询(按字段:authors):
# 金瓶眉所有作者的名字以及手机号
book_obj=Book.objects.filter(title="金瓶眉").first()
authors=book_obj.authors.all()
for author_obj in authors:
      print(author_obj.name,author_obj.authorDetail.telephone)

反向查询(按表名:book_set):
# 查询egon出过的所有书籍的名字
author_obj=Author.objects.get(name="egon")
book_list=author_obj.book_set.all() #与egon作者相关的所有书籍
for book_obj in book_list:
      print(book_obj.title)

 

  注意:当一对多中的ForeignKey和ManyToMany字段设置了related_name参数,则被关联表查询关联表数据时关联表名_set就要换成related_name参数的值,比如

publish = ForeignKey(Blog, related_name='bookList')


# 查询 人民出版社出版过的所有书籍
 
publish=Publish.objects.get(name="人民出版社")
 
book_list=publish.bookList.all()  # 与人民出版社关联的所有书籍对象集合,book_set替换为booklist

 

  5、集合查询和分组查询

  Django聚合查询需要用到aggregate()方法,使用格式为QuerySet对象|表名.objects.aggregate(别名1=聚合函数1('列名1'),......),aggregate()方法返回一个字典,键名为列名_聚合函数名,值就是聚合函数计算的结果,如果起了别名,那键名就会使用别名

from django.db.models import Avg, Max, Min
Book.objects.aggregate(average_price=Avg('price'), Max('price'), Min('price'))
{'average_price': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}  #查询结果

 

   Django分组查询需要用到annotate()方法,使用格式为QuerySet对象|表名.objects.annotate(别名1=聚合函数1('列名1'),......),这种方式是对主键分组如果想对其他列分组需要先用values或filter等将列取出在后面再用annotate分组,annotate()方法返回的是QuerySet对象,如果不想返回QuerySet对象可以使用valuelist()方法,比如

统计每一个出版社的最便宜的书
方式1:返回QuerySet对象
publishList=Publish.objects.annotate(MinPrice=Min("book__price"))

for publish_obj in publishList:
    print(publish_obj.name,publish_obj.MinPrice)


方式2:使用valuelist方法
queryResult= Publish.objects
            .annotate(MinPrice=Min("book__price"))
            .values_list("name","MinPrice")
print(queryResult)


方式3:反向查询
queryResult=Book.objects.values("publish__name").annotate(MinPrice=Min('price'))
注意:values内的字段即group by的字段

 

  6、F查询和Q查询

  F查询用于同一个model模型不同字段的比较以及一些加减乘除取模的操作(F对象间或F对象与常数),比如

# 查询评论数大于收藏数的书籍
 
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum'))


#F对象的算术运算
# 查询评论数大于收藏数2倍的书籍
Book.objects.filter(commnetNum__lt=F('keepNum')*2)

Book.objects.all().update(price=F("price")+30) 

 

  Q查询用于复杂条件的查询,Q()方法产生的Q对象可以和&与、|或、~取反等操作符进行连接组合,Q对象还可以和关键字参数一起使用进行查询,但是Q对象必须在所有关键字参数的前面,比如

bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))

其中Q(authors__name="yuan")|Q(authors__name="egon")等价于WHERE name ="yuan" OR name ="egon"


#与关键字参数组合
bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),
                                  title__icontains="python"
                                 )

 

posted @ 2017-10-26 17:30  魅力宁波  阅读(215)  评论(0)    收藏  举报