Django–模型与站点管理

「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」。

一.MTV开发模式

我们熟知的MVC框架,Model View Controller模式,Django同样也遵循这种MVC模式,以下是Django中M、V、C各自的含义:

  • M:数据存取部分,由django数据库层处理,重点
  • V:选择哪些数据要显示以及怎样显示,由视图和模板处理
  • C:根据用户输入委派视图的部分,由Django框架根据URLconf设置,对给定URL调用适当的Python函数

由于C由框架自行处理,而Django里更关注的是模型(Model)、模板(Template)和视图(Views),Django也被称为MTV框架:

  • M代表模型(Model),即数据存取层,该层处理与数据相关的所有事务
  • T代表模板(Template),即表现层。该层处理与表现相关的决定
  • V代表视图(View),即业务逻辑层,该层包含存取模型及调取恰当模板的相关逻辑,是模型与模板之间的桥梁

二.数据库配置

数据库的配置也是在Django的配置文件里面,缺省是settings.py:

DATABASE_ENGINE = '' # 告诉Django使用哪个数据库引擎
DATABASE_NAME = '' # 将数据库名称告诉Django
DATABASE_USER = '' # 告诉Django用哪个用户连接数据库
DATABASE_PASSWORD = '' # 告诉Django连接用户的密码
DATABASE_HOST = '' # 告诉Django连接哪一台主机的数据库服务器
DATABASE_PORT = '' # 告诉Django连接服务器的端口号
复制代码

DATABASE_ENGINE是告诉Django使用哪个数据库引擎,如果在Django中使用数据库,DATABASE_ENGINE必须是下表中的值:

设置 数据库 所需适配器
postgresql PostgreSQL psycopg 1.x版, www.djangoproject.com/r/python-pg…
postgresql_psycopg2 PostgreSQL psycopg 2.x版, www.djangoproject.com/r/python-pg…
mysql MySQL MySQLdb , www.djangoproject.com/r/python-my….
sqlite3 SQLite 如果使用Python 2.5+则不需要适配器。 否则就使用 pysqlite ,www.djangoproject.com/r/python-sq…
oracle Oracle cx_Oracle , www.djangoproject.com/r/python-or….

在输入这些配置并保存后,我们可以在工程目录下执行"python3 manage.py shell"来进行测试,输入以下命令来测试你的数据库配置:

>>> from django.db import connection
>>> cursor = connection.cursor()
复制代码

如果没有什么错误信息,那么配置就是正确的,否则,就得查看错误信息来纠正:

错误信息 解决办法
You haven’t set the DATABASE_ENGINE setting yet. 不要以空字符串配置 DATABASE_ENGINE 的值。
Environment variable DJANGO_SETTINGS_MODULE is undefined. 使用 python manager.py shell 命令启动交互解释器,不要以 python 命令直接启动交互解释器。
Error loading _____ module: No module named _____. 未安装合适的数据库适配器 (例如, psycopg 或 MySQLdb )。Django并不自带适配器,所以你得自己下载安装。
_____ isn’t an available database backend. 把DATABASE_ENGINE 配置成前面提到的合法的数据库引擎。 也许是拼写错误?
database _____ does not exist 设置 DATABASE_NAME 指向存在的数据库,或者先在数据库客户端中执行合适的 CREATE DATABASE 语句创建数据库。
role _____ does not exist 设置 DATABASE_USER 指向存在的用户,或者先在数据库客户端中执创建用户。
could not connect to server 查看DATABASE_HOST和DATABASE_PORT是否已正确配置,并确认数据库服务器是否已正常运行。

三.在Python代码里定义模型

​ Django模型是用Python代码形式表述的数据在数据库中的定义。对数据层来说它等同于CREATE TABLE语句,只不过执行的是Python代码而不是SQL,而且还包含了比数据库字段定义更多的含义。Django用模型在后台执行SQL代码并把结果用Python的数据结构来描述。Django也使用模型来呈现SQL无法处理的高级概念。

