python Django基础

django官网https://www.djangoproject.com/download/

文档https://docs.djangoproject.com/

安装Django

安装官网LTS版本

pip install django==3.2.15

Django命令

> django-admin

Type 'django-admin help <subcommand>' for help on a specific subcommand.

Available subcommands:

[django]
    check
    compilemessages
    createcachetable
    sqlmigrate
    sqlsequencereset
    squashmigrations
    startapp
    startproject
    test
    testserver
Note that only Django core commands are listed as settings are not properly configured (error: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.).

startproject

作用:创建Django项目。

语法:django-admin startproject name [directory]

命令默认在当前目录创建一个文件夹,文件夹下包含manage.py文件以及工程文件夹,在工程文件夹下包含settings.py文件和其他必要文件。

django-admin startproject stduTestdjango-admin startproject stduTest .的区别

不带点,会多新建一个文件夹

带点

startapp

python manage.py startapp <app name>

在manage管理的这个项目下创建一个app

makemigrations

python manage.py makemigrations

在manage管理的这个项目下创建迁移文件

migrate

执行迁移

python manage.py migrate

当需要迁移的时候,执行迁移,第一次建议完全迁移

migrate后接app名,便是迁移某个app

python manage.py migrate <app name>

createsuperuser

创建管理员用户和密码

python .\manage.py createsuperuser

runserver

启动一个测试用的server

python .\manage.py runserver 0.0.0.0:8080

直接就可以访问本机的8080端口

项目准备

创建项目大致文件结构

django-admin startproject stduTest .

出现了一个文件夹和一个文件

会出现一个以项目名命名的文件夹,以及一个manage.py的文件

 $ python .\manage.py

Type 'manage.py help <subcommand>' for help on a specific subcommand.

Available subcommands:

[auth]
    changepassword
    createsuperuser

[contenttypes]
    remove_stale_contenttypes

[django]
    check
    compilemessages
    createcachetable
    dbshell
    diffsettings
    dumpdata
    startapp
    startproject
    test
    testserver

[sessions]
    clearsessions

[staticfiles]
    collectstatic
    findstatic
    runserver

创建应用

python manage.py startapp firstapp

会生成一个应用名命名的文件夹

配置

打开salary/settings.py主配置文件

​ 修改数据库

​ 配置修改时区

​ 注册应用

重点

生产环境下一定要把DEBUG设置为False

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

注册应用,添加上自己的应用

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'firstapp',			#添加上自己的刚刚创建的应用
]

修改数据库配置

ENGINE

要使用的数据库后端。内置的数据库后端有:

  • 'django.db.backends.postgresql'
  • 'django.db.backends.mysql'
  • 'django.db.backends.sqlite3'
  • 'django.db.backends.oracle'
# Database
#文档
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': BASE_DIR / 'db.sqlite3',
#     }
# }

#加上自己数据库的配置段
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',   #数据库名
        'USER': 'root',		#用户名
        'PASSWORD': '111111',	#密码
        'HOST': '10.0.0.12',		#主机
        'PORT': '3306',			#端口
    }
}

修改时区

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'

修改语言

#LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-Hans'

修改全局的编码

需要添加DEFAULT_CHARSET关键字

DEFAULT_CHARSET='utf8'

加上日志服务

在djangoproject.com上,找到文档区,然后用浏览器的搜索logging,找到范例

https://docs.djangoproject.com/zh-hans/3.2/topics/logging/#examples

写入文件的范例

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/path/to/django/debug.log',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

控制台输出的范例

import os

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

logger也分好多种

https://docs.djangoproject.com/zh-hans/3.2/topics/logging/#id3

为了能看到sql的执行过程,我们使用django.db.backends的logger

这里我们两种handler一起使用

#必须保证DEBUG = True
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': './debug.log',
            'formatter': 'verbose'
        }
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console', 'file'],
            'propagate': True,
            'level': 'DEBUG',
        },
    }
}

配置后,就可以在控制台看到执行的SQL语句。

注意,settings.py中必须DEBUG=True,同时loggerslevelDEBUG,否则从控制台看不到SQL语句。

模型Model

字段类型

字段类 说明
AutoField 自增的整数字段。
如果不指定,django会为模型类自动增加主键字段
BooleanField 布尔值字段,True和False
对应表单控件CheckboxInput
NullBooleanField 比BooleanField多一个null值
CharField 字符串,max_length设定字符长度
对应表单控件TextInput
TextField 大文本字段,一般超过4000个字符使用
对应表单控件Textarea
IntegerField 整数字段
BigIntegerField 更大整数字段,8字节
DecimalField 使用Python的Decimal实例表示十进制浮点数。max_digits总位数,decimal_places小数点后的位数
FloatField Python的Float实例表示的浮点数
DateField 使用Python的datetime.date实例表示的日期
auto_now=False每次修改对象自动设置为当前时间。
auto_now_add=False对象第一次创建时自动设置为当前时间。
auto_now_add、auto_now、default互斥
对应控件为TextInput,关联了一个Js编写的日历控件
TimeField 使用Python的datetime.time实例表示的时间,参数同上
DateTimeField 使用Python的datetime.datetime实例表示的时间,参数同上
FileField 一个上传文件的字段
ImageField 继承了FileField的所有属性和方法,但是对上传的文件进行校验,确保是一个有效的图片
EmailField 能做Email检验,基于CharField,默认max_length=254
GenericIPAddressField 支持IPv4、IPv6检验,缺省对应文本框输入
URLField 能做URL检验,基于基于CharField,默认max_length=200

