django-深入模型(待完善)

django-深入模型

Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解耦合

所谓“模型”说得更直白一些就是数据,所以通常也被称作“数据模型”

在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在很长一段时间都是持久化的首选方案

MySQL为例来说明如何使用关系型数据库来实现持久化操作

配置关系型数据库MySQL

  1. 修改项目的settings.py文件,首先将我们之前创建的应用hrs添加已安装的项目中,然后配置MySQL作为持久化方案。

(venv)$ cd oa/settings.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 此处省略上面的代码

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'hrs',
]

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'oa',
'HOST': 'localhost',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '123456',
}
}

# 此处省略下面的代码

在配置ENGINE属性时,常用的可选值包括:

  • ‘django.db.backends.sqlite3’
  • ‘django.db.backends.postgresql’
  • ‘django.db.backends.mysql’
  • ‘django.db.backends.oracle’

NAME属性代表数据库的名称,如果使用SQLite它对应着一个文件,在这种情况下NAME的属性值应该是一个绝对路径;使用其他关系型数据库,则要配置对应的HOST(主机)、PORT(端口)、USER(用户名)、PASSWORD(口令)等属性。

  1. 安装MySQL客户端工具,Python 3中使用PyMySQL,Python 2中用MySQLdb。

(venv)$ pip install pymysql
如果使用Python 3需要修改项目的init.py文件并加入如下所示的代码,这段代码的作用是将PyMySQL视为MySQLdb来使用,从而避免Django找不到连接MySQL的客户端工具而询问你:“Did you install mysqlclient? ”(你安装了mysqlclient吗?)。

1
2
3
import pymysql

pymysql.install_as_MySQLdb()

  1. 运行manage.py并指定migrate参数实现数据库迁移,为应用程序创建对应的数据表,当然在此之前需要先启动MySQL数据库服务器并创建名为oa的数据库,在MySQL中创建数据库的语句如下所示。
    1
    2
    drop database if exists oa;
    create database oa default charset utf8;

mysql和sqlite数据库不同,sqlite是生成了文件在当前目录,而mysql是都存放在安装目录的Data文件里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(venv)$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, 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 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 sessions.0001_initial... OK

会出现的问题:

  • pymysql的版本太低,然后报错:

1

解决办法就是到那个对应的py文件里面,把一个if 版本太低 <= 1.3. 然后的操作语句注释掉

  • python2和python3 的区别,decode和encode搞不清

1

解决办法是到那个对应的py文件夹,找到那个decode,然后换成encode

  1. 可以看到,Django帮助我们创建了10张表,这些都是使用Django框架需要的东西,稍后我们就会用到这些表。除此之外,我们还应该为我们自己的应用创建数据模型。如果要在hrs应用中实现对部门和员工的管理,我们可以创建如下所示的数据模型。

django给我们创建的10张表:
1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from django.db import models


class Dept(models.Model):
"""部门类"""

no = models.IntegerField(primary_key=True, db_column='dno', verbose_name='部门编号')
name = models.CharField(max_length=20, db_column='dname', verbose_name='部门名称')
location = models.CharField(max_length=10, db_column='dloc', verbose_name='部门所在地')

class Meta:
db_table = 'tb_dept'


class Emp(models.Model):
"""员工类"""

no = models.IntegerField(primary_key=True, db_column='eno', verbose_name='员工编号')
name = models.CharField(max_length=20, db_column='ename', verbose_name='员工姓名')
job = models.CharField(max_length=10, verbose_name='职位')
# 自参照完整性多对一外键关联
mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='主管编号')
sal = models.DecimalField(max_digits=7, decimal_places=2, verbose_name='月薪')
comm = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True, verbose_name='补贴')
# 多对一外键关联
dept = models.ForeignKey(Dept, db_column='dno', on_delete=models.PROTECT, verbose_name='所在部门')

class Meta:
db_table = 'tb_emp'

说明:上面定义模型时使用了字段类及其属性,其中IntegerField对应数据库中的integer类型CharField对应数据库的varchar类型DecimalField对应数据库的decimal类型ForeignKey用来建立多对一外键关联。字段属性primary_key用于设置主键max_length用来设置字段的最大长度db_column用来设置数据库中与字段对应的列verbose_name则设置了Django后台管理系统中该字段显示的名称

更多详细资料:谷歌百度:模型定义参考-字段-字段属性-模型元数据选项-查询参考

db_table是用于指定自定义数据库表名的

  1. 通过模型创建数据表。