为什么使用Python代码来定义数据模型,这样是不是显得多余?有以下这些原因:

  • 自省(运行时自动识别数据库)会导致过载和数据完整性问题。为了提供方便的数据访问API,Django需要以某种方式知道数据库层内部信息,有两种实现方式,第一种方式是用Python明确地定义数据模型,第二种方式是通过自省来自动侦测识别数据模型;

  • 第二种方式看起来更清晰,因为数据表信息只存放在一个地方-数据库里,但是会带来一些问题。首先,运行时扫描数据库会带来严重的系统过载,第二,某些数据库,尤其是老版本的MySQL,并未完整存储那些精确的自省元数据;

  • 编写Python代码能让你避免让你的大脑在不同的模式之间来回切换,尽可能保持单一的编程环境;

  • 把数据模型用代码的方式表述来让你更容易对它们进行版本控制;

  • SQL只能描述特定类型的数据字段,高级的数据类型带来更高的效率和更好的代码复用;

  • SQL还有在不同数据平台的兼容性问题;

  • 当然,这个方法也有一个缺点,就是Python代码和数据库表的同步问题。如果你修改了一个Django模型,你要自己来修改数据库来保证和模型同步。

3.1 第一个模型

用Python代码来描述它,打开models.py文件并输入:

from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()
复制代码

​ 每个数据模型都是django.db.models.Model的子类,它的父类Model包含了所有必要的和数据库交互的方法,并提供了一个简洁漂亮的定义数据库字段的语法。每个模型相当于单个数据库表,每个属性也是这个表中的一个字段,属性名就是字段名。Django在拿到这些定义的模型后,可以自动生成相应的Sql语句,同时对于数据库中的多对多关系,Django还会额外创建一张表来表述这些映射关系。还有就是,我们无需显式地为这些模型定义主键,除非你单独指明,否则Django会自动为每个模型生成一个自动增长的整数主键字段,每个Django模型都要求有单独的主键,id。

3.2 安装模型

先需要在setting.py文件中的INSTALLED_APPS中添加所写模型文件的说明。然后:

  • 验证模型的有效性:python3 manage.py check,没有错误会显示"0 errors";

  • python3 manage.py makemigrations:创建迁移文件;

  • python3 manage.py migrate:通过创建的迁移文件往数据库执行sql语句,至此,数据库和数据表创建完成;

  • python3 manage.py sqlmigrate books 0001(0001_init.py):打印执行过的sql语句:

BEGIN;
--
-- Create model Author
--
CREATE TABLE `books_author` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `first_name` varchar(30) NOT NULL, `last_name` varchar(40) NOT NULL, `email` varchar(254) NOT NULL);
--
-- Create model Book
--
CREATE TABLE `books_book` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `title` varchar(100) NOT NULL, `publication_date` date NOT NULL);
CREATE TABLE `books_book_authors` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `book_id` integer NOT NULL, `author_id` integer NOT NULL);
--
-- Create model Publisher
--
CREATE TABLE `books_publisher` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL, `address` varchar(50) NOT NULL, `city` varchar(60) NOT NULL, `state_province` varchar(30) NOT NULL, `country` varchar(50) NOT NULL, `website` varchar(200) NOT NULL);
--
-- Add field publisher to book
--
ALTER TABLE `books_book` ADD COLUMN `publisher_id` integer NOT NULL;
ALTER TABLE `books_book_authors` ADD CONSTRAINT `books_book_authors_book_id_ed3433e7_fk_books_book_id` FOREIGN KEY (`book_id`) REFERENCES `books_book` (`id`);
ALTER TABLE `books_book_authors` ADD CONSTRAINT `books_book_authors_author_id_984f1ab8_fk_books_author_id` FOREIGN KEY (`author_id`) REFERENCES `books_author` (`id`);
ALTER TABLE `books_book_authors` ADD CONSTRAINT `books_book_authors_book_id_author_id_8714badb_uniq` UNIQUE (`book_id`, `author_id`);
ALTER TABLE `books_book` ADD CONSTRAINT `books_book_publisher_id_189e6c56_fk_books_publisher_id` FOREIGN KEY (`publisher_id`) REFERENCES `books_publisher` (`id`);
COMMIT;
复制代码

1.png

注意:

  1. 自动生成的表名是app名称(books)和模型的小写名称(publisher,book,author)的组合;
  2. Django自动为每个表添加一个id主键,你可以重新设置它;
  3. 按约定,Django添加"_id"后缀到外键字段名,这个同样也可以自定义;
  4. 外键是REFFERENCES语句明确定义的;
  5. django_migrations:该表存放的是迁移过的信息,只要在这里存放过的信息,当修改数据后再次执行migrate,migrate就不会再操作执行过的迁移,我们需要修改生成的initial.py文件和删除django_migrations表中相应的迁移记录,才可以再次进行迁移操作。

四.基本数据访问

