18.2 创建应用程序

Django项目 由一系列应用程序组成,它们协同工作,让项目成为一个整体。我们暂时只创建一个应用程序,它将完成项目的大部分工作。在第19章,我们将再添加一个管理用户账户的应用程序。

当前,在前面打开的终端窗口中应该还运行着runserver 。请再打开一个终端窗口(或标签页),并切换到manage.py所在的目录。激活该虚拟环境,再执行命令startapp

  1. learning_log$ source ll_env/bin/activate
  2. (ll_env)learning_log$ python manage.py startapp learning_logs
  3. (ll_env)learning_log$ ls
  4. db.sqlite3 learning_log learning_logs ll_env manage.py
  5. (ll_env)learning_log$ ls learning_logs/
  6. admin.py __init__.py migrations models.py tests.py views.py
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  

命令startapp appname 让Django建立创建应用程序所需的基础设施。如果现在查看项目目录,将看到其中新增了一个文件夹learning_logs(见❶)。打开这个文件夹,看看Django都创建了什么(见❷)。其中最重要的文件是models.py、admin.py和views.py。我们将使用models.py来定义我们要在应用程序中管理的数据。admin.py和views.py将在稍后介绍。

18.2.1 定义模型

我们来想想涉及的数据。每位用户都需要在学习笔记中创建很多主题。用户输入的每个条目都与特定主题相关联,这些条目将以文本的方式显示。我们还需要存储每个条目的时间戳,以便能够告诉用户各个条目都是什么时候创建的。

打开文件models.py,看看它当前包含哪些内容:

models.py

  1. from django.db import models
  2. # 在这里创建模型
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  

这为我们导入了模块models,还让我们创建自己的模型。模型告诉Django如何处理应用程序中存储的数据。在代码层面,模型就是一个类,就像前面讨论的每个类一样,包含属性和方法。下面是表示用户将要存储的主题的模型:

  1. from django.db import models
  2. class Topic(models.Model):
  3. """用户学习的主题"""
  4. text = models.CharField(max_length=200)
  5. date_added = models.DateTimeField(auto_now_add=True)
  6. def __str__(self):
  7. """返回模型的字符串表示"""
  8. return self.text
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  

我们创建了一个名为Topic 的类,它继承了Model ——Django中一个定义了模型基本功能的类。Topic 类只有两个属性:textdate_added

属性text是一个CharField——由字符或文本组成的数据(见❶)。需要存储少量的文本,如名称、标题或城市时,可使用CharField 。定义CharField 属性时,必须告诉Django该在数据库中预留多少空间。在这里,我们将max_length 设置成了200(即200个字符),这对存储大多数主题名来说足够了。

属性date_added 是一个DateTimeField ——记录日期和时间的数据(见❷)。我们传递了实参auto_add_now=True ,每当用户创建新主题时,这都让Django将这个属性自动设置成当前日期和时间。

注意  要获悉可在模型中使用的各种字段,请参阅Django Model Field Reference(Django模型字段参考),其网址为https://docs.djangoproject.com/en/1.8/ref/models/fields/ 。就当前而言,你无需全面了解其中的所有内容,但自己开发应用程序时,这些内容会提供极大的帮助。

我们需要告诉Django,默认应使用哪个属性来显示有关主题的信息。Django调用方法str() 来显示模型的简单表示。在这里,我们编写了方法str() ,它返回存储在属性text 中的字符串(见❸)。

注意  如果你使用的是Python 2.7,应调用方法unicode() ,而不是str() ,但其中的代码相同。

18.2.2 激活模型

要使用模型,必须让Django将应用程序包含到项目中。为此,打开settings.py(它位于目录learning_log/learning_log中),你将看到一个这样的片段,即告诉Django哪些应用程序安装在项目中:

settings.py

  1. --snip--
  2. INSTALLED_APPS = (
  3. 'django.contrib.admin',
  4. 'django.contrib.auth',
  5. 'django.contrib.contenttypes',
  6. 'django.contrib.sessions',
  7. 'django.contrib.messages',
  8. 'django.contrib.staticfiles',
  9. )
  10. --snip--
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  

这是一个元组,告诉Django项目是由哪些应用程序组成的。请将INSTALLED_APPS 修改成下面这样,将前面的应用程序添加到这个元组中:

  1. --snip--
  2. INSTALLED_APPS = (
  3. --snip--
  4. 'django.contrib.staticfiles',
  5. # 我的应用程序
  6. 'learning_logs',
  7. )
  8. --snip--
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  

通过将应用程序编组,在项目不断增大,包含更多的应用程序时,有助于对应用程序进行跟踪。这里新建了一个名为My apps的片段,当前它只包含应用程序learning_logs。