1
2
3
4
5
6
7
8
9
10
(venv)$ python manage.py makemigrations hrs
Migrations for 'hrs':
hrs/migrations/0001_initial.py
- Create model Dept
- Create model Emp
(venv)$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, hrs, sessions
Running migrations:
Applying hrs.0001_initial... OK

执行完数据模型迁移操作之后,可以在通过图形化的MySQL客户端工具查看到E-R图(实体关系图)。

1

具体操作百度

在后台管理模型

创建超级管理员账号。

1
2
3
4
5
6
(venv)$ python manage.py createsuperuser
Username (leave blank to use 'hao'): zzm99
Email address: zzm99@126.com
Password:
Password (again):
Superuser created successfully.

启动Web服务器,登录后台管理系统。

1
2
3
(venv)$ python manage.py runserver

访问http://127.0.0.1:8000/admin,会来到如下图所示的登录界面。

1

登录后进入管理员操作平台。

1

至此我们还没有看到之前创建的模型类,需要在应用的admin.py文件中模型进行注册

注册模型类。

1
(venv)$ vim hrs/admin.py

1
2
3
4
5
6
from django.contrib import admin

from hrs.models import Emp, Dept

admin.site.register(Dept)
admin.site.register(Emp)

注册模型类后,就可以在后台管理系统中看到它们。

1

对模型进行CRUD操作。

可以在管理员平台对模型进行C(新增create)R(查看retrieve)U(更新update)D(删除delete)操作,如下图所示。

添加新的部门。

1

查看所有部门。

1

更新和删除部门。

1

注册模型管理类。

再次修改admin.py文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.contrib import admin

from hrs.models import Emp, Dept


class DeptAdmin(admin.ModelAdmin):

list_display = ('no', 'name', 'location')
ordering = ('no', )


class EmpAdmin(admin.ModelAdmin):

list_display = ('no', 'name', 'job', 'mgr', 'sal', 'comm', 'dept')
search_fields = ('name', 'job')


admin.site.register(Dept, DeptAdmin)
admin.site.register(Emp, EmpAdmin)

1

1

为了更好的查看模型数据,可以为Dept和Emp两个模型类添加str魔法方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#models.py
# Create your models here.
from django.db import models


class Dept(models.Model):
"""部门类"""

no = models.IntegerField(primary_key=True, db_column='dno', verbose_name='部门编号')
name = models.CharField(max_length=20, db_column='dname', verbose_name='部门名称')
location = models.CharField(max_length=10, db_column='dloc', verbose_name='部门所在地')

def __str__(self):
return self.name

class Meta:
db_table = 'tb_dept'

class Emp(models.Model):
"""员工类"""

no = models.IntegerField(primary_key=True, db_column='eno', verbose_name='员工编号')
name = models.CharField(max_length=20, db_column='ename', verbose_name='员工姓名')
job = models.CharField(max_length=10, verbose_name='职位')
# 自参照完整性多对一外键关联
mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='主管编号')
sal = models.DecimalField(max_digits=7, decimal_places=2, verbose_name='月薪')
comm = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True, verbose_name='补贴')
# 多对一外键关联
dept = models.ForeignKey(Dept, db_column='dno', on_delete=models.PROTECT, verbose_name='所在部门')

mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='直接主管')

def __str__(self):
return self.name

class Meta:
db_table = 'tb_emp'

修改代码后刷新查看Emp模型的页面,效果如下图所示。

1

使用ORM完成模型的CRUD操作

对象关系映射(英语:(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换 。 从效果上说,它其实是创建了一个可在编程语言里使用的–“虚拟对象数据库”。

在了解了Django提供的模型管理平台之后,我们来看看如何从代码层面完成对模型的CRUD(Create / Read / Update / Delete)操作。我们可以通过manage.py开启Shell交互式环境,然后使用Django内置的ORM框架对模型进行CRUD操作。

1
2
3
4
5
6
(venv)$ python manage.py shell
Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

新增

1
2
3
>>> from hrs.models import Dept, Emp
>>> dept = Dept(40, '研发2部', '深圳')
>>> dept.save()

更新

1
2
>>> dept.name = '研发3部'
>>> dept.save()

查询

1
2
>>> Dept.objects.all()
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]>

过滤数据。

1
2
3
4
5
6
7
8
9
10
11
>>> Dept.objects.filter(name='研发3部') # 查询部门名称为“研发3部”的部门
<QuerySet [<Dept: 研发3部>]>
>>>
>>> Dept.objects.filter(name__contains='研发') # 查询部门名称包含“研发”的部门(模糊查询)
<QuerySet [<Dept: 研发1部>, <Dept: 研发3部>]>
>>>
>>> Dept.objects.filter(no__gt=10).filter(no__lt=40) # 查询部门编号大于10小于40的部门
<QuerySet [<Dept: 销售1部>, <Dept: 运维1部>]>
>>>
>>> Dept.objects.filter(no__range=(10, 30)) # 查询部门编号在10到30之间的部门
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>]>