一旦创建了模型,Django自动为这些模型提供了高级的Python API。打开python3 manage.py shell并输入下面内容:

>>> from books.models import Publisher
>>> p1 = Publisher(name='Apress', address='2855 Telegraph Avenue',
...     city='Berkeley', state_province='CA', country='U.S.A.',
...     website='http://www.apress.com/')
>>> p1.save()
>>> p2 = Publisher(name="O'Reilly", address='10 Fawcett St.',
...     city='Cambridge', state_province='MA', country='U.S.A.',
...     website='http://www.oreilly.com/')
>>> p2.save()
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
[<Publisher: Publisher object>, <Publisher: Publisher object>]
复制代码
  • 首先,导入Publisher模型类,通过这个类我们可以跟包含出版社的数据表进行交互;
  • 接着,创建Publisher实例并设置字段"name,address"等值;
  • 调用对象的save()方法,将对象保存到数据库中,Django会在后台执行一条INSERT语句;
  • 最后,使用"Publisher.objects"属性从数据库中调出出版商的信息,这个属性有很多的方法,比如:Publisher.objects.all()方法获取数据库中"Publisher"类的所有对象,这个操作的幕后实则是Django执行了一条SELECT SQL语句;
  • 这里值得注意的地方是,实例化对象时,Django并未将对象保存至数据库内,除非你调用了"save()"方法;
  • 如果需要一步完成对象的创建和存储至数据库,就调用objects.create()方法。

4.1 添加模块的字符串表现

当我们打印整个publisher列表时,我们没有得到想要的信息,无法把对象区分开来:

System Message: WARNING/2 (<string>, line 872);

Inline literal start-string without end-string.

System Message: WARNING/2 (<string>, line 872);

Inline literal start-string without end-string.

[<Publisher: Publisher object>, <Publisher: Publisher object>]
复制代码

我们可以简单解决这个问题,只需要为Publisher对象添加一个方法:_unicode_()。_unicode_()方法告诉Python如何将对象以unicode的方式显示出来。_unicode_()方法可以进行任何处理来返回对一个对象的字符串表示。对_unicode_()的唯一要求就是它要返回一个unicode对象,如果"unicode()"方法未返回一个Unicode对象,而返回一个比如说一个整型数字,那么Python将抛出一个"TypeError"错误,并提示"coercing to Unicode:need string or buffer, int found"。

4.2 Unicode对象

概念:你可以认为unicode对象就是一个Python字符串,它可以处理上百万不同类别的字符--从古老版本的Latin字符到非Latin字符再到曲折的引用和艰涩的符号。Unicode对象并没有编码,它们使用一致的通用字符编码集,当你在Python中处理Unicode对象的时候,你可以直接将它们混合使用和互相匹配而不必去考虑编码细节。请确保每一个模型里都包含_unicode_()方法,这不只是为了交互方便,在Django其他地方也要用到_unicode_()来显示对象。最后,_unicode_()也是一个很好的例子来演示我们怎么添加行为到模型里。

五.对数据库数据的操作

5.1 数据库选择对象

  • 比如:p_list = Publisher.objects.all(),这就是从模型Publisher中获取全部的数据;

  • Django在选择数据时并未使用SELECT*,而是显式地列出了所有的字段,因为SELECT*会更慢;

  • 这个语句中,objects被称为管理器,你可以在查找数据时使用它,all()方法返回数据库中的记录,看起来像一个列表,实际上是一个QuerySet对象,这个对象是数据库中一些记录的集合。

5.2 数据库数据过滤

在Django中,我们可以使用filter()方法对数据进行过滤。

Publsher.objects.filter(name='***'),我们还可以传递多个参数到filter来缩小选择范围。

Publisher.objetcts.filter(name__contains='press'),在name和contains之间有双下划线,表明会进行一些魔术般的操作,这里contains部分会被Django翻译成LIKE语句:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE '%press%';
复制代码

其他的一些查找类型有:icontains(大小写无关的LIKE),startwith、endwith,还有range。

获取单个对象:

使用get()方法:Publisher.objects.get(name="***"),这样就返回了单个对象,如果结果是多个对象,就会抛出异常,查询没有返回结果也会抛出异常。

数据排序:

在Django中,使用order_by()方法就能对返回的结果排序。

如果需要以多个字段作为排序标准,第二个字段会在第一个字段的值相同的情况下被使用到,使用多个参数就可以了。