接下来,需要让Django修改数据库,使其能够存储与模型Topic 相关的信息。为此,在终端窗口中执行下面的命令:

  1. (ll_env)learning_log$ python manage.py makemigrations learning_logs
  2. Migrations for 'learning_logs':
  3. 0001_initial.py:
  4. - Create model Topic
  5. (ll_env)learning_log$
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  

命令makemigrations 让Django确定该如何修改数据库,使其能够存储与我们定义的新模型相关联的数据。输出表明Django创建了一个名为0001_initial.py的迁移文件,这个文件将在数据库中为模型Topic 创建一个表。

下面来应用这种迁移,让Django替我们修改数据库:

  1. (ll_env)learning_log$ python manage.py migrate
  2. --snip--
  3. Running migrations:
  4. Rendering model states... DONE
  5. Applying learning_logs.0001_initial... OK
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  

这个命令的大部分输出都与我们首次执行命令migrate的输出相同。我们需要检查的是❶处的输出行,在这里,Django确认为learning_logs 应用迁移时一切正常(OK )。

每当需要修改“学习笔记”管理的数据时,都采取如下三个步骤:修改models.py;对learning_logs 调用makemigrations ;让Django迁移项目。

18.2.3 Django管理网站

为应用程序定义模型时,Django提供的管理网站(admin site)让你能够轻松地处理模型。网站的管理员可使用管理网站,但普通用户不能使用。在本节中,我们将建立管理网站,并通过它使用模型Topic 来添加一些主题。

1. 创建超级用户

Django允许你创建具备所有权限的用户——超级用户。权限决定了用户可执行的操作。最严格的权限设置只允许用户阅读网站的公开信息;注册了的用户通常可阅读自己的私有数据,还可查看一些只有会员才能查看的信息。为有效地管理Web应用程序,网站所有者通常需要访问网站存储的所有信息。优秀的管理员会小心对待用户的敏感信息,因为用户对其访问的应用程序有极大的信任。

为在Django中创建超级用户,请执行下面的命令并按提示做:

  1. (ll_env)learning_log$ python manage.py createsuperuser
  2. Username (leave blank to use 'ehmatthes'): ll_admin
  3. Email address:
  4. Password:
  5. Password (again):
  6. Superuser created successfully.
  7. (ll_env)learning_log$
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  

你执行命令createsuperuser 时,Django提示你输入超级用户的用户名(见❶)。这里我们输入的是ll_admin,但你可以输入任何用户名,比如电子邮件地址,也可让这个字段为空(见❷)。你需要输入密码两次(见❸)。

注意  可能会对网站管理员隐藏有些敏感信息。例如,Django并不存储你输入的密码,而存储从该密码派生出来的一个字符串——散列值。每当你输入密码时,Django都计算其散列值,并将结果与存储的散列值进行比较。如果这两个散列值相同,就通过了身份验证。通过存储散列值,即便黑客获得了网站数据库的访问权,也只能获取其中存储的散列值,而无法获得密码。在网站配置正确的情况下,几乎无法根据散列值推导出原始密码。

2. 向管理网站注册模型

Django自动在管理网站中添加了一些模型,如UserGroup ,但对于我们创建的模型,必须手工进行注册。

我们创建应用程序learning_logs 时,Django在models.py所在的目录中创建了一个名为admin.py的文件:

admin.py

  1. from django.contrib import admin
  2. # 在这里注册你的模型
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  

为向管理网站注册Topic ,请输入下面的代码:

  1. from django.contrib import admin
  2. from learning_logs.models import Topic
  3. admin.site.register(Topic)
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  

这些代码导入我们要注册的模型Topic (见❶),再使用admin.site.register() (见❷)让Django通过管理网站管理我们的模型。

现在,使用超级用户账户访问管理网站:访问http://localhost:8000/admin/ ,并输入你刚创建的超级用户的用户名和密码,你将看到类似于图18-2所示的屏幕。这个网页让你能够添加和修改用户和用户组,还可以管理与刚才定义的模型Topic 相关的数据。

{%}

图18-2 包含模型Topic 的管理网站

注意  如果你在浏览器中看到一条消息,指出访问的网页不可用,请确认你在终端窗口中运行着Django服务器。如果没有,请激活虚拟环境,并执行命令python manage.py runserver

3. 添加主题

向管理网站注册Topic 后,我们来添加第一个主题。为此,单击Topics进入主题网页,它几乎是空的,这是因为我们还没有添加任何主题。单击Add,你将看到一个用于添加新主题的表单。在第一个方框中输入Chess ,再单击Save,这将返回到主题管理页面,其中包含刚创建的主题。

下面再创建一个主题,以便有更多的数据可供使用。再次单击Add,并创建另一个主题Rock Climbing 。当你单击Save时,将重新回到主题管理页面,其中包含主题Chess和Rock Climbing。

