淘先锋技术网

首页 1 2 3 4 5 6 7

数据库的CRUD

  • Create——新增
  • Read——读/查询
  • Update——修改
  • Update——修改

模型的创建

from django.db import models


class CommonModule(models.Model):
    # 自定义模型的基类
    create_time = models.DateTimeField('注册时间', auto_now_add=True)
    update_time = models.DateTimeField('修改时间', auto_now=True)

    class Meta:
        abstract = True


class User(CommonModule):
    USER_STATUS = (
        (1, '正常'),
        (0, '删除')
    )

    username = models.CharField('用户名', max_length=128, unique=True)
    password = models.CharField('密码',max_length=256)
    nickname = models.CharField('昵称', max_length=256, null=True, blank=True)
    avatar = models.ImageField('用户头像', upload_to='avatar', null=True, blank=True)
    status = models.SmallIntegerField('用户状态', default=1, choices=USER_STATUS)
    is_super = models.BooleanField('是否为超级用户', default=False)

    class Meta:
        db_table = 'accounts_user'


class UserProfile(CommonModule):
    # 用户详细信息
    SEX_CHOICES = (
        (0, '未知'),
        (1, '男'),
        (2, '女')
    )
    user = models.OneToOneField('User', verbose_name='关联用户',
                                on_delete=models.CASCADE,
                                related_name='profile')
    username = models.CharField('用户名', max_length=128, unique=True)
    real_name = models.CharField('用户名', max_length=128, null=True, blank=True)
    sex = models.SmallIntegerField('用户性别', default=0, choices=SEX_CHOICES)
    maxim = models.CharField('用户格言', max_length=128, null=True, blank=True)
    address = models.CharField('用户地址', max_length=128, null=True, blank=True)

    class Meta:
        db_table = 'accounts_user_profile'
        

class LoginHistory(models.Model):
    user = models.ForeignKey(User, related_name='login_history_list',
                             on_delete=models.CASCADE,
                             verbose_name='关联的用户')
    username = models.CharField('用户名', max_length=128)
    login_type = models.CharField('账号平台', max_length=128)
    ip = models.CharField('IP地址', max_length=32, default='')
    ua = models.CharField('登陆来源', max_length=128, default='')
    create_time = models.DateTimeField('注册时间', auto_now_add=True)
    
    class Meta:
        db_table = 'accounts_login_history'
        # 倒叙排列
        ordering = ['-create_time']

使用ORM实现数据新增

1.Django shell

从控制台(terminal)进入 python manage.py shell
在这里插入图片描述
从pyCharm进入
在这里插入图片描述

2.使用ORM新增数据

2.1 使用save()保存数据

author=Author(name='aaaa')
author.save()
或
author=Author()
author.name="bbb"
author.save()

2.2 使用create()新增数据

使用create只需要一行代码,更简单。

Author.objects.create(name='test')
或
info={'sex':0,'email':'[email protected]','address':'上海','author_id':1}
AuthorDetail.objects.create(**info)

2.3 使用bulk_create()批量新增数据

user1 = User(username='admin', password='password')
user2 = User(username='manager', password='password')
user_list = [user1, user2]
User.objects.bulk_create(user_list)

3.外键关联数据的插入

举例:记录用户登录的历史
在这里插入图片描述

user = User(username='admin', password='password')
LoginHistory.objects.create(user=user,*args, **kwargs)

使用ORM修改数据

使用save()修改单条数据

user_obj = User.objects.get(pk=1)
user_obj.nickname = '管理员'
user_obj.save()

使用update()批量修改数据

count = User.objects.all().update(*args, **kwargs)
count
>>> 3
或
author=Author.objects.filter(id=1).update(name='yqh')

注意不能修改外键关联的对象。

使用bulk_update()批量修改数据

obj:需要修改的记录列表
fields:指定需要修改的字段
batch_size:每次提交多少条记录进行修改

使用ORM删除数据