缺省主键

缺省情况下,Django的每一个Model都有一个名为id的AutoField字段,如下

id = models.AutoField(primary_key=True)

如果显式定义了主键,这种缺省主键就不会被创建了。

字段选项

官方文档

说明
db_column 表中字段的名称。如果未指定,则使用属性名
primary_key 是否主键
unique 是否是唯一键
default 缺省值。这个缺省值不是数据库字段的缺省值,而是新对象产生的时候被填入的缺省值
null 表的字段是否可为null,默认为False
blank Django表单验证中,如果True,则允许该字段为空。默认为False
db_index 字段是否有索引
to_field 关联对象的字段。默认情况下,Django 使用相关对象的主键。如果你引用了一个不同的字段,这个字段必须有 unique=True

关系类型字段类

说明
ForeignKey 外键,表示一对多
ForeignKey('production.Manufacturer')
自关联ForeignKey('self')
ManyToManyField 表示多对多
OneToOneField 表示一对一

一对多时,自动创建会增加_id后缀。

​ 从一访问多,使用对象.小写模型类_set

​ 从一访问一,使用对象.小写模型类

访问id 对象.属性_id

Model类

基类 django.db.models.Model

表名不指定默认使用<appname>_<model_name>。使用Meta类修改表名

编辑应用下的models.py文件(这里是employee/models.py)

from django.db import models

"""
CREATE TABLE `employees` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` smallint(6) NOT NULL DEFAULT 1 COMMENT 'M=1, F=2',
  `hire_date` date NOT NULL,
  PRIMARY KEY (`emp_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

"""


# Create your models here.
class Employee(models.Model):
    class Meta:
        db_table = 'employees'#定义表名
	
    #将列名一一映射出来
    emp_no = models.IntegerField(primary_key=True)
    birth_date = models.DateField(null=False)
    first_name = models.CharField(null=False, max_length=14)
    last_name = models.CharField(null=False, max_length=16)
    gender = models.SmallIntegerField(null=False)
    hire_date = models.DateField(null=False)

    def __repr__(self):#重写可视化效果
        return '< Employee {}:{}-{} >'.format(self.emp_no, self.first_name, self.last_name)

    __str__ = __repr__

在项目根目录编写一段测试代码

import os
import django

#简单的连接前,需要安装mysqlclient,使用pip install mysqlclient
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings')
django.setup()

#导入必须在连接setup之后,不然会报应用未注册
from employee.models import Employee
emps=Employee.objects.all()
for i in emps:
    print(type(i),i)
    
#结果
<class 'employee.models.Employee'> < Employee 10001:Georgi-Facello >
<class 'employee.models.Employee'> < Employee 10002:Bezalel-Simmel >
<class 'employee.models.Employee'> < Employee 10003:Parto-Bamford >
<class 'employee.models.Employee'> < Employee 10004:Chirstian-Koblick >
<class 'employee.models.Employee'> < Employee 10005:Kyoichi-Maliniak >
<class 'employee.models.Employee'> < Employee 10006:Anneke-Preusig >
<class 'employee.models.Employee'> < Employee 10007:Tzvetan-Zielinski >
<class 'employee.models.Employee'> < Employee 10008:Saniya-Kalloufi >
<class 'employee.models.Employee'> < Employee 10009:Sumant-Peac >
<class 'employee.models.Employee'> < Employee 10010:Duangkaew-Piveteau >
<class 'employee.models.Employee'> < Employee 10011:Mary-Sluis >
<class 'employee.models.Employee'> < Employee 10012:Patricio-Bridgland >
<class 'employee.models.Employee'> < Employee 10013:Eberhardt-Terkki >
<class 'employee.models.Employee'> < Employee 10014:Berni-Genin >
<class 'employee.models.Employee'> < Employee 10015:Guoxiang-Nooteboom >
<class 'employee.models.Employee'> < Employee 10016:Kazuhito-Cappelletti >
<class 'employee.models.Employee'> < Employee 10017:Cristinel-Bouloucos >
<class 'employee.models.Employee'> < Employee 10018:Kazuhide-Peha >
<class 'employee.models.Employee'> < Employee 10019:Lillian-Haddadi >
<class 'employee.models.Employee'> < Employee 10020:Mayuko-Warwick >
(0.001) SELECT @@SQL_AUTO_IS_NULL; args=None
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.001) SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees`; args=()

查出了所有记录

管理器对象

Django会为模型类提供一个objects对象,它是django.db.models.manager.Manager类型,用于与数据库交互。当定义模型类的时候没有指定管理器,则Django会为模型类提供一个objects的管理器。如果在模型类中手动指定管理器后,Django不再提供默认的objects的管理器了。

管理器是Django的模型进行数据库查询操作的接口,Django应用的每个模型都至少拥有一个管理器。用户也可以自定义管理器类,继承自django.db.models.manager.Manager,实现表级别控制。

查询

查询集

查询会返回结果的集,它是django.db.models.query.QuerySet类型。

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings')
django.setup()

#
from employee.models import Employee
emps=Employee.objects.all()
print(type(emps))

#结果
<class 'django.db.models.query.QuerySet'>

它是惰性求值,和sqlalchemy一样。结果就是查询的集。

它是可迭代对象。

限制查询集(切片)

分页功能实现,使用限制查询集。

查询集对象可以直接使用索引下标的方式(不支持负索引),相当于SQL语句中的limit和offset子句。注意使用索引返回的新的结果集,依然是惰性求值,不会立即查询。

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings')
django.setup()

from employee.models import Employee
emps=Employee.objects.all()
print(emps[2:5])

#结果
<class 'django.db.models.query.QuerySet'>
<QuerySet [< Employee 10003:Parto-Bamford >, < Employee 10004:Chirstian-Koblick >, < Employee 10005:Kyoichi-Maliniak >]>
(0.000) SELECT @@SQL_AUTO_IS_NULL; args=None
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.000) SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees`  LIMIT 3 OFFSET 2; args=()

