[Python自学] day-19 (2) (Django-ORM)
一、ORM的分类
ORM一般分为两类:
1.DB first:先在DB中创建数据库、表结构,然后自动生成代码中的类。在后续操作中直接在代码中操作相应的类即可。
2.Code first:直接在代码中实现各种类,然后执行,代码自动在DB中创建对应的数据库和表结构。
最常用的是后者,即Code First类型的ORM。例如 [Python自学] day-12 (Mysql、事务、索引、ORM) 中的SQLAlchemy,我们即将要了解的Django ORM也属于Code first。
二、利用ORM创建表
在我们不修改Django数据库配置的情况下,Django默认使用的数据库是sqlite3:
我们暂且使用该默认的数据库。
1.在cmdb APP中的models.py中创建一个类
from django.db import models class UserInfo(models.Model): # ORM会自动帮我们生成一个id列,是自增的 # 生成cmdb_userinfo表,里面含有username和password两列 username = models.CharField(max_length=32) password = models.CharField(max_length=64)
2.使用命令行在数据库中生成表
python manage.py makemigrations
理论上,会在cmdb/migrations中生成一个临时文件。但是这里并未生成,这是因为cmdb这个APP的models.py并未装载到Django中,所以Django找不到该models.py。
3.配置settings.py(重要)
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'cmdb', ]
4.再次执行命令行
D:\pycharm_workspace\secondsite>d:\Dev_apps\Anaconda5.3.0\python.exe manage.py makemigrations Migrations for 'cmdb': cmdb\migrations\0001_initial.py - Create model UserInfo
我们查看cmdb/migrations目录:
然后执行命令:
D:\pycharm_workspace\secondsite>d:\Dev_apps\Anaconda5.3.0\python.exe manage.py migrate Operations to perform: Apply all migrations: admin, auth, cmdb, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying cmdb.0001_initial... OK Applying sessions.0001_initial... OK
我们可以看到,Django除了帮我们执行了cmdb.0001_initial,还执行了一大堆其他的东西,这些东西是Django默认帮我们生成的表,包括sessions等常用的东西。
三、配置使用Mysql数据库
查看settings.py:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
可以看到,这里配置使用数据库后端为sqlite3。
我们将其修改为Mysql:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbname', # 数据库名,这个django不能帮我们创建,需要我们手工创建 'USER': 'root', # 登录用户名 'PASSWORD': 'xxx', # 登录密码 'HOST': '', # IP 'PORT': '', # port } }
修改完settings.py后,执行命令,即可在mysql中创建表:
python manage.py makemigrations
python manage.py migrate
特别注意:(非常重要)
Django连接mysql默认使用的事MySQLdb模块,但是在python3中还没有这个模块。
我们需要在工程目录的__init__.py:
写入以下代码:
import pymysql pymysql.install_as_MySQLdb()
四、查看sqlite3数据库
使用Navicat软件连接sqlite3数据库,查看Django-ORM为我们创建的表。
1.运行Navicat软件:
2.连接sqlite3数据库:
3.查看数据库中的表
我们可以从中找到我们在cmdb/models.py中使用类创建的表,这里叫 cmdb_userinfo。
4.查看cmdb_userinfo表
可以看到,表中有三个列,id列(django自动创建,自增的)、username列和password列。
五、ORM插入数据(基础)
我们写一个orm测试页面。
1.在cmdb/urls.py中添加一个映射关系:
urlpatterns = [ path('admin/', admin.site.urls), path('login', views.login), path('home', views.home), # path('mypage', views.MyPage.as_view()), path('mypage12376sjhdfjnwjer', views.MyPage.as_view(), name='mypage'), path('users', views.user_page), re_path('details-(\d+).html', views.details), path('orm', views.orm_test), ]
2.在视图函数中使用ORM插入数据:
# 导入cmdb的models模块 from cmdb import models def orm_test(request): # 在表中插入一条数据 models.UserInfo.objects.create( username='root', password='123456' ) return HttpResponse('ok')
另外一种方式:
def orm_test(request): obj = models.UserInfo(username='alex', password='123') obj.save() return HttpResponse('ok')
第三种方式(第一种的变种,其实就是拆包):
def orm_test(request): # 在表中插入一条数据 dic = {'username': 'eric', 'password': '123'} models.UserInfo.objects.create(**dic) return HttpResponse('ok')
3.请求http://127.0.0.1/cmdb/orm
4.查看数据库中的cmdb_userinfo表
我们可以看到,数据已经成功插入表中。
六、ORM查询数据(基础)
1.查询表中所有数据(相当于select * from cmdb_userinfo;):
def orm_test(request): # 查询数据,返回的result是QuerySet类型,实际上相当于一个列表,每个元素是一个对象,相当于 select * from cmdb_userinfo; result = models.UserInfo.objects.all() # 便利result中的每个对象,其中每列对应一个成员属性 for row in result: print(row.id, row.username, row.password) return HttpResponse('ok')
2.查询部分列(相当于 select username,password from cmdb_userinfo;):
def orm_test(request): # result同样是QuerySet,但每个元素变成了字典 result = models.UserInfo.objects.all().values('username','password') # 遍历result中的每个对象 for row in result: print(row['username'], row['password']) return HttpResponse('ok')
当然,我们如果将result通过render函数合并进模板,在模板语言中也要按result的结构来获取数据。
除了使用values()来获取部分列,还可以使用values_list('username','password'):
def orm_test(request): # result同样是QuerySet,但每个元素变成了元组 result = models.UserInfo.objects.all().values_list('username','password') # 遍历result中的每个对象 for row in result: print(row[0], row[1]) return HttpResponse('ok')
3.过滤查询(相当于 select * from cmdb_userinfo where username='alex';):
def orm_test(request): # 过滤查询,过滤出username='alex'的记录 result = models.UserInfo.objects.filter(username='alex') # 便利result中的每个对象,其中每列对应一个成员属性 for row in result: print(row.id, row.username, row.password) return HttpResponse('ok')
如果是多个过滤条件组成交集(AND),则为:
models.UserInfo.objects.filter(username='alex', password='123')
七、ORM删除数据(基础)
def orm_test(request): # 删除所有数据 models.UserInfo.objects.all().delete() # 删除过滤数据 models.UserInfo.objects.filter(username='alex').delete() return HttpResponse('ok')
八、ORM更新数据(基础)
1.基本更新操作
def orm_test(request): # 修改所有条目的password models.UserInfo.objects.all().update(password='6666') # 修改部分条目的password models.UserInfo.objects.filter(username='alex').update(password='10000') return HttpResponse('ok')
2.同时更新多个数据
def orm_test(request): # 修改部分条目的password models.UserInfo.objects.filter(id=3).update(password='10000', username='Leo') return HttpResponse('ok')
九、实现基于数据库的用户登录
1.login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> <link rel="stylesheet" href="/static/commons.css"/> <style> label{ width: 80px; text-align: right; display: inline-block; } .error_span{ color: red; } </style> </head> <body class="common"> <form action="/cmdb/login/" method="post"> <p> <label for="username">用户名:</label> <input id="username" type="text" name="user"/> <span class="error_span">{{ user_error }}</span> </p> <p> <label for="password">密码:</label> <input id="password" type="password" name="pwd"/> <input type="submit" value="提交"/> <span class="error_span">{{ pwd_error }}</span> </p> </form> <script src="/static/jquery-1.12.4.js"></script> </body> </html>
2.cmdb/urls.py映射关系
urlpatterns = [ path('login/', views.login), ]
3.cmdb/views.py视图函数
def login(request): if request.method == 'POST': username = request.POST.get('user', None) password = request.POST.get('pwd', None) pwd_error_msg = '' # 从数据库中验证用户名密码是否存在,filter获取的是一个列表,first是取列表的第一个元素,即一条记录或者None obj = models.UserInfo.objects.filter(username=username, password=password).first() if obj: return redirect('http://www.baidu.com') else: pwd_error_msg = "账号或密码不正确" return render(request, 'login.html', {"pwd_error": pwd_error_msg})
4.实现效果
输入账号密码提交给后台,然后从数据库验证,如果正确,则跳转到www.baidu.com,如果不正确则跳转回当前页面,重新输入。
十、ORM修改表结构
修改表结构,直接对app/models.py中的类进行修改就可以了。
修改完后都要执行以下命令:
python manage.py makemigrations
python manage.py migrate
1.修改数据类型(长度)
class UserInfo(models.Model): # ORM会自动帮我们生成一个id列,是自增的 # 生成UserInfo表,里面含有username和password两列 username = models.CharField(max_length=32) #password = models.CharField(max_length=64) password = models.CharField(max_length=80) # 将密码的长度修改为80
如果将长度改短,则如果数据超出长度,会丢失。
2.增加列
class UserInfo(models.Model): # ORM会自动帮我们生成一个id列,是自增的 username = models.CharField(max_length=32) #password = models.CharField(max_length=64) password = models.CharField(max_length=80) # 将密码的长度修改为80 email = models.CharField(max_length=100) # 增加一列email
默认增加的列是不能为空的,所以在执行"python manage.py makemigrations"命令时会弹出提示:
(venv) D:\pycharm_workspace\secondsite>d:\Dev_apps\Anaconda5.3.0\python.exe manage.py makemigrations You are trying to add a non-nullable field 'email' to userinfo without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option: 1 Please enter the default value now, as valid Python The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now Type 'exit' to exit this prompt >>> "xxx@email.com" Migrations for 'cmdb': cmdb\migrations\0002_auto_20191218_1319.py - Add field email to userinfo - Alter field password on userinfo
选择1,然后输入默认值。接着再执行"python manage.py migrate"命令。
执行列可以为空:
class UserInfo(models.Model): # ORM会自动帮我们生成一个id列,是自增的 username = models.CharField(max_length=32) password = models.CharField(max_length=80) email = models.CharField(max_length=100, null=True) # 增加一列email,允许为空
使用"null=True"设置列允许为空。
3.删除列
class UserInfo(models.Model): # ORM会自动帮我们生成一个id列,是自增的 username = models.CharField(max_length=32) password = models.CharField(max_length=80) #email = models.CharField(max_length=100, null=True) # 删除email列
注释或删除需要删除的列对应属性。然后重新
十一、ORM字段类型
在mysql数据库中,有字符串、数字、二进制等类型,在Django的ORM中也有对应的字段类型。
前面我们使用了CharField类型:
username = models.CharField(max_length=32)
ORM提供的数据类型:
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 自定义无符号整数字段 class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return 'integer UNSIGNED' PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)', BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 (过期) GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
上述提供的字段类型主要分为四类:
- 字符串
- 数字
- 时间
- 二进制
注意:我们可以看到一些URLField、EmailField,看似有语法验证的功能,但实际上就是一个普通的CharField。
为什么要提供这种特殊的字段,是因为Django为我们提供的admin后台管理组件要使用他们,当我们创建这样的字段时,如果使用admin进行数据库后台管理,那么页面上就会进行语法验证(对于数据库还是varchar)。
AutoField:
from django.db import models class UserInfo(models.Model): # 手工创建自增id列(手工创建了,Django就不会自动创建) uid = models.AutoField(primary_key=True) # 生成UserInfo表,里面含有username和password两列 username = models.CharField(max_length=32) password = models.CharField(max_length=80)
使用AutoField可以手工创建自增id列,必须指定为主键。
十二、ORM字段的参数
ORM提供的字段类型,可以使用以下参数:
null 数据库中字段是否可以为空 eg. null=True 允许为空 db_column 数据库中字段的列名 eg. db_column='c_name' 设置列名为c_name db_tablespace default 数据库中字段的默认值 eg. default='content' 默认值为content primary_key 数据库中字段是否为主键 eg. primary_key=True 设置为主键 db_index 数据库中字段是否可以建立索引 eg. db_index=True 建立索引 unique 数据库中字段是否可以建立唯一索引 eg. unique=True 唯一索引 unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引 eg. 2019年1月1日11时11分11秒 只对其中的日期做索引 unique_for_month 数据库中字段【月】部分是否可以建立唯一索引 eg. 只对月份做索引 unique_for_year 数据库中字段【年】部分是否可以建立唯一索引 eg. 只对年份做索引 auto_now 用于时间字段,在更新数据时自动插入当前时间(也包括新插入数据时) eg. uptime = models.DateTimeTield(auto_now=True, null=True) 要使该时间更新,不能使用: obj = models.UserInfo.objects.filter(id=1).update(password='123') 必须使用: obj = models.UserInfo.objects.filter(id=1).first() obj.password="123" obj.save() # 此时uptime才会更新 auto_now_add 用于时间字段,在插入数据时自动插入当前时间 eg. ctime = models.DateTimeTield(auto_now_add=True, null=True) verbose_name Admin中显示的字段名称 eg. verbose_name="用户名" 页面上就不会显示"username",而是显示"用户名" blank Admin中是否允许用户输入为空 eg. blank=True 页面上不填内容提交时,不会进行内容为空的提示 editable Admin中是否可以编辑 eg. editable=False 页面上相应输入框消失(不可编辑) help_text Admin中该字段的提示信息 eg. help_text="输入密码" 页面上会在密码输入框下显示"输入密码"提示 choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作 如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1) error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息; 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date 如:{'null': "不能为空.", 'invalid': '格式错误'} validators 自定义错误验证(列表类型),从而定制想要的验证规则 from django.core.validators import RegexValidator from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\ MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 如: test = models.CharField( max_length=32, error_messages={ 'c1': '优先错信息1', 'c2': '优先错信息2', 'c3': '优先错信息3', }, validators=[ RegexValidator(regex='root_\d+', message='错误了', code='c1'), RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'), EmailValidator(message='又错误了', code='c3'), ] )
其中有很大一部分是专门用于操作Django admin后台管理页面的。
十三、ORM外键
例如有两个表:用户表和部门表
from django.db import models class GroupInfo(models.Model): gid = models.AutoField(primary_key=True) groupname = models.CharField(max_length=32) class UserInfo(models.Model): # 手工创建自增id列(手工创建了,Django就不会自动创建) uid = models.AutoField(primary_key=True) # 添加一个列,使用外键,外键为GroupInfo表中的gid列(该列的值必须是唯一的),如果不写to_field则默认使用主键,默认值为1 group = models.ForeignKey('GroupInfo', to_field='gid', default=1) # 生成UserInfo表,里面含有username和password两列 username = models.CharField(max_length=32) password = models.CharField(max_length=80)
特别注意:虽然我们定义外键时使用的变量名叫"group",但Django帮我们在表里生成的实际的列名叫做"group_id"。如下图所示:
当我们查询某个用户信息时,联合UserGroup表一起查询该用户所在group:
def orm_test(request): # 向UserGroup表中插入一个group (gid=1,groupname='Dev') models.UserGroup.objects.create(groupname="Dev") # 向UserInfo表中插入一个user (uid=1,username='Leo',password='123',group_id=1),注意这里要使用真实列名group_id models.UserInfo.objects.create(username="Leo", password='123', group_id=1) # 联合查询UserInfo以及UserGroup # 获取第一个用户,这里只有一个用户Leo obj = models.UserInfo.objects.filter(uid=1).first() # 打印用户的用户名、密码(这些内容都在UserInfo表中) print(obj.username) print(obj.password) # 这里注意,group是外键,指向UserGroup表,所以这里的group属性是一个对象(UserGroup表的一条记录),我们通过该对象来获取groupname print(obj.group.groupname) return HttpResponse('ok')
当我们查询一个带有外键的表的记录时,Django会自动关联查询外键指向的表(UserGroup),并将对应的记录存放到一个对象中(UserGroup一条记录),我们通过该记录可以获取外键指向的表中的信息(groupname)。
def orm_test(request): # 查询数据,返回的result是QuerySet类型,实际上相当于一个列表,每个元素是一个对象,相当于 select * from cmdb_userinfo; result = models.UserInfo.objects.all() # 便利result中的每个对象,其中每列对应一个成员属性 for row in result: print(row.id, row.username, row.password) return HttpResponse('ok')