Django学习之models
The Django Book第五章学习笔记
学习环境:
- PyCharm 2019.2 专业版
- Python 3.7
- Django 2.24
一点小感想
- 感觉Pycharm对代码的格式检查很是严格,尤其是块与块之间(比如类的定义)之间须空两行。
- Project目录改名,直接在文件浏览器改名——这证明是错误的决定,最后我决定还是清空PycharmProjects目录下的所有文件,重新开始。不过,新建Project需要搭建新的工作平台,要下载安装很多东西,特别是Django要重新下载安装,数据库驱动也要重新下载……。
在settings.py中设置如下:
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.postgres_psycopg2',
'NAME': 'test',
'USER': 'username',
"PASSWORD": 'password',
"HOST": 'ip',
'PORT': ,
}
}
创建books app
python manage.py startapp books
出现错误:
django.core.exceptions.ImproperlyConfigured: 'django.db.backends.postgres_psycopg2' isn't an available database backend.
Try using 'django.db.backends.XXX', where XXX is one of:
'mysql', 'oracle', 'postgresql', 'sqlite3'
没有安装psycopg2,安装一下:
pip install psycopg2
重新执行4.命令,还是同样错误,原来是
'ENGINE': 'django.db.backends.postgres_psycopg2',
设置错误,应为
'ENGINE': 'django.db.backends.postgresql_psycopg2',
当初是听了PyCharm自己的语法检查改的,原来是错的。
然后在books的models.py中输入以下代码:
from django.db import models
# Create your models here.
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,on_delete=models.CASCADE,)
publication_date = models.DateField()
settings.py中设置:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'books.apps.BooksConfig',
]
然后在终端中执行:
> python manage.py makemigrations books
显示:
(venv) C:\Users\admin\PycharmProjects\mysite>python manage.py makemigrations books
Migrations for 'books':
books\migrations\0001_initial.py
- Create model Author
- Create model Publisher
- Create model Book
通过运行makemigrations命令,可以通知Django程序已经作出改动,要求它验证通过,以便正式将我们的app编译。
现在我们来看看如果我们migrate books model,会发生些什么?执行命令:
> python manage.py sqlmigrate books 0001
显示:
BEGIN;
--
-- Create model Author
--
CREATE TABLE "books_author" ("id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(40) NOT NULL, "email" varchar(254) NOT NULL);
--
-- Create model Publisher
--
CREATE TABLE "books_publisher" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(30) NOT NULL, "address" varchar(50) NOT NULL, "city" varchar(60) NOT NULL, "state_province" v
archar(30) NOT NULL, "country" varchar(50) NOT NULL, "website" varchar(200) NOT NULL);
--
-- Create model Book
--
CREATE TABLE "books_book" ("id" serial NOT NULL PRIMARY KEY, "title" varchar(100) NOT NULL, "publication_date" date NOT NULL, "publisher_id" integer NOT NULL);
CREATE TABLE "books_book_authors" ("id" serial NOT NULL PRIMARY KEY, "book_id" integer NOT NULL, "author_id" integer NOT NULL);
ALTER TABLE "books_book" ADD CONSTRAINT "books_book_publisher_id_189e6c56_fk_books_publisher_id" FOREIGN KEY ("publisher_id") REFERENCES "books_publisher" ("id") DEFERRABLE I
NITIALLY DEFERRED;
CREATE INDEX "books_book_publisher_id_189e6c56" ON "books_book" ("publisher_id");
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") DEFERRABLE INITI
ALLY DEFERRED;
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") DEFERRAB
LE INITIALLY DEFERRED;
ALTER TABLE "books_book_authors" ADD CONSTRAINT "books_book_authors_book_id_author_id_8714badb_uniq" UNIQUE ("book_id", "author_id");
CREATE INDEX "books_book_authors_book_id_ed3433e7" ON "books_book_authors" ("book_id");
CREATE INDEX "books_book_authors_author_id_984f1ab8" ON "books_book_authors" ("author_id");
COMMIT;
这些都是我们运行正式migrate命令后,Django要做的。总共要建4个表。
好了,我们正式开始我们的migration吧!执行:
> python manage.py migrate
数据库就这样简简单单地建立起来了。
三个步骤:
- 在models.py中进行表的定义(class)
- 运行 python manage.py makemigrations 来验证这些定义
- 运行python manage.py migrate 来实施这些定义,在数据库中创建相应的表。
数据的插入与保存
好了,我们现在可以在Django(API)中访问我们的数据了。在Terminal中运行(不能在Python Console中执行):
> python manage.py shell
在终端中依次输入以下代码:
(venv) C:\Users\admin\PycharmProjects\mysite> python manage.py shell
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from books.models import Publisher
>>> p1 = Publisher(name='张三叶', address='上海杨浦区国定路10号', city='上海市', state_province='上海', country='中国',website='http://www.hotmail.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
<QuerySet [<Publisher: Publisher object (1)>, <Publisher: Publisher object (2)>]>
>>>
这里我抄一段Django Book的的说明:
- 首先,导入Publisher模型类, 通过这个类我们可以与包含 出版社 的数据表进行交互。
- 接着,创建一个
Publisher
类的实例并设置了字段name, address
等的值。- 调用该对象的 save() 方法,将对象保存到数据库中。 Django 会在后台执行一条 INSERT 语句。
- 最后,使用
Publisher.objects
属性从数据库取出出版商的信息,这个属性可以认为是包含出版商的记录集。 这个属性有许多方法, 这里先介绍调用Publisher.objects.all()
方法获取数据库中Publisher
类的所有对象。这个操作的幕后,Django执行了一条SQLSELECT
语句。
这个已经说的很清楚了。下面的命令与上面等效,是一步到位的做法,不用调用save()方法。
>>> p1 = Publisher.objects.create(name='Apress',
... address='2855 Telegraph Avenue',
... city='Berkeley', state_province='CA', country='U.S.A.',
... website='http://www.apress.com/')
>>> p2 = Publisher.objects.create(name="O'Reilly",
... address='10 Fawcett St.', city='Cambridge',
... state_province='MA', country='U.S.A.',
... website='http://www.oreilly.com/')
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
数据的访问、过滤与查询
好了在数据库中,表books_publisherk中就有了两条记录,现在我们可以用以下方式来获取表中的各个字段的值:
>>> p1.name
'张三叶'
>>> p1.address
'上海杨浦区国定路10号'
上面我们在调用Publisher.objects.all()方法时,仅仅简单地返回了
<QuerySet [<Publisher: Publisher object (1)>, <Publisher: Publisher object (2)>]>
这太不直观了,我们可以通过在models.py中添加一些代码来解决这个问题,这样原来代码就变成如下样子:
from django.db import models
# Create your models here.
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()
def __str__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
def __str__(self):
return u'%s %s' % (self.first_name, self.last_name)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE,)
publication_date = models.DateField()
def __str__(self):
return self.title
添加了一个__str__(self)方法。这个方法在人机交互调试时,很有用的。
除了上述系统内部方法外,我们也可在models中添加自己的方法。例如在class Book(models.Model)中加入以下代码:
# 100天内出版的书,算是最近出版的。
def was_published_recently(self):
return self.publication_date >= timezone.now() - datetime.timedelta(days=100)
当然,为此我们要引入两个库:一个是Python的标准库datetime,专业处理时间问题,一个是django的表示时区的类timezone。代码如下:
import datetime
from django.utils import timezone
关于timezone的详细内容可以参考django.utils.timezone
好,我们保存,然后重新运行 python manage.py shell——运行前记得要先退出哦。
>python manage.py shell
>>> from books.models import Publisher
试试API新方法——数据过滤:
>>> Publisher.objects.filter(id=2)
id是关键字段
我们多参数过滤:
>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
返回:
<QuerySet [<Publisher: O'Reilly>]>
还有模糊过滤
>>> Publisher.objects.filter(name__contains="press")
这个name__contains并非字段名(或子类名),其中双下划线____contains 相当于SQL语句中的 LIKE 。如果翻译成标准SQL语句,应该是:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE '%press%';
其他的一些查找类型有:icontains
(大小写无关的LIKE),startswith
和endswith
, 还有range
(SQLBETWEEN查询)。
上面的例子中filter()
函数返回一个记录集,这个记录集是一个列表。 相对列表来说,有些时候我们更需要获取单个的对象, get()
方法就是在此时使用的:
>>> from books.models import Publisher
>>> Publisher.objects.get(address="北京市")
<Publisher: 中央编译出版社>
>>>
我们还可以更复杂一些——获取今年(2019年)出版的书:
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> from books.models import Book
>>>Book.objects.get(publication_date__year=current_year)
<Book: 古希腊神话与传说>
但get()
方法只能返回一个对象(一个值),如果结果超过一个,会抛出异常:
books.models.Book.MultipleObjectsReturned: get() returned more than one Book -- it returned 2!
如果查询没有返回结果也会抛出异常:
>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
...
DoesNotExist: Publisher matching query does not exist.
可以用以下方式处理异常:
try:
p = Publisher.objects.get(name='Apress')
except Publisher.DoesNotExist:
print "Apress isn't in the database yet."
else:
print "Apress is in the database."
排序问题
order_by()
方法
>>> Publisher.objects.order_by("name")
<QuerySet [<Publisher: O'Reilly>, <Publisher: 中央编译出版社>]>
多个字段用短号分隔。还有逆向排序:
>>> Publisher.objects.order_by("-name")
<QuerySet [<Publisher: 中央编译出版社>, <Publisher: O'Reilly>]>
当然,也可以在models.py
中设定:
class Publisher(models.Model):
name = models.CharField(max_length=30)
......
**class Meta:**
**ordering = ['name']**
结合起来,我们可以这样做:
>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
限制返回的记录数
>>> Publisher.objects.order_by('name')[0]
指定返回数据
>>> Publisher.objects.order_by('name')
<QuerySet [<Publisher: O'Reilly>, <Publisher: 三联书店>, <Publisher: 中华书局>, <Publisher: 中央编译出版社>]>
>>> Publisher.objects.order_by('name')[3]
<Publisher: 中央编译出版社>
指定返回按顺序第4条记录。
更新多个对象
在“插入和更新数据”小节中,我们有提到模型的save()
方法,这个方法会更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。例如说我们现在想要将中华书局
的名称由原来的”中华书局更改为中华书局出版社
。若使用save()
方法,如:
>>> p = Publisher.objects.get(name='中华书局')
>>> p.name = '中华书局出版社'
>>> p.save()
这等同于如下SQL语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = '中华书局';
UPDATE books_publisher SET
name = '中华书局出版社',
address = '北京市',
city = '北京',
state_province = '北京,
country = '中国.',
website = 'http://www.apress.com'
WHERE id = 4;
在这个例子里我们可以看到Django的save()方法更新了不仅仅是name列的值,还有更新了所有的列。 若name以外的列有可能会被其他的进程所改动的情况下,只更改name列显然是更加明智的。 更改某一指定的列,我们可以调用结果集(QuerySet)对象的update()方法: 示例如下:
>>> Publisher.objects.filter(id=4).update(name='中华书局出版社')
这等同于
UPDATE books_publisher
SET name = '中华书局出版社'
WHERE id = 4;
update()
方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录。 以下示例演示如何将所有Publisher的country字段值全部改为’中华人民共和国’:
>>> Publisher.objects.all().update(country='中华人民共和国')
4
update()
方法会返回一个整型数值,表示受影响的记录条数。 在上面的例子中,这个值是4。
删除对象
删除数据库中的对象只需调用该对象的delete()方法即可:
>>> p = Publisher.objects.get(name="O'Reilly")
>>> p.delete()
>>> Publisher.objects.all()
<QuerySet [<Publisher: 中央编译出版社>, <Publisher: 三联书店>, <Publisher: 中华书局>]>
此外,我们还可以用以下方法部分,甚至清空一个表(类)中的全部数据:
>>> Publisher.objects.filter(country='USA').delete()
>>> Publisher.objects.all().delete()