结果集方法

名称 说明
all() 所有的
filter() 过滤,返回满足条件的数据,如:mgr.filter(emp_no=10010) 如果未查询到,返回空
exlude() 排除,排除满足条件的数据,多条排除条件则为and关系
order_by() 排序,注意参数是字符串,倒序需要在字符串前面加-号,如:mgr.order_by('-emp_no')
values() 返回一个对象字典的列表,列表的元素是字典,字典内的字段的键值对,将字段以参数的形式带入,可以控制投影的字段
mgr =Employee.objects
print(mgr.all())
print(mgr.values())
print(mgr.values('pk','first_name'))
print(mgr.filter(pk=10010).values())
print(mgr.exclude(emp_no=10001))
print(mgr.exclude(emp_no=10002).order_by('emp_no'))
print(mgr.exclude(emp_no=10002).order_by('-pk'))
print(mgr.exclude(emp_no=10002).order_by('-pk').values())

filter(pk=10)这里pk指的就是主键,不用关心主键字段名,当然也可以使用使用主键名filter(emp_no=10)

返回单个值的方法

名称 说明
get() 仅返回单个满足条件的对象
如果未能返回对象则抛出DoesNotExist异常
如果能返回多条,抛出MultipleObjectsReturned异常
count() 返回当前查询的总条数
first() 返回第一个对象,找不到返回None
last() 返回最后一个对象,返回最后一个对象,找不到返回None
exists() 判断查询集中是否有数据,如果有则返回True

字段查询(Field Lookup)表达式

字段查询表达式可以作为filter()exclude()get()的参数,实现where子句。

语法:属性名称__比较运算符=值

注意:属性名和运算符之间使用双下划线

比较运算符如下

名称 例子 说明
exact Entry.objects.get(headline__exact="Cat bites dog") 等价于SQL语句SELECT ... WHERE headline = 'Cat bites dog'; 严格等于,可省略不写
contains Entry.objects.get(headline__contains='Lennon')等价于SQL语句 SELECT ... WHERE headline LIKE '%Lennon%'; 是否包含,大小写敏感,等价于like '%天%'
startswith
endswith
Entry.objects.filter(headline__startswith='Lennon')等价于SQL语句SELECT ... WHERE headline LIKE 'Lennon%';
Entry.objects.filter(headline__endswith='Lennon')等价于SQL语句SELECT ... WHERE headline LIKE '%Lennon';
以什么开头或结尾,大小写敏感
isnull
isnotnull
Entry.objects.filter(pub_date__isnull=True)等价于SQL语句SELECT ... WHERE pub_date IS NULL; 是否为null
iexact
icontains
istartswith
iendswith
i的意思是忽略大小写
in filter(emp_no__in=[1,2,3,100])等价于SQL语句SELECT ... WHERE emp_no IN (1,2,3 100); 是否在指定范围数据中
gt、gte
lt、lte
filter(id__gt=3)等价于SQL语句SELECT ... WHERE id>3;
filter(id__lte=6)等价于SQL语句SELECT ... WHERE id<=6;
filter(pub_date__gt=datetime.date(2000,1,1))等价于SQL语句SELECT ... WHERE pub_date > 2000-1-1;
大于,大于等于
小于,小于等于
year、month、dayweek_dayhour、minute、second filter(pub_date__year=2000)等价于SELECT ... WHERE pub_date between '2000-01-01' and '2000-12-31' 对日期类型属性处理

Q对象

虽然Django提供传入条件的方式,但是不方便,它还提供了Q对象来解决。

Q对象是django.db.models.Q

可以使用&|操作符来组成逻辑表达式。

~表示not

from django.db.models import Q
mgr = Employee.objects
print(mgr.filter(Q(pk__lt=10006))) # 不如直接写filter(pk__lt=10006)


# 下面几句一样
print(mgr.filter(pk__gt=10003).filter(pk__lt=10006)) # 与
print(mgr.filter(pk__gt=10003, pk__lt=10006)) # 与
print(mgr.filter(Q(pk__gt=10003), Q(pk__lt=10006)))
print(mgr.filter(Q(pk__gt=10003) & Q(pk__lt=10006))) # 与
print(mgr.filter(pk__gt=10003) & mgr.filter(pk__lt=10006))