18.2.4 定义模型Entry

要记录学到的国际象棋和攀岩知识,需要为用户可在学习笔记中添加的条目定义模型。每个条目都与特定主题相关联,这种关系被称为多对一关系,即多个条目可关联到同一个主题。

下面是模型Entry 的代码:

models.py

  1. from django.db import models
  2. class Topic(models.Model):
  3. --snip--
  4. class Entry(models.Model):
  5. """学到的有关某个主题的具体知识"""
  6. topic = models.ForeignKey(Topic)
  7. text = models.TextField()
  8. date_added = models.DateTimeField(auto_now_add=True)
  9. class Meta:
  10. verbose_name_plural = 'entries'
  11. def __str__(self):
  12. """返回模型的字符串表示"""
  13. return self.text[:50] + "..."
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  

Topic 一样,Entry 也继承了Django基类Model (见❶)。第一个属性topic 是一个ForeignKey 实例(见❷)。外键是一个数据库术语,它引用了数据库中的另一条记录;这些代码将每个条目关联到特定的主题。每个主题创建时,都给它分配了一个键(或ID)。需要在两项数据之间建立联系时,Django使用与每项信息相关联的键。稍后我们将根据这些联系获取与特定主题相关联的所有条目。

接下来是属性text ,它是一个TextField 实例(见❸)。这种字段不需要长度限制,因为我们不想限制条目的长度。属性date_added 让我们能够按创建顺序呈现条目,并在每个条目旁边放置时间戳。

在❹处,我们在Entry 类中嵌套了Meta 类。Meta 存储用于管理模型的额外信息,在这里,它让我们能够设置一个特殊属性,让Django在需要时使用Entries 来表示多个条目。如果没有这个类, Django将使用Entrys来表示多个条目。最后,方法str() 告诉Django,呈现条目时应显示哪些信息。由于条目包含的文本可能很长,我们让Django只显示text 的前50个字符(见❺)。我们还添加了一个省略号,指出显示的并非整个条目。

18.2.5 迁移模型Entry

由于我们添加了一个新模型,因此需要再次迁移数据库。你将慢慢地对这个过程了如指掌:修改models.py,执行命令python manage.py makemigrations app_name ,再执行命令python manage.py migrate

下面来迁移数据库并查看输出:

  1. (ll_env)learning_log$ python manage.py makemigrations learning_logs
  2. Migrations for 'learning_logs':
  3. 0002_entry.py:
  4. - Create model Entry
  5. (ll_env)learning_log$ python manage.py migrate
  6. Operations to perform:
  7. --snip--
  8. Applying learning_logs.0002_entry... OK
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  

生成了一个新的迁移文件——0002_entry.py,它告诉Django如何修改数据库,使其能够存储与模型Entry 相关的信息(见❶)。执行命令migrate ,我们发现Django应用了这种迁移且一切顺利(见❷)。

18.2.6 向管理网站注册Entry

我们还需要注册模型Entry 。为此,需要将admin.py修改成类似于下面这样:

admin.py

  1. from django.contrib import admin
  2. from learning_logs.models import Topic, Entry
  3. admin.site.register(Topic)
  4. admin.site.register(Entry)
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  

返回到http://localhost/admin/ ,你将看到learning_logs下列出了Entries。单击Entries的Add链接,或者单击Entries再选择Add entry。你将看到一个下拉列表,让你能够选择要为哪个主题创建条目,还有一个用于输入条目的文本框。从下拉列表中选择Chess,并添加一个条目。下面是我添加的第一个条目。

The opening is the first part of the game, roughly the first ten moves or so. In the opening, it's a good idea to do three things— bring out your bishops and knights, try to control the center of the board, and castle your king.(国际象棋的第一个阶段是开局,大致是前10步左右。在开局阶段,最好做三件事情:将象和马调出来;努力控制棋盘的中间区域;用车将王护住。)

Of course, these are just guidelines. It will be important to learn when to follow these guidelines and when to disregard these suggestions.(当然,这些只是指导原则。学习什么情况下遵守这些原则、什么情况下不用遵守很重要。)

当你单击Save时,将返回到主条目管理页面。在这里,你将发现使用text[:50] 作为条目的字符串表示的好处:管理界面中,只显示了条目的开头部分而不是其所有文本,这使得管理多个条目容易得多。

再来创建一个国际象棋条目,并创建一个攀岩条目,以提供一些初始数据。下面是第二个国际象棋条目。

In the opening phase of the game, it's important to bring out your bishops and knights. These pieces are powerful and maneuverable enough to play a significant role in the beginning moves of a game.(在国际象棋的开局阶段,将象和马调出来很重要。这些棋子威力大,机动性强,在开局阶段扮演着重要角色。)

下面是第一个攀岩条目:

