Django学习笔记-第一个应用程序
Project和APP的区别:
一个是配置,一个是代码;
一个project包含很多个Django app以及对它们的配置;
技术上,project的作用是提供配置文件,比方说哪里定义数据库连接信息,安装的app列表,TEMPLATE_DIRS,等;
一个app是一套Django功能的集合,通常包括模型和视图,按python的包结构的方式存在;
Django本身内建有一些app,eg:注释系统、自动管理界面,app的一个关键点是它们很容易移植到其他project和被多个project复用。
总的来说,就是对于如何架构Django代码并没有快速成套的规则。 如果你只是建造一个简单的Web站点,那么可能你只需要一个app就可以了; 但如果是一个包含许多不相关的模块的复杂的网站,例如电子商务和社区之类的站点,那么你可能需要把这些模块划分成不同的app,以便以后复用。
注意:
Django系统对app有一个约定:如果你使用了Django的数据库层(模型),就必须创建一个Django app,模型必须存放在apps中。
开始本例:
在’mysite’项目文件下输入下面的命令来创建’books’ app:
python manage.py startapp books
在Python代码里定义模型:
MTV里的M代表模型。 Django模型是用Python代码形式表述的数据在数据库中的定义。对数据层来说它等同于 CREATE TABLE 语句,只不过执行的是Python代码而不是 SQL,而且还包含了比数据库字段定义更多的含义。 Django用模型在后台执行SQL代码并把结果用Python的数据结构来描述。 Django也使用模型来呈现SQL无法处理的高级概念。
第一个模型:
数据库结构:书籍/作者/出版商
假定:
一个作者有姓,有名及Email地址;
出版商有名称,地址,所在城市,省,国家,网站;
书籍有书名和出版日期;
书籍有一个或多个作者(和作者是多对多的关联关系[many-to-many]),只有一个出版商(和出版商是一对多的关联关系[one to many],也被称作外键[foreign key]);
第一步,用python代码来描述它们,在models.py添加如下代码:
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.ManyToMany(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
注意:
每个模型都相当于单个数据库表,每个属性也是这个表中的一个字段。属性名就是字段名,它的类型(eg:CharFiled)相当于数据库的字典类型(eg:varchar)。
以Publisher模块为例,它等同于下面这张表(用SQL语句CREATE TABLE):
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" varchar(30) NOT NULL,
"country" varchar(50) NOT NULL,
"website" varchar(200) NOT NULL
);
注意:
每个数据表对应一个类,这条规则的例外情况是多对多关系。eg:在本例中,Book有一个多对多字段叫做authors。该字段表明一本书籍有一个或多个作者,但Book数据库表却并没有authors字段,相反,Django创建了一个额外的表(多对多连接表)来处理书籍和作者之间的映射关系。
本例中并没有显式地为这些模型定义任何主键,除非你单独指明,否则Django会自动为每个模型生成一个自增长的整数主键字段每个Django模型都要求有单独的主键。
在INSTALLED_APPS的末尾也需添加一个逗号,因为这是个单元素的的元组,每一个tuple元素后面加一个逗号,不管它是不是只有一个元素,这样是为了避免忘加逗号。
在ISNTALLED_APPS中的每个app都使用Python的路径描述,包的路径,用小数点“.”间隔。
接下来,就可以创建数据库表了,首先,用如下命令对模型有效性进行验证:
python manage.py validate
但是在新版的Django里,则是使用如下命令进行验证:
python manage.py check
如果有错误,错误输出会给出非常有用的搓洗信息来帮助修正模型。
如果没有错误,系统会返回:
system check identified no issues (0 silenced)
系统检查没有错误(0沉默)
模型确认无误,运行下面的命令来生成CREATE TABLE语句:
python manage.py makemigrations books
Migrations for 'books':
0001_initial.py:
- Create model Author
- Create model Book
- Create model Publisher
- Add field publisher to book
python manage.py sqlmigrate books 0001
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` ALTER COLUMN `publisher_id` DROP DEFAULT;
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_8714badb_uniq` UNIQUE (`book_id`, `author_id`);
CREATE INDEX `books_book_2604cbea` ON `books_book` (`publisher_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;
注意:
上述两条命令:python manage.py makemigrations books、python manage.py sqlmigrate books 0001,并没有在数据库中真正创建数据表,只是把SQL语句段打印出来,这样是为了方便看到Django会做什么,如果需要创建数据表,可以把SQL语句复制到数据库客户端执行,不过Django提供了一种更为简易的提交SQL语句至数据库的方法:python manage.py migrate
该命令仅在1.9版本之后存在,之前是使用syncdb命令,不再适用新版本了,执行上述命令后,会看到如下内容:
python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, books, auth, sessions
Running migrations:
Rendering model states... DONE
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 books.0001_initial... OK
Applying sessions.0001_initial... OK
再次运行该命令,什么也没发生,因为你没有添加新的模型或者添加新的app,所以再次运行该命令也是安全的,因为它不会重复执行SQL语句。
运行python manage.py dbshell
将直接进入SQL客户端,本例使用的是MySQL所以运行后,会直接进入到mysql的客户端。
基本数据访问
一旦创建了模型,Django自动为这些模型提供了高级的Python API,运行python manage.py shell
并输入如下内容:
python manage.py shell
Python 2.7.10 (default, Oct 14 2015, 16:09:02)
[GCC 5.2.1 20151010] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 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语句。
如果需要一步完成对象的创建与存储至数据库,就使用”objects.create()”方法,如下所示:
>>> 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
添加模块的字符串表现
当我们打印Publisher的时候,并没有得到想要的信息,解决这个问题很简单,只需要为Publisher对象添加一个方法__unicode__(),该方法会告诉Python如何将对象以unicode的方式显示出来,修改books下的models.py:
from __future__ import unicode_literals
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 __unicode__(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 __unicode__(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)
publication_date = models.DateField()
def __unicode__(self):
return self.title
Publisher和Book对象的__unicode__()方法简单地返回各自的名称和标题,Author对象的__unicode__()方法则是将first_name和last_name字段值以空格连接后再返回。
如果__unicode__()方法返回的并不是unicode对象而是一个整型数字,则会抛出”TypeError”并提示:“coercing to Unicode: need string or buffer, int found”。
注意:
Django在其内部的各个方面都使用到了Unicode对象。模型对象中,检索匹配方面的操作使用的是Unicode对象,视图函数之间的交互使用的是Unicode对象,模板的渲染也是用的Unicode对象。 通常,我们不必担心编码是否正确,后台会处理的很好。
再次验证修改后的models的效果:
root@blackromantic:/home/zxz/djcode/mysite# python manage.py shell
Python 2.7.10 (default, Oct 14 2015, 16:09:02)
[GCC 5.2.1 20151010] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from books.models import Publisher
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
[<Publisher: Apress>, <Publisher: O'Reilly>]
最后,__unicode__()也是一个很好的例子来演示我们怎么添加行为到模型里,Django的模型不只是为对象定义了数据库表的结构,还定义了对象的行为。
插入和更新数据
直接利用Publisher来更新全部数据,在调用save()方法后,就保存至数据库,相当于SQL里的INSERT语句;
直接利用Publisher来更新某一条数据,在调用save()方法后,就保存至数据库,相当于SQL里的UPDATE语句。
选择对象
创建新的数据库,并更新之中的数据是很必要的,但是对于web应用程序来说,更多的时候是在检索查询数据库。
例如,我们已经知道如何从一个给定的模型中取出所有记录:
>>> Publisher.objects.all()
[<Publisher: Apress>, <Publisher: O'Reilly>]
这相当于SQL语句:
SELECT id, name, address, city, state_province, country, website FROM books_publisher;
Publisher.objects.all():
- 首先,有一个已定义的模型Publisher,这是必须的,要查找数据,就需要模型来获得数据;
- 其次,是objects属性,它被称为管理器,管理器管理者所有针对数据包含、还有最重要的数据查询的表格级操作,所有的模型都自动拥有一个objects管理器,在想要查找数据时使用它;
- 最后,是all()方法,这个方法返回数据库中所有的记录,尽管这个对象看起来像一个列表,它实际上是一个QuerySet对象,这个对象是数据库中一些记录的集合。
数据过滤
通常我们只针对一部分数据进行操作,在DjangoAPI中,使用”filter()”方法对数据进行过滤:
>>> from books.models import Publisher
>>> Publisher.objects.filter(name='Apress')
[<Publisher: Apress>]
>>>
filter()根据关键字参数来转换成WHERE SQL语句,上面的例子相当于:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = 'Apress';
可以传递多个参数到filter()来缩小查询范围:
>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
[<Publisher: Apress>]
转换成SQL语句就是:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A.'
AND state_province = 'CA';
SQL缺省的=操作符是精确匹配的,其他类型的查找也可以使用:
>>> Publisher.objects.filter(name__contains="press")
[<Publisher: Apress>]
name和contains之间有双下划线,Django也使用双下划线来表明进行一些特殊操作,这里的contains部分会被Django翻译成SQL的LIKE语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE '%press%';
其他类型还有:
icontains:大小写无关的LIKE
startwith、endwith、range:相当于SQL的BETWEEN查询
获取单个对象
如果我们要获取单个的对象,可以使用”get()”方法:
>>> Publisher.objects.get(name="Apress")
<Publisher: Apress>
这样返回的就是单个对象,而不是列表(更准确的说,QuerySet)。
如果返回的不是单个对象,而是多个对象,就会抛出异常:
>>> Publisher.objects.get(country="U.S.A")
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/local/lib/python2.7/dist-packages/django/db/models/manager.py", line 122, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 391, in get
(self.model._meta.object_name, num)
MultipleObjectsReturned: get() returned more than one Publisher -- it returned 2!
>>>
如果查询的结果并不存在,也会抛出异常:
>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/local/lib/python2.7/dist-packages/django/db/models/manager.py", line 122, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 387, in get
self.model._meta.object_name
DoesNotExist: Publisher matching query does not exist.
>>>
这个DoesNotExist异常是Publisher这个model类的一个属性,即Publihser.DoesNotExist,在你的应用中,可以捕获并处理这个异常:
try:
p = Publisher.objects.get(name='Apress')
except Publihser.DoesNotExist:
print "Apress isn't in the database yet."
else:
print "Apress is in the database."
数据排序
在Django应用中,如果希望根据某字段的值对检索结果拍学,比如按字母顺序,可以使用”order_by()”方法:
>>> Publisher.objects.order_by("name")
[<Publisher: Apress>, <Publisher: O'Reilly>]
相当于SQL语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name;
如果需要以多个字段为标准进行排序(第二个字段会在第一个字段的值相同的情况下被使用到),使用多个参数即可:
>>> Publisher.objects.order_by("state_province", "address")
[<Publisher: Apress>, <Publisher: O'Reilly>]
还可以指定逆向排序,只需要再前面加一个减号前缀:
>>> Publisher.objects.order_by("‐name")
[<Publisher: O'Reilly>, <Publisher: Apress>]
虽然这种方式很灵活但是每次都要用”order_by()”显得有点啰嗦,大多数时间只会对某些字段进行排序,在这种情况下,Django可以使用指定模型的缺省排序方式:
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 __unicode__(self):
return self.name
class Meta:
ordering = ['name']
class Meta内嵌于Publisher这个类的定义中,可以在任意一个模型类中使用Meta类,来设置一些与特点模型相关的选项,如果你设置了这个选项,那么除非你检索时特意额外地使用了order_by(),否则,当你使用Django的数据库API去检索时,Publisher对象的相关返回默认地都会按name字段排序。
连锁查询
如果同时进行过滤和排序查询的操作,可以简单地写成这种“链式”的形式:
>>> Publisher.objects.filter(country='U.S.A').order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress>]
转换成SQL查询就是WHERE和ORDER_BY的组合:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A'
ORDER BY name DESC;
限制返回的数据
取出固定数目的记录,比如只想显示第一个,可以使用标准的Python列表裁剪语句:
>>> Publisher.objects.order_by('name')[0]
<Publisher: Apress>
相当于SQL语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
LIMIT 1;
同理,还可以使用Python的range-slicing语法来取出数据的特定子集:
>>> Publisher.objects.order_by('name')[0:2]
相当于SQL语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
OFFSET 0 LIMIT 2;
但是不支持Python的负索引negative slicing:
>>> Publisher.objects.order_by('name')[‐1]
Traceback (most recent call last):
...
AssertionError: Negative indexing is not supported.
但是可以使用其他的方法,比如,稍微修改order_by()语句:
>>> Publisher.objects.order_by('‐name')[0]
更新多个对象
save()方法会更新一行里的所有列,但是如果我们想更新一行里的某几列,比如将Apress Publisher的名称由原来的“Apress”更改为“Apress Publishing”,可以调用结果集(QuerySet)对象的update()方法:
>>> Publisher.objects.filter(id=52).update(name='Apress Publishing')
注意:这里假设Apress的ID为52
相当于SQL语句:
UPDATE books_publisher
SET name = 'Apress Publishing'
WHERE id = 52;
update()方法对于任何结果集(QuerySet)均有效,这意味着可以同时更新多条记录,例如将所有的Publisher的country字段值由’U.S.A’更改为’USA’:
>>> Publisher.objects.all().update(country='USA')
2
update()方法会返回一个整型数值,表示受影响的记录条数,在上面的例子中,这个值是2。
删除对象
删除数据库中的对象只需要调用该对象的delete()方法即可:
>>> p = Publisher.objects.get(name="O'Reilly")
>>> p.delete()
>>> Publisher.objects.all()
[<Publisher: Apress Publishing>]
同样可以在结果集上调用delete()方法同时删除多条记录:
>>> Publisher.objects.filter(country='USA').delete()
>>> Publisher.objects.all().delete()
>>> Publisher.objects.all()
[]
删除数据时要谨慎,为了预防误删除掉某一个表内的所有数据,Django要求在删除表内所有数据时显示使用all():
>>> Publisher.objects.delete()
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Manager' object has no attribute 'delete'
一旦使用all()方法,所有数据将会被删除,切记!