我们还可以在字段名前加上“-”前缀表示逆向排序但每次都需要使用order_by()方法会显得啰嗦,所以,Django可以让你指定模型的缺省排序方式:

class Meta:
    ordering=['name']
复制代码

class Meta,内嵌于模型类中,你可以在任意一个模型类中使用Meta类,来设置一些与特定模型相关的选项,比如这里的ordering,这样在不指定排序方式的情况下,默认按字段"name"进行排序。

限制返回的数据:

我们可以使用Python的range-slicing语法来取出数据的特定子集。

Publisher.objects.order_by('name')[0:2],但这里不支持Python的负索引,为了反向搜索,可以改变order_by后面的字段为"-字段"即可。

更新数据,删除数据:

使用update()方法更新数据,使用delete()方法删除数据(就在前面的查询结果后加上.delete()即可)。

六.站点管理

url/admin直接进入管理界面。

使用"python manage.py create"创建一个超级用户。

在MIDDLEWARE_CLASSES中添加"django.middleware.locale.LocaleMiddleWare",就能让浏览器显示中文。

在模型app文件目下的admin.py中写入:

from django.contrib import admin
from mysite.books.models import Publisher, Author, Book

admin.site.register(Publisher)
admin.site.register(Author)
admin.site.register(Book)
复制代码

就能将数据库信息传到admin界面,在admin界面可以对数据库进行增删改查,结果也会同步到本地的数据库.

在字段名中添加属性:blank=True,这使得该字段允许输入空值.

Admin是如何工作的:

当服务启动时,Django从"url.py"引导URLconf,然后执行"admin.autodiscover()"语句,这个函数遍历INSTALLED_APPS配置,并寻找相关的admin.py文件,如果在指定的app目录下找到admin.py文件,它就执行其中的代码。在"books"应用程序目录下的"admin.py"文件中,每次调用"admin.site.register()"都将哪个模块注册到管理工具中。管理工具只为那些明确注册了的模块显示一个编辑/修改的界面。

设置日期型和数字型字段可选:

如果想允许一个日期型或数字型字段为空,需要加上null=True和blank=True。添加null=True改变了数据的语义,因此还需要更新数据库。

自定义字段标签:

email = models.EmailField(blank=True, verbose_name='e-mail' )
复制代码

这样就把email修改成了e-mail。但这种方法不适用于ManyToManyField和Foreignkey字段,因为它们的第一个参数必须是模块类,那种情形,必须显式使用verbose_name这个参数名称。

七.自定义ModelAdmin类

7.1 自定义列表

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email')
admin.site.register(Author, AuthorAdmin)
复制代码

这样就为模型Author添加了一个新的展示信息,在原本姓名的基础上添加了一列邮箱信息,list_display是一个字段名称的元组,用于列表显示,然后用admin.site.register函数追加注册了这个Author模块。当你再次在后台刷新Author模型类,就能看到first_name,last_name,emali三个列用于展示。

另外,点击列头可以对那一列进行排序。接下来,可以添加一个快速查询栏:向AuthorAdmin追加search_fileds,如:

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email')
    search_fields = ('first_name', 'last_name')
复制代码

刷新浏览器,就能在页面顶端看到一个查询栏。接下来为模型book添加一些过滤器:

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
复制代码

使用list_filter这个字段元组创建过滤器,它位于列表的右边,过滤器同样适用于其它类型字段,而不单是“日期型”,当有两个以上值时,过滤器就会显示。另外一种过滤日期的方式是使用date_hierarchy选项,如:

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
复制代码

注意,date_hierarchy接受的是“字符串”而不是元组,因为只能对一个日期型字段进行层次划分。

7.2 自定义编辑表单

可以通过ModelAdmin子类中的fields选项来改变它。

fields = ('title', 'authors', 'publisher', 'publication_date')
复制代码

完成之后,编辑表单将按照指定的顺序显示各字段。通过fields这个选项,你可以排除一些不想被其他人编辑的fields。当你的admi用户只是被信任可以更改你的某一部分数据时,或者,你的数据被一些外部的程序自动处理而改变了,你就可以用这个功能。当一个用户用这个不包含完整信息的表单添加一本新书时,Django会简单地将publication_date设置为none,以确保这个字段满足null=True的条件。

使用filter_horizontal选项(注意移出fields选项),就会看到一个精巧的JavaScript过滤器,它允许你检索选项,然后将所选结果从Available框移到Chose框,还可以移回来。注意filter_horizontal选项只能用在多对多字段

2.png