One of the most important concepts in climbing is to keep your weight on your feet as much as possible. There's a myth that climbers can hang all day on their arms. In reality, good climbers have practiced specific ways of keeping their weight over their feet whenever possible.(最重要的攀岩概念之一是尽可能让双脚承受体重。有谬误认为攀岩者能依靠手臂的力量坚持一整天。实际上,优秀的攀岩者都经过专门训练,能够尽可能让双脚承受体重。)

继续往下开发“学习笔记”时,这三个条目可为我们提供使用的数据。

18.2.7 Django shell

输入一些数据后,就可通过交互式终端会话以编程方式查看这些数据了。这种交互式环境称为Django shell,是测试项目和排除其故障的理想之地。下面是一个交互式shell会话示例:

  1. (ll_env)learning_log$ python manage.py shell
  2. >>> from learning_logs.models import Topic
  3. >>> Topic.objects.all()
  4. [<Topic: Chess>, <Topic: Rock Climbing>]
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  

在活动的虚拟环境中执行时,命令python manage.py shell 启动一个Python解释器,可使用它来探索存储在项目数据库中的数据。在这里,我们导入了模块learning_logs.models 中的模型Topic (见❶),然后使用方法Topic.objects.all() 来获取模型Topic 的所有实例;它返回的是一个列表,称为查询集(queryset)。

我们可以像遍历列表一样遍历查询集。下面演示了如何查看分配给每个主题对象的ID:

  1. >>> topics = Topic.objects.all()
  2. >>> for topic in topics:
  3. ... print(topic.id, topic)
  4. ...
  5. 1 Chess
  6. 2 Rock Climbing
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  

我们将返回的查询集存储在topics 中,然后打印每个主题的id 属性和字符串表示。从输出可知,主题Chess的ID为1,而Rock Climbing的ID为2。

知道对象的ID后,就可获取该对象并查看其任何属性。下面来看看主题Chess的属性textdate_added 的值:

  1. >>> t = Topic.objects.get(id=1)
  2. >>> t.text
  3. 'Chess'
  4. >>> t.date_added
  5. datetime.datetime(2015, 5, 28, 4, 39, 11, 989446, tzinfo=<UTC>)
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  

我们还可以查看与主题相关联的条目。前面我们给模型Entry 定义了属性topic ,这是一个ForeignKey ,将条目与主题关联起来。利用这种关联,Django能够获取与特定主题相关联的所有条目,如下所示:

  1. >>> t.entry_set.all()
  2. [<Entry: The opening is the first part of the game, roughly...>, <Entry: In the opening phase of the game, it's important t...>]
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  

为通过外键关系获取数据,可使用相关模型的小写名称、下划线和单词set(见❶)。例如,假设你有模型PizzaTopping ,而Topping通过一个外键关联到Pizza ;如果你有一个名为my_pizza 的对象,表示一张比萨,就可使用代码my_pizza.topping_set.all() 来获取这张比萨的所有配料。

编写用户可请求的网页时,我们将使用这种语法。确认代码能获取所需的数据时,shell很有帮助。如果代码在shell中的行为符合预期,那么它们在项目文件中也能正确地工作。如果代码引发了错误或获取的数据不符合预期,那么在简单的shell环境中排除故障要比在生成网页的文件中排除故障容易得多。我们不会太多地使用shell,但应继续使用它来熟悉对存储在项目中的数据进行访问的Django语法。

注意  每次修改模型后,你都需要重启shell,这样才能看到修改的效果。要退出shell会话,可按Ctr + D;如果你使用的是Windows系统,应按Ctr + Z,再按回车键。

 

动手试一试

18-2 简短的条目 :当前,Django在管理网站或shell中显示Entry 实例时,模型Entry 的方法str() 都在它的末尾加上省略号。请在方法str() 中添加一条if 语句,以便仅在条目长度超过50字符时才添加省略号。使用管理网站来添加一个长度少于50字符的条目,并核实显示它时没有省略号。

18-3 Django API :编写访问项目中的数据的代码时,你编写的是查询。请浏览有关如何查询数据的文档,其网址为https://docs.djangoproject.com/en/7.8/topics/db/queries/ 。其中大部分内容都是你不熟悉的,但等你自己开发项目时,这些内容会很有用。

18-4 比萨店 :新建一个名为pizzeria 的项目,并在其中添加一个名为pizzas 的应用程序。定义一个名为Pizza 的模型,它包含字段name ,用于存储比萨名称,如Hawaiian和Meat Lovers。定义一个名为Topping 的模型,它包含字段pizzaname ,其中字段pizza 是一个关联到Pizza 的外键,而字段name 用于存储配料,如pineappleCanadian baconsausage

向管理网站注册这两个模型,并使用管理网站输入一些比萨名和配料。使用shell来查看你输入的数据。