1.使用ORM物理删除数据

使用模型的delete()删除数据,

  • 删除单条数据
user_obj = User.objects.get(pk=1)
user_obj.delete()
  • 删除多条数据(批量删除)

2.使用ORM逻辑删除数据

实际上是做了修改的操作。

物理删除和逻辑删除

物理删除:将数据从数据库删除,删除后找不回来,不占磁盘空间。
逻辑删除:将数据标记删除,删除后还可以恢复,占用磁盘空间,删除后通过查询条件不展示给用户。

如果是多对多的关系: remove()和clear()方法

book = Book.objects.filter(id=1)
book.author.clear()        #清空与book中id=1 关联的所有数据
book.author.remove(2)  #可以为id
book.author.remove(*[1,2,3,4])     #可以为列表,前面加*
author = Author.objects.filter(id=1)
author.book_set.clear() #清空与book中id=1 关联的所有数据

使用ORM查询

返回QuerySet对象:

  • all()
  • filter()
  • exclude()
  • order_by()
  • reverse()
  • distinct()

返回特殊QuerySet

  • values():返回一个可迭代的字典序列
  • values_list():返回一个可迭代的元祖序列

返回具体对象

  • get()
  • first()
  • last()

返回布尔值

  • exists()

返回数字

  • count()

1.查询单条数据

  • get(**kwargs): 按照查询条件返回单条数据
  • latest(*fields)/earliest(*fields) 返回最晚/最早的一条记录

使用get()查询单条数据

Author.objects.get(name='aaaa')
<Author: aaaa>

使用latest()和earliest()查询数据

latest = LoginHistory.objects.latest('created_at')
latest
>>> <LoginHistory: LoginHistory object (4)>
earliest = LoginHistory.objects.earliest('created_at')
earliest
>>> <LoginHistory: LoginHistory object (1)>

注意:

  1. 注意异常的处理:
  • DoesNotExist:查询的记录不存在
  • MultipleObjectsReturned:查询的记录有多条
  1. get_or_create()
    若有则返回,若无则创建后返回。
  1. get_object_or_404(klass, *args, **kwargs)
    如果没有则触发404异常。

2.使用all()查询所有数据

Author.objects.all()
<QuerySet [<Author: yang>, <Author: test>, <Author: aaaa>, <Author: bbb>]>

3.filter()

筛选出满足条件的多条记录

Author.objects.filter(name='aaaa')
<QuerySet [<Author: aaaa>]>

4.exclude()

exclude(**kwargs):   它包含了与所给筛选条件不匹配的对象
Book.objects.all().exclude(title='shell')
<QuerySet [<Book: python>]>
Book.objects.exclude(title='shell')
<QuerySet [<Book: python>]>

5.order_by()

对查询结果排序

6.values()和values_list()

values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列

Book.objects.all().values('title')
<QuerySet [{'title': 'shell'}, {'title': 'python'}]>

values_list() 方法用于查询部分字段的数据。
values_list(*field): 它与values()非常相似,它返回的是一个元组序列。

查询条件

1. 相等、等于、布尔条件

1.1 exact 等于**值(默认的形式)
如:id__exact=6 或者 id=6
1.2 iexact 像 **值
如:name__iexact=‘zhangsan’
1.3 布尔条件:

  • gt 大于某个值
  • gte 大于或等于某个值
  • lt 小于某个值
  • lte 小于或等于某个值
2.是否包含**字符串

2.1 contains 包含
如:name__contains=‘san’
2.2 icontains 包含
值,不区分大小写
如:name__contains=‘san’
#ZhangSan和zhangsan都满足条件。

3.以**开始/结束

开始:startswith,istartswith
结束:endswith,iendswith

4.日期及时间

date 日期
year 年
month 月份
day 天
hour/minute/second 时分秒
week/week_day 星期

7.双下划线

7.1 单表查询