# 下面几句等价
print(mgr.filter(pk__in=[10003, 10006])) # in
print(mgr.filter(Q(pk=10003) | Q(pk=10006))) # 或
print(mgr.filter(pk=10003) | mgr.filter(pk=10006))
print(mgr.filter(~Q(pk__gt=10003))) # 非

可使用&|Q对象来构造复杂的逻辑表达式,可以使用一个或多个Q对象。

如果混用关键字参数和Q对象,那么Q对象必须位于关键字参数的前面

聚合、分组

aggregate() 返回字典,方便使用,只能返回一条记录

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


mgr = Employee.objects
print(mgr.filter(pk__gt=10010).count()) # 单值
print(mgr.filter(pk__gt=10010).aggregate(Count('pk'), Max('pk'))) # 字典
print(mgr.filter(pk__lte=10010).aggregate(Avg('pk')))
print(mgr.aggregate(Max('pk'), min=Min('pk'))) # 别名

annotate()方法用来分组聚合,返回查询集

annotate()中结合统计方法Avg, Sum, Max, Min, Count等等做聚合,返回聚合后的查询集


  • 只有annotate()时,就是以annotate()中的字段为聚合对象,以主键为分组基准,就是先以主键来分组,再对每个分组中annotate()中的字段进行聚合,并且投影所有字段

    所得到的结果,以一个个的对象组成的列表,数据并没有显示出来,需要手动去取

emps.filter(pk__lt=10005).annotate(c=Count('gender'),a=Sum('pk'))
-- 等价SQL语句
SELECT
	`emp_no`,
	`birth_date`,
	`first_name`,
	`last_name`,
	`gender`,
	`hire_date`,
	COUNT(`gender`) AS `c`,
	SUM(`emp_no`) AS `a`
FROM
	`employees`
WHERE
	`emp_no` < 10005
GROUP BY
	`emp_no`;
  • annotate()values()的情况

    1、如果values()annotate()前面,则是以values()中的字段为分组基准,也就是先以values()中的字段来分组,在对各组中的annotate()中的字段进行聚合

    ​ 如果values()中没有字段,则跟上面那种情况是一样的,只是返回的不再是对象,而是字典组成的查询集,数据都已经显示出来了

    emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk'))
    
    -- 等价SQL语句
    SELECT
    	`gender`,
    	COUNT(`gender`) AS `c`,
    	SUM(`emp_no`) AS `a`
    FROM
    	`employees`
    WHERE
    	`emp_no` < 10005
    GROUP BY
    	`gender`
    

    2、如果values()annotate()后面,则values()中的字段就表名了投影的字段,都是以主键来分组的,当values()中为空,则投影所有字段

    ​ 返回的结果是字典组成的查询集,数据已经出来了

    emps.filter(pk__lt=10005).annotate(c=Count('gender'),a=Sum('pk')).values('gender','c','a','first_name')
    
    -- 等价SQL语句
    SELECT
    	`gender`,
    	`first_name`,
    	COUNT(`gender`) AS `c`,
    	SUM(`emp_no`) AS `a`
    FROM
    	`employees`
    WHERE
    	`emp_no` < 10005
    GROUP BY
    	`emp_no`
    

    3、如果annotate()前后都有values()

    前面values()为空,无论后面的values()是否为空,都是以主键来分组

    emps.filter(pk__lt=10005).values().annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name')
    
    -- 等价SQL语句
    SELECT
    	`first_name`,
    	COUNT(`gender`) AS `c`,
    	SUM(`emp_no`) AS `a`
    FROM
    	`employees`
    WHERE
    	`emp_no` < 10005
    GROUP BY
    	`emp_no`;
    

    后面values()为空,无论前面的values()是否为空,都以主键来分组,并且投影所有字段

    emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values()
    
    -- 等价SQL语句
    SELECT
    	`emp_no`,
    	`birth_date`,
    	`first_name`,
    	`last_name`,
    	`gender`,
    	`hire_date`,
    	COUNT(`gender`) AS `c`,
    	SUM(`emp_no`) AS `a`
    FROM
    	`employees`
    WHERE
    	`emp_no` < 10005
    GROUP BY
    	`emp_no`;
    

    ​ 后面的values()有主键存在,则无论前面的values()有什么,都是以主键来分组。

    emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name','emp_no')
    
    -- 等价SQL语句
    SELECT
    	`first_name`,
    	`emp_no`,
    	COUNT(`gender`) AS `c`,
    	SUM(`emp_no`) AS `a`
    FROM
    	`employees`
    WHERE
    	`emp_no` < 10005
    GROUP BY
    	`emp_no`;
    

    ​ 后面的values()没有主键存在,也没有除了聚合后结果的字段,哪怕后面的也没有前面values()的字段,都有以 前面values()的字段来分组

    emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a')
    
    -- 等价SQL语句
    SELECT
    	COUNT(`gender`) AS `c`,
    	SUM(`emp_no`) AS `a`
    FROM
    	`employees`
    WHERE
    	`emp_no` < 10005
    GROUP BY
    	`gender`;
    

    后面的values()没有主键存在,但是有其他字段存在,则是以前面values()字段和后面values()中存在的其他字段联合起来分组,也就是联合起来的字段必须完全相同才能被分到同一组

emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name','last_name')
-- 等价SQL语句
SELECT
	`first_name`,
	`last_name`,
	COUNT(`gender`) AS `c`,
	SUM(`emp_no`) AS `a`
FROM
	`employees`
WHERE
	`emp_no` < 10005
GROUP BY
	`gender`,
	`first_name`,
	`last_name`;

order_by()---》排序,一般在最后使用

升序就是原字段,例如order_by('c')

降序就是在原字段前面加个-,例如order_by('-c')

注意,如果order_by()中出现了主键,则annotate()前面的values()将无效,

emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name').order_by('emp_no')
-- 等价SQL语句
SELECT
	`first_name`,
	COUNT(`gender`) AS `c`,
	SUM(`emp_no`) AS `a`
FROM
	`employees`
WHERE
	`emp_no` < 10005
GROUP BY
	`emp_no`
ORDER BY
	`emp_no` ASC;

​ 如果order_by()中出现了annotate()后面的values()中没有的字段,则联合起来分组的字段中会添加上这个字段,投影也会添加这个字段

emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name').order_by('last_name')
-- 等价SQL语句
SELECT
	`first_name`,
	COUNT(`gender`) AS `c`,
	SUM(`emp_no`) AS `a`
FROM
	`employees`
WHERE
	`emp_no` < 10005
GROUP BY
	`gender`,
	`first_name`,
	`last_name`
ORDER BY
	`last_name` ASC;

一对多

构建一对多表

员工表和员工工资表

-- 员工表
CREATE TABLE `employees` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` smallint(6) NOT NULL DEFAULT 1 COMMENT 'M=1, F=2',
  `hire_date` date NOT NULL,
  PRIMARY KEY (`emp_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

-- 员工工资表
CREATE TABLE `salaries` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `salary` int(11) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date NOT NULL,
  PRIMARY KEY (`id`),
  KEY `dsad` (`emp_no`),
  CONSTRAINT `dsad` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb3;

因为Django不支持多个主键,所以只能给组个主键降级,降为候选键,添加一个id主键,将员工号emp_no作为外键

新建模型

#在employee/models.py文件下创建工资表的模型
class Salary(models.Model):
    class Meta:
        db_table = 'salaries'

    id = models.AutoField(primary_key=True, null=False)
    emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no')   #直接将emp_no新建成外键
    salary = models.IntegerField(null=False)
    from_date = models.DateField(null=False)
    to_date = models.DateField(null=False)

    def __repr__(self):
        return '< Salary {} {} {} >'.format(self.id, self.emp_no, self.salary)
    
    #新建属性
    @property
    def name(self):
        return '{}_{}'.format(self.first_name, self.last_name)

    __str__ = __repr__

emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no')

直接将emp_no设置成外键,查询时,如果不设置db_column,则以emp_no_id来搜索,在主表中是搜索不到的,所以需要写上真实的名字,所以写上db_column='emp_no'

外键

大概格式

emp_no = models.ForeignKey(Employee, on_delete=models.CASCADE,db_column='emp_no')

Employee是一个模型,也可以看作一个表,就是一对多的那个一,外键也会取这个模型的主键

db_column

用来设置外键的被搜索的字段,因为被设置为外键,当联合搜索时,会被自动默认为字段_id的字段。比如,emp_no被设置为外键,那么联合搜索时的主表中的字段就是emp_no_id,如果不设置db_column,就会报错,无法搜索到字段,设置db_column相当与设置成真实字段的名字

这个是设置本表的字段名

用来给主表搜索从表使用的,如果不设置,主表中就会多一个表名_set的属性,用来方便主表对从表进行操作,比如,主表的对象为Employee,那么在主表中就会产生一个employee_set的属性。如果手动设置一下,会方便自己查询。

on_delete!

位于 django.db.models

测试

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings')
django.setup()

from employee.models import Employee, Salary
from django.db.models import Q, Count, Sum

emps = Employee.objects
semps = Salary.objects
print(semps.all())

会进入主表employees表中反复搜索

特殊属性

一端对多端

一端Empoyee类中会增加一个特殊属性'salary_set'

​ 特殊属性'salary_set'可以用来查询多端表属性

​ 例如:

from employee.models import Employee, Salary
from django.db.models import Q, Count, Sum

emps = Employee.objects
semps = Salary.objects

print(emps.get(emp_no=10004).abc.filter(from_date__year=1995))
#转换为SQL语句
"""
SELECT
	`employees`.`emp_no`,
	`employees`.`birth_date`,
	`employees`.`first_name`,
	`employees`.`last_name`,
	`employees`.`gender`,
	`employees`.`hire_date`
FROM
	`employees`
WHERE
	`employees`.`emp_no` = 10004;

SELECT
	`salaries`.`id`,
	`salaries`.`emp_no`,
	`salaries`.`salary`,
	`salaries`.`from_date`,
	`salaries`.`to_date`
FROM
	`salaries`
WHERE
	(
		`salaries`.`emp_no` = 10004
		AND `salaries`.`from_date` BETWEEN '1995-01-01'
		AND '1995-12-31'
	);
"""    
    