使用raw_id_fields选项,它是一个包含外键字段名称的元组,它包含的字段将被展现成“文本框”,而不再是“下拉框”。管理工具提供了一个放大镜图标方便你输入,点击放大镜会弹出一个窗口提供选择。

3.png

八.用户、用户组和权限

用户对象有标准的用户名、密码、邮箱地址和真实姓名,同时它还有关于使用管理界面的权限定义。首先,这里有一组三个布尔型标记:

  • 活动标志,它用来控制用户是否已经激活,如果一个用户账号的这个标记是关闭状态,而用户又尝试用它登录时,即使密码正确,也无法登录系统;
  • 成员标志:它用来控制这个用户是否可以登录管理界面(即:这个用户是不是你们组织里的成员)由于用户系统可以被用于控制公众页面,即非管理页面的访问权限,这个标志可以用来区分公众用户和管理用户;
  • 超级用户标志:它赋予用户在管理界面中添加、修改和删除任何项目的权限。如果一个用户账号有这个标志,那么所有权限设置都会被忽略。

admin模型类中常用属性描述如下:

  • date_hierarchy:设置一个日期类型字段,使其出现在按日期导航找模型实例的界面中;
  • empty_value_display:设置一个字符串,定义空值的显示方式;
  • fields和exclude:分别用于设置需要管理的字段和排除管理的字段;
  • fieldsets:配置字段分组;
  • list_editable:设置字段列表,指定模型中的哪些字段可以编辑;
  • list_per_page:设置一个整数,指定每页显示的实例数量,默认为100;
  • search_fields:设置字段列表,出现一个搜索页面使管理员能够按照这些字段进行实例搜索;
  • ordering:设置字段列表,定义管理页面中模型实例的排序方式。

九.模型中的Meta类

通过模型类中的Meta子类定义模型元数据,比如数据库表名、数据默认排序方式等。

Meta类的属性名由Django预定义,常用的Meta类属性汇总如下:

  • abstract:True or False,标识本类是否为抽象基类;
  • app_label:定义本类所属的应用,比如app_label = 'myapp';
  • db_table:映射的数据表名,比如db_table = 'moments',如果Meta中不提供db_table字段,则django会为模型自动生成数据表名,生成的格式为"应用名_模型名",比如应用app的模型Comment的默认数据表名为app_comment;
  • db_tablespace:映射的表空间名;
  • default_related_name:定义本模型的反向关系引用名称,默认与模型名一致;
  • get_latest_by:定义按哪个字段值排列以获得模型的开始或结束记录,本属性值通常指向一个日期或整型的模型字段;
  • managed:True or False,定义Django的manage.py命令行工具是否管理本模型,默认为True,若设为False,在migrate时,则不会生成该模型的数据表,维护需手工维护;
  • ordering:默认排序字段,以升序排列,降序需在字段前面加上负号;
  • default_permissions:模型操作权限,默认=('add','change','delete');
  • proxy:True或False,本模型及所有继承自本模型的子模型是否为代理模型;
  • required_db_features:定义底层数据库所必需具备的特性;
  • required_db_vendor:定义底层数据库的类型;
  • unique_together:用来设置的不重复的字段组合,必须唯一(可以将多个字段做联合唯一);
  • index_together:定义联合索引的字段,可以设置多个;
  • verbose_name:指明一个易于理解和表述的单数形式的对象名称;
  • verbose_name_plural:指明一个易于理解和表述的复数形式的对象名称。

补充内容

Django有两种过滤器用于筛选记录:

  • filter:返回符合筛选条件的数据集;
  • exclude:返回不符合条件的数据集。

filed lookup(字段查询方式):基本表现形式:字段名称__谓词

Django谓词表:

谓词 含义
exact 精确等于
iexact 大小写不敏感的等于
contains 模糊匹配
in 包含
gt 大于
gte 大于等于
lt 小于
lte 小于等于
startswith 以......开头
endswith 以......结尾
range 在......范围内
year
month
day
week_day 星期几
isnull 是否为空

Django支持三种风格的模型继承:

  • 抽象类继承:父类继承自models.Model,但不会在底层数据库中生成相应的数据表。父类的属性列存储在其子类的数据表中;
  • 多表继承:多表继承的每个模型类都在底层数据库中生成相应的数据表管理数据;
  • 代理模型继承:父类用于在底层数据库中管理数据表,而子类不定义数据列,只定义查询数据集的排序方式等元数据。