Book.objects.filter(id__lt=4,id__gt=0) # 获取id大于1 且 小于10的值
#<QuerySet [<Book: shell>, <Book: python>]>
Book.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
Book.objects.exclude(id__in=[11, 22, 33])  # not in
Book.objects.filter(title__contains="p") #title中包含'p' 区分大小写
Book.objects.filter(title__icontains="p") # icontains大小写不敏感
Book.objects.exclude(id__range=[1, 2]) # 范围bettwen and

7.2 多表条件关联查询
正向查找:属性名称__跨表的属性名称
正向查找:查询书籍为php的出版社的名字

Book.objects.filter(title='php').values('publisher__name')
#<QuerySet [{'publisher__name': '上海出版社'}]>

正向查找:查询书籍为php的作者的姓名

Book.objects.filter(title='php').values('authors__name')
#<QuerySet [{'authors__name': 'yang'}]>

正向查找的publisher__name或者authors__name中的publisher,authors是book表中绑定的字段。一对多和多对多在这里用法没区别
反向查找:从外键不在本表开始查询对应关系表的数据
反向:小写类名__跨表的属性名称
查询上海出版社出版的书名

Publisher.objects.filter(name='上海出版社').values('book__title'))
#<QuerySet [{'book__title': 'shell'}, {'book__title': 'php'}]>

根据Publisher查询书籍为Python的出版社的名字

Publisher.objects.filter(book__title='Python').values('name')
#<QuerySet [{'name': '北京出版社'}]>
Publisher.objects.filter(book__title='php').values('book__authors')
#<QuerySet [{'book__authors': 1}]>

查询书籍为php作者的名字

Author.objects.filter(book__title='php').values('name')
#<QuerySet [{'name': 'yang'}]>

8.聚合查询(聚合函数)

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

返回值:字典。键的名称默认是(属性名称加上__聚合函数名),值是计算出来的聚合值。
聚合函数 aggregate(别名 = 聚合函数名("属性名称"))
通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
示例:计算所有图书的平均价格:

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数
...
res = models.Book.objects.aggregate(Avg("price"))
print(res, type(res))
...

结果:{‘price__avg’:Decimal (‘200.000000’)}

9.分组查询(annotate)

返回值:

  • 分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
  • 分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组。
    示例:统计每一个出版社的最便宜的书的价格
res = models.Publish.objects.values("name").annotate(in_price = Min("book__price"))
print(res)

结果:<QuerySet [{'name': '菜鸟出版社', 'in_price': Decimal('100.00')}, {'name': '明教出版社', 'in_price': Decimal('300.00')}]>
示例:统计不止一个作者的图书名称

res = models.Book.objects.annotate(c = Count("authors__name")).filter(c__gt=0).values("title","c")
print(res)

结果:<QuerySet [{'title': '菜鸟教程', 'c': 1}, {'title': '吸星大法', 'c': 1}, {'title': '冲灵剑法', 'c': 1}]>
示例:查询各个作者出的书的总价格

res = models.Author.objects.annotate(all = Sum("book__price")).values("name","all")
print(res)

10.F() 查询

F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

from django.db.models import F

用法:F("字段名称")
F 动态获取对象字段的值,可以进行运算。Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作。
修改操作(update)也可以使用 F() 函数。
示例:查询工资大于年龄的人

from django.db.models import F
...
book=models.Emp.objects.filter(salary__gt=F("age")).values("name","age")
...

示例:将每一本书的价格提高100元

res = models.Book.objects.update(price=F("price")+100)
print(res)

11.Q()查询

使用Q()函数实现复杂的查询。Q()函数支持&和|,对应SQL中的AND和OR
优先级从高到低:~ & |。

from django.db.models import Q

用法:Q(条件判断)
示例:Q(title__startswith="菜")
可以混合使用 Q 对象和关键字参数,Q 对象和关键字参数是用"and"拼在一起的(即将逗号看成 and ),但是 Q 对象必须位于所有关键字参数的前面。
示例:查询价格大于 350 或者名称以菜开头的书籍的名称和价格。