print(emps.get(emp_no=10004).abc.filter(from_date__gt=datetime.date(1993,1,1)))
#转换为SQL语句
"""
SELECT
	`employees`.`emp_no`,
	`employees`.`birth_date`,
	`employees`.`first_name`,
	`employees`.`last_name`,
	`employees`.`gender`,
	`employees`.`hire_date`
FROM
	`employees`
WHERE
	`employees`.`emp_no` = 10004;

SELECT
	`salaries`.`id`,
	`salaries`.`emp_no`,
	`salaries`.`salary`,
	`salaries`.`from_date`,
	`salaries`.`to_date`
FROM
	`salaries`
WHERE
	(
		`salaries`.`emp_no` = 10004
		AND `salaries`.`from_date` > '1993-01-01'
	);
 """   
    
print(emps.get(last_name='Simmel').abc.filter(from_date__gt=datetime.date(2000,1,1)))
#转化为SQL语句
"""
SELECT
	`employees`.`emp_no`,
	`employees`.`birth_date`,
	`employees`.`first_name`,
	`employees`.`last_name`,
	`employees`.`gender`,
	`employees`.`hire_date`
FROM
	`employees`
WHERE
	`employees`.`last_name` = 'Simmel';

SELECT
	`salaries`.`id`,
	`salaries`.`emp_no`,
	`salaries`.`salary`,
	`salaries`.`from_date`,
	`salaries`.`to_date`
FROM
	`salaries`
WHERE
	(
		`salaries`.`emp_no` = 10002
		AND `salaries`.`from_date` > '2000-01-01'
	);
"""

abc是对特殊属性的改名emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no',related_name='abc')

​ 在创建模型,设置外键的时候,对特殊属性进行命名为abc

多端Salary类中会增加一个特殊属性'emp_no_id'

查询

例子:查询员工10004的所有工资

从一端往多端上查

需要借助特殊属性salary_set,来达到查询到从表的目的

emps.get(emp_no=10004).salary_set.all()

转换为SQL语句

SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees` WHERE `employees`.`emp_no` = 10004;
SELECT `salaries`.`id`, `salaries`.`emp_no`, `salaries`.`salary`, `salaries`.`from_date`, `salaries`.`to_date` FROM `salaries` WHERE `salaries`.`emp_no` = 10004;

也可以给特殊属性salary_set命名

employee/models.py中创建模型时,给外键添加一个related_name='abc',就会给Employ添加一个abc的属性,相当于salary_set

emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no',related_name='abc')
print(*Employee.__dict__.items(),sep='\n')

('abc', <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000001894FCD7D68>)

从多端往一端查询

semps = Salary.objects

x=semps.filter(emp_no=10004).values('id','emp_no_id','salary')
for i in x:
    print(i)

转换成SQL语句

SELECT `salaries`.`id`, `salaries`.`emp_no`, `salaries`.`salary` FROM `salaries` WHERE `salaries`.`emp_no` = 10004;

distinct-----》去重

目标,查询工资salary>60000的所有员工信息

子查询的方式

emps = Employee.objects

emp_nos=semps.filter(salary__gt=60000).values('emp_no').distinct()
#查工资表salaries所有salary大于60000的记录,根据emp_no投影,再去重,获得一个员工emp_no的查询集

print(emps.filter(emp_no__in=emp_nos).values('emp_no','first_name','last_name'))
#可以直接把查询集放到条件中,用来过滤employees表,最后投影出想要的信息

转换为SQL语句

SELECT
	`employees`.`emp_no`,
	`employees`.`first_name`,
	`employees`.`last_name`
FROM
	`employees`
WHERE
	`employees`.`emp_no` IN 
(
		SELECT DISTINCT
			U0.`emp_no`
		FROM
			`salaries` U0
		WHERE
			U0.`salary` > 60000
	);

会发现,已经转换成了子查询的结构

非子查询的方式

emps = Employee.objects

emp_nos=semps.filter(salary__gt=60000).values('emp_no').distinct()
#这里还是一样的,去重后的结果是一个emp_no的查询集

print(emps.filter(emp_no__in=(i['emp_no'] for i in emp_nos)).values('emp_no','first_name','last_name'))
#将查询集转换成元组,再查询,就不会成为子查询

转换为SQL语句

SELECT DISTINCT
	`salaries`.`emp_no`
FROM
	`salaries`
WHERE
	`salaries`.`salary` > 60000;

SELECT
	`employees`.`emp_no`,
	`employees`.`first_name`,
	`employees`.`last_name`
FROM
	`employees`
WHERE
	`employees`.`emp_no` IN (10001, 10002, 10004);

raw的使用--->执行原生SQL语句

-- 将salaries表和employees表合并,然后查询salary大于55000的员工名
MariaDB [test]> select distinct e.first_name,e.last_name from salaries as s  join employees as e on s.emp_no=e.emp_no where s.salary>55000;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| Georgi     | Facello   |
| Bezalel    | Simmel    |
| Chirstian  | Koblick   |
+------------+-----------+
3 rows in set (0.000 sec)

使用raw,在django中执行

from employee.models import Employee, Salary

emps = Employee.objects
semps = Salary.objects

sql = """
SELECT DISTINCT
   e.emp_no,
   e.first_name,
   e.last_name
FROM
   salaries AS s
JOIN employees AS e ON s.emp_no = e.emp_no
WHERE
   s.salary > 55000;
"""

x=emps.raw(sql)
print(type(x))
print(list(x))
for i in x:
    print(i.first_name,i.gender)
    
#运行结果
<class 'django.db.models.query.RawQuerySet'>
[< Employee 10001:Georgi-Facello >, < Employee 10002:Bezalel-Simmel >, < Employee 10004:Chirstian-Koblick >]
Georgi 1
Bezalel 2
Chirstian 1

因为执行的SQL语句必须包含管理器对象的主键,也就是emps = Employee.objects,所以需要加上e.emp_no

而查询的结果,x,属于Raw类型的查询集,而产生的对象则是管理器对象 (emps)类型的对象,而最后可以调用的字段包含了管理器对象(emps)所有的字段和sql语句中投影的其他表的字段,没有加入投影的其他表的字段,是无法查询的

例如

sql = """
SELECT DISTINCT
    s.emp_no,
    s.salary,
   e.first_name,
   e.last_name
FROM
   salaries AS s
JOIN employees AS e ON s.emp_no = e.emp_no
WHERE
   s.salary > 55000;
"""

x=emps.raw(sql)
print(type(x))
for i in x:
    print(i.first_name,i.gender,i.salary)
#这里查询了前面投影的字段,也有没有投影的字段

多对多

三个表

-- 员工表
CREATE TABLE `employees` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` smallint(6) NOT NULL DEFAULT 1 COMMENT 'M=1, F=2',
  `hire_date` date NOT NULL,
  PRIMARY KEY (`emp_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

-- 部门表
CREATE TABLE `departments` (
  `dept_no` char(4) NOT NULL,
  `dept_name` varchar(40) NOT NULL,
  PRIMARY KEY (`dept_no`),
  UNIQUE KEY `dept_name` (`dept_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

-- 第三张表
CREATE TABLE `dept_emp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_no` int(11) NOT NULL,
  `dept_no` char(4) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date NOT NULL,
  PRIMARY KEY (`id`),
  KEY `dept_no` (`dept_no`),
  KEY `employee` (`emp_no`),
  CONSTRAINT `deptart` FOREIGN KEY (`dept_no`) REFERENCES `departments` (`dept_no`) ON DELETE CASCADE,
  CONSTRAINT `employee` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb3;

创建模型

employee/models.py文件下

class Employee(models.Model):
    class Meta:
        db_table = 'employees'

    emp_no = models.IntegerField(primary_key=True)
    birth_date = models.DateField(null=False)
    first_name = models.CharField(null=False, max_length=14)
    last_name = models.CharField(null=False, max_length=16)
    gender = models.SmallIntegerField(null=False)
    hire_date = models.DateField(null=False)

    def __repr__(self):
        return '< Employee {}:{}-{} >'.format(self.emp_no, self.first_name, self.last_name)

    @property
    def name(self):
        return '{}_{}'.format(self.first_name, self.last_name)

    __str__ = __repr__
    
 
class Department(models.Model):
    class Meta:
        db_table = 'departments'

    dept_no = models.CharField(primary_key=True, null=False, max_length=4)
    dept_name = models.CharField(unique=True, null=False, max_length=40)

    def __repr__(self):
        return '< Dept {} {} >'.format(self.dept_no, self.dept_name)

    __str__ = __repr__



class Dept_emp(models.Model):
    class Meta:
        db_table = 'dept_emp'

    id = models.AutoField(primary_key=True)
    emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no',related_name='en')
    dept_no = models.ForeignKey('Department', on_delete=models.CASCADE,db_column='dept_no',related_name='de')
    from_date = models.DateField(null=False)
    to_date = models.DateField(null=False)

    def __repr__(self):
        return '< Dept_emp {} {} {} >'.format(self.id, self.emp_no_id, self.dept_no_id)
    											#这里改成emp_no_id 是为了防止二次查询造成资源浪费

    __str__ = __repr__

使用查询

查询emp_no=10010的部门编号以及部门名称和员工信息

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings')
django.setup()

from employee.models import Employee,Department,Dept_emp

emgr = Employee.objects
dmgr=Department.objects
d_e_mgr=Dept_emp.objects
print(emgr.filter(emp_no=10010).values().first())
dept_nos=[i.dept_no_id for i in emgr.get(emp_no=10010).en.all()]
print(dmgr.filter(dept_no__in=dept_nos).values())

#运行结果
{'emp_no': 10010, 'birth_date': datetime.date(1963, 6, 1), 'first_name': 'Duangkaew', 'last_name': 'Piveteau', 'gender': 2, 'hire_date': datetime.date(1989, 8, 24)}
<QuerySet [{'dept_no': 'd004', 'dept_name': 'Production'}, {'dept_no': 'd006', 'dept_name': 'Quality Management'}]>

显示事务处理

因为需要数据的完整性,所以需要显式控制事务

from django.db.transaction import atomic

两种方法

  • 作为装饰器,为整个视图函数
from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()
  • 作为上下文,对部分代码执行显示事务
from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

测试代码

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog.settings')
django.setup()

from post.models import *
from django.contrib.auth.models import User
from django.db.transaction import atomic
post=Post()
content=Content()
try:
    post.title='sadasd'
    post.author_id=User.objects.get(id=2)
    with atomic():
        post.save()
        content.id=post
        raise BufferError
        content.content='qwer'
        content.save()
except Exception as e:
    print(e)

当第一个表数据插入完成,抛出异常,事务会回滚

对数据库的增删改查

增加

不指定主键,就默认为插入数据

例如,插入一条,作者为admin,标题为abcd 的数据

from post.models import *
from django.contrib.auth.models import User
from django.db.transaction import atomic

post = Post()
content = Content()
try:
    post.title = 'abcd'
    post.author_id = User.objects.get(username='admin')
    with atomic():
        post.save()
        content.id = post
        content.content = 'qwer'
        content.save()
except Exception as e:
    print(e)

修改

先查出title='aaaaa'的post表中的数据,再把数据组成的对象与content表对应,然后修改content字段

先查再修改

多表修改

from post.models import *
from django.contrib.auth.models import User
from django.db.transaction import atomic

content = Content()
try:
    post = Post.objects.filter(title='aaaaa').first()
    with atomic():
        content.id = post
        content.content = '1111111'
        content.save()
except Exception as e:
    print(e)

单表本表修改

from post.models import *
from django.contrib.auth.models import User
from django.db.transaction import atomic
import datetime
try:
    post = Post.objects.filter(title='aaaaa').first()
    with atomic():
        post.postdate=datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))
        post.save()