查询单个对象

1
2
3
4
5
6
7
8
9
10
11
>>> Dept.objects.get(no=10)
<Dept: 研发1部>
>>>
>>> Dept.objects.get(no=20)
<Dept: 销售1部>
>>>
>>> Dept.objects.get(no__exact=30)
<Dept: 运维1部>
>>>
>>> Dept.objects.filter(no=10).first()
<Dept: 研发1部>

排序数据

1
2
3
4
5
>>> Dept.objects.order_by('no') # 查询所有部门按部门编号升序排列
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]>
>>>
>>> Dept.objects.order_by('-no') # 查询所有部门按部门编号降序排列
<QuerySet [<Dept: 研发3部>, <Dept: 运维1部>, <Dept: 销售1部>, <Dept: 研发1部>]>

切片数据

1
2
3
4
5
>>> Dept.objects.order_by('no')[0:2] # 按部门编号排序查询1~2部门
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>]>
>>>
>>> Dept.objects.order_by('no')[2:4] # 按部门编号排序查询3~4部门
<QuerySet [<Dept: 运维1部>, <Dept: 研发3部>]>

高级查询。

1
2
3
4
5
6
7
8
>>> Emp.objects.filter(dept__no=10) # 根据部门编号查询该部门的员工
<QuerySet [<Emp: 乔峰>, <Emp: 张无忌>, <Emp: 张三丰>]>
>>>
>>> Emp.objects.filter(dept__name__contains='销售') # 查询名字包含“销售”的部门的员工
<QuerySet [<Emp: 黄蓉>]>
>>>
>>> Dept.objects.get(no=10).emp_set.all() # 通过部门反查部门所有的员工
<QuerySet [<Emp: 乔峰>, <Emp: 张无忌>, <Emp: 张三丰>]>

Q对象(用于执行复杂查询)的使用:

1
2
3
4
5
6
>>> from django.db.models import Q
>>> Emp.objects.filter(
... Q(name__startswith='张'),
... Q(sal__gte=5000) | Q(comm__gte=1000)
... ) # 查询名字以“张”开头且工资大于等于5000或补贴大于等于1000的员工
<QuerySet [<Emp: 张三丰>]>

  • 说明1:由于员工与部门之间存在多对一外键关联,所以也能通过部门反向查询该部门的员工(从一对多关系中“一”的一方查询“多”的一方),反向查询属性默认的名字是类名小写_set(如上面例子中的emp_set),当然也可以在创建模型时通过ForeingKey的related_name属性指定反向查询属性的名字。如果不希望执行反向查询可以将related_name属性设置为’+’或以’+’开头的字符串。

  • 说明2:查询多个对象的时候返回的是QuerySet对象,QuerySet使用了惰性查询,即在创建QuerySet对象的过程中不涉及任何数据库活动,等真正用到对象时(求值QuerySet)才向数据库发送SQL语句并获取对应的结果,这一点在实际开发中需要引起注意!

  • 说明3:可以在QuerySet上使用update()方法一次更新多个对象。

删除

1
2
>>> Dept.objects.get(no=40).delete()
(1, {'hrs.Dept': 1})

Django模型最佳实践

  • 正确的为模型和关系字段命名。
  • 设置适当的related_name属性。
  • 用OneToOneField代替ForeignKeyField(unique=True)。
  • 通过“迁移操作”(migrate)来添加模型。
  • 用NoSQL来应对需要降低范式级别的场景。
  • 如果布尔类型可以为空要使用NullBooleanField。
  • 在模型中放置业务逻辑。
  • .DoesNotExists取代ObjectDoesNotExists。
  • 在数据库中不要出现无效数据。
  • 不要对QuerySet调用len()函数。
  • 将QuerySet的exists()方法的返回值用于if条件。
  • 用DecimalField来存储货币相关数据而不是FloatField。
  • 定义str方法。
  • 不要将数据文件放在同一个目录中。
Donate? comment?