res=models.Book.objects.filter(Q(price__gt=350)|Q(title__startswith="菜")).values("title","price")
print(res)

示例:查询出版日期是 2004 或者 1999 年,并且书名中包含有"菜"的书籍。

res = models.Book.objects.filter(Q(pub_date__year=2004) | Q(pub_date__year=1999), title__contains="菜")
print(res)  

12.查询优化

哪些查询比较慢?到底执行了多少次查询?
1.QuerySet.query属性查看执行的SQL。

q = User.objects.all()
print(q.query)
>>> SELECT……

2.django-debug-tool
安装:pip install django-debug-tool
使用:详情可看
https://django-debug-toolbar.readthedocs.io/en/latest/installation.html
settings.py

INSTALLED_APPS = [
    # ...
    'django.contrib.staticfiles',
    # ...
    'debug_toolbar',
]

STATIC_URL = '/static/'

在urls.py中:

import debug_toolbar
from django.conf import settings
from django.urls import include, path

urlpatterns = [
    ...
    path('__debug__/', include(debug_toolbar.urls)),
]

settings.py中加:
放在最前面

MIDDLEWARE = [
    # ...
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    # ...
]
INTERNAL_IPS = [
    # ...
    '127.0.0.1',
    # ...
]

示例:
ORM查询优化示例
显示页面:
在这里插入图片描述
右侧就是django-debug-toolbar,点击SQL可以看到SQL语句的相关信息
在这里插入图片描述

优化外键关联查询

QuerySet.select_related()
将外键关联的对象查询合并到主查询,一次性查询结果,减少SQL执行的数量。
示例:

views.py
……
profile_list = UserProfile.objects.all().select_related('user')
……

使用SQL查询

方式一:使用管理器的raw(sql)函数

返回django.db.models.query.RawQuerySet实例
示例:

# 使用SQL查询
    user_list = User.objects.raw('SELECT * FROM  `accounts_user`')