except Exception as e:
    print(e)

删除

当有外键,且从表有数据

  • 借助模板中从表中定义的related_name='content',用来删除从表中的数据,再删除主表的数据

    通过related_name='content'返回的数据是一个关联管理器对象,需要转换成数据对象,再进行删除

from post.models import *
from django.contrib.auth.models import User
from django.db.transaction import atomic
import datetime
content=Content()
try:
    post = Post.objects.filter(title='aaaaa').first()
    with atomic():
        post.content.first().delete()
        post.delete()
except Exception as e:
    print(e)
  • 手动找到从表中的数据,然后删除
  1. 删除单个
from post.models import *
from django.contrib.auth.models import User
from django.db.transaction import atomic
import datetime
content=Content()
try:
    post = Post.objects.filter(title='aaaaa').first()
    with atomic():
        content.id=post
        content.delete()
        post.delete()
except Exception as e:
    print(e)
  1. 删除多个
from post.models import *
from django.contrib.auth.models import User
from django.db.transaction import atomic
import datetime
content=Content()
try:
    post = Post.objects.filter(title='sadasd').all()
    with atomic():
        for i in post:
            content.id=i
            content.delete()
        post.delete()
except Exception as e:
    print(e)

以上都是查从表,然后删主表

现在的需求时知道从表内容,要连主表一起删

由从表删主表

import datetime, os, django

print(datetime.date.fromtimestamp(datetime.datetime.now().timestamp()))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog.settings')
django.setup(set_prefix=False)

from post.models import Post, Content
from django.db.transaction import atomic

cont = Content()

with atomic():
    content = Content.objects.filter(content="内容")
    post = Post()
    for i in content:
        post = Post.objects.filter(id=i.id_id).first()
        i.delete()
        post.delete()

迁移

如果建立好模型类,想从这些类来生成数据库的表,使用下面语句。

生成迁移文件

 python manage.py makemigrations

会在app名下的migrations文件夹下生成一个迁移文件

例如上图的0001_initial.py文件

执行迁移

python manage.py migrate

第一次迁移,最好全部迁移

后续再指定app进行迁移

 python manage.py migrate employee

Django后台管理

1. 创建管理员

$(python_blog) PS D:\python_blog> python manage.py createsuperuser
Username (leave blank to use 'hexug'): admin
Email address: admin@123.com
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

创建管理员,实际上是在auth_user表中增加一个特殊用户

2. 本地化

settings.py中设置语言、时区
语言名称可以查看 django\contrib\admin\locale 目录

LANGUAGE_CODE = 'zh-Hans' #'en-us' 中文简体
USE_TZ = True
TIME_ZONE = 'Asia/Shanghai' #'UTC'

3. 启动WEB Server

$ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
November 22, 2021 - 18:29:38
Django version 3.2.24, using settings 'blog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

默认启动8000端口

4. 登录后台管理

因为在urls.py文件中默认配置了一条,当访问根目录下的admin自动跳准的路由,所以后台登录地址http://127.0.0.1:8000/admin

表明成功登录

登录后如果希望后来能够管理这些表,就需要在admin文件中注册对应的models

举例

models.py

from django.db import models

# Create your models here.
class Us(models.Model):
    class meta:
        db_table="a"
    pass

在admin.py文件中注册

from .models import Us
admin.site.register(Us)

在后台web端查看

posted @ 2023-02-18 14:42  厚礼蝎  阅读(78)  评论(0编辑  收藏  举报