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 stduTest
与django-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,同时loggers的level是DEBUG,否则从控制台看不到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
相当与设置成真实字段的名字
这个是设置本表的字段名
related_name
用来给主表搜索从表使用的,如果不设置,主表中就会多一个表名_set
的属性,用来方便主表对从表进行操作,比如,主表的对象为Employee
,那么在主表中就会产生一个employee_set
的属性。如果手动设置一下,会方便自己查询。
on_delete!
位于 django.db.models
-
CASCADE
级联删除。Django 模拟 SQL 约束 ON DELETE CASCADE 的行为,并删除包含 ForeignKey 的对象。
Model.delete()
不会在相关模型上调用,但 会为所有已删除的对象发送pre_delete
和post_delete
信号。 -
PROTECT
通过提高
ProtectedError
的子类来 防止删除引用的对象django.db.IntegrityError
。
测试
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)
- 手动找到从表中的数据,然后删除
- 删除单个
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)
- 删除多个
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: [email protected]
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端查看