方式二:获取数据库连接、游标,直接执行sql
获取数据库连接:from django.db import connection
从连接得到游标:cursor = connection.cursor()
执行SQL:cursor.execute("SELECT * FROM table WHERE baz=%s, [baz])
查询结果:row = cursor.fetchone()

13.分页查询

分页的实现

1.对查询结果集QuerySet进行分页

返回前n个对象 User.objects.all()[:10]
返回第11到第20个对象 User.objects.all()[10:20]

def user_list_slice(request):
    """ 分页-使用切片 """
    page = request.GET.get('page', 1)
    try:
        page = int(page)
    except:
        # 出现异常默认设置为第一页
        page = 1
    # 每页放多少条数据
    page_size = 10
    # user_list = User.objects.all()[0: 10]
    # user_list = User.objects.all()[10: 20]
    start = (page - 1) * page_size
    end = page * page_size
    user_list = User.objects.all()[start: end]
    return render(request, 'user_list_slice.html', {
        'user_list': user_list
    })
2.使用django.core.paginator进行分页处理

优点:功能更完善,对异常的处理很健壮
步骤一:取得分页器Paginator(objects, page_size)
objects:分页的数据
page_size:每页数据的多少
步骤二:取得页面实例page = p.get_page(page_num)

def user_list_paginator(request):
    """ 分页-使用分页器 """
    page = request.GET.get('page', 1)
    user_list = User.objects.all()
    # 第一步,取得分页器
    p = Paginator(user_list, 15)
    # 第二步,取得某一页的对象(实例)
    # page_data = p.get_page(page)
    try:
        page_data = p.page(page)
    except EmptyPage as e:
        print('页面不存在')
        raise Http404
    except PageNotAnInteger as e:
        print('无效的页码')
        raise Http404

    return render(request, 'user_list_paginator.html', {
        'page_data': page_data
    })

异常处理:

  • InvalidPage 无效的页码
  • PageNotAnInteger 页码必须是整数
  • EmptyPage 空页(没有数据)
3.使用ListView进行分页

使用面向对象的方式来实现的。
page_obj 分页数据,如页码、当前第几页
object_list 当前页的数据列表

class UserListView(ListView):
    """ 分页处理, 面向对象 """
    # 对应的模板
    template_name = 'user_list_class.html'
    # 对应的ORM模型
    model = User
    # 页面大小
    paginate_by = 20
    # 获取页面的参数(当期第几页)
    page_kwarg = 'p'

模型管理器(Manager)

Manager是Django的模型进行数据库查询操作的接口,每个模型都拥有至少一个Manager,Django为每个模型类添加一个名为objects的默认Manager。

自定义管理器

添加新的管理器

from django.db import models

class User(models.Model):
	#...
	users = models.Manager()

使用:

user_list = User.users.all()
user_list.count()
>>> 104

一般情况下不需要自定义。

聚合与统计

Django内置聚合函数:

  • Sum 求和
  • Avg 求平均数
  • Count 计数
  • Max/Min 最大值/最小值

数据统计

使用aggregate从整个查询结果集生成统计数据
示例1:求某个学生期末成绩的总和。

grade_list = Grade.objects.filter(student_name='张三')
total = grade_list.aggregate(total_score=Sum('score'))
print(total)
return render(request, 'page_grade.html', {
        'total': total
    })

在这里插入图片描述
示例2:求某一科目成绩的平均分

subject = Grade.objects.filter(subject_name='语文').aggregate(
        max=Max('score'),
        min=Min('score'),
        avg=Avg('score'),
    )

aggregate返回一个结果。

聚合查询

使用annotate为查询结果集的每一项生成统计数据
示例3:求每个学生期末成绩的总和

stu_list = Student.objects.annotate(total=Sum('stu_grade__score'))
return render(request, 'page_grade.html', {
        'student': student,
        'subject': subject,
        'stu_list': stu_list
    })

annotate在列表中使用,返回多个结果。

事务处理

什么是事务

例如:银行转账
在这里插入图片描述
事务:多个数据库逻辑操作的集合
回滚:多个逻辑中某个操作出错,回到初始状态。
事务的原子性要求事务要么全部完成,要么全部不完成,不可能停滞在某个中间状态。

在django中使用事务

1.自动提交:使用装饰器

示例:

from django.db import transaction

@transaction.atomic()
def user_signup_trans(request):
    """ 事务的使用-装饰器 """
    username = '13000000003'
    user = User.objects.create(username=username,
                               password='123456',
                               nickname='王五')
    profile = UserProfile.objects.create(user=user, usernamex=username)
    return HttpResponse('ok')

过程解析:代码进入到最外层的atomic代码块时会打开一个事务,进入到内层atomic代码块会创建一个标记,退出内部块时会释放或回滚至标记,退出外部块时提交或回退事务。

2.自动提交:使用with语法

def user_signup_trans_with(request):
    """ 事务的使用-with语法"""
    with transaction.atomic():
        username = '13000000005'
        user = User.objects.create(username=username,
                                   password='123456',
                                   nickname='王五')
        profile = UserProfile.objects.create(user=user, usernamex=username)
    return HttpResponse('ok')

3.手动提交和回滚

def user_signup_trans_hand(request):
    """ 事务的自动提交 """
    username = '13000000007'
    # 放弃自动提交事务
    transaction.set_autocommit(False)
    try:
        user = User.objects.create(username=username,
                                   password='123456',
                                   nickname='王五')
        profile = UserProfile.objects.create(user=user, username=username)
        # profile = UserProfile.objects.create(user=user, usernamex=username)
        # 手动提交事务
        transaction.commit()
        # 4. 反馈结果:成功/失败
        return HttpResponse('ok')
    except Exception as e:
        # user.delete()
        print(e)
        # 手动控制事务,实现回滚
        transaction.rollback()
        return HttpResponse('no')