Python项目:Learning Log

Learning Log

1.从Django入手

Django是一个Web框架,即一套旨在帮助开发交互式网站的工具。

1.建立项目

1.制定规范

建立项目时,首先需要以规范的方式对项目进行描述

完整的规范详细说明了项目的目标,阐述了项目的功能,并讨论了项目的外观和用户界面。

当前项目的规范:

​ 编写一个名为“学习笔记”的Web应用程序,让用户能够记录感兴趣的主题,并在学习每个主题的过程中添加日志条目。“学习笔记”的主页对这个网站进行描述,并邀请用户注册或登录。用户登录后,可以创建新主题、添加新条目以及阅读既有的条目。

2.建立虚拟环境

虚拟环境是系统的一个位置,可在其中安装包,并将之与其他Python包隔离。

为项目新建一个目录,再在终端中切换到这个目录,并执行如下命令创建一个虚拟环境

C:\Users\zxc>S:

S:\>cd S:\pythonProgram\learning_log

S:\pythonProgram\learning_log>python -m venv learning_log_env

这里运行了模块venv,并使用它创建了一个名为learning_log_env虚拟空间

3.激活虚拟环境

learning_log_env\Scripts\activate这个命令运行learning_log_env\Scripts中的脚本activate

环境处于活动状态时,环境名将包含在圆括号内。在这种情况下,可以在环境中安装包,并使用已安装的包。在learning_log_env中安装的包仅在该环境处于活动状态时才可用。

要停止使用虚拟环境,可执行命令deactivate

如果关闭运行虚拟环境的终端,虚拟环境也将不再处于活动状态。

S:\pythonProgram\learning_log>learning_log_env\Scripts\activate
(learning_log_env) S:\pythonProgram\learning_log>

(learning_log_env) S:\pythonProgram\learning_log>deactivate

4.安装Django

激活虚拟环境后,安装Django,其仅在虚拟环境learning_log_env处于活动状态时才可用。

(learning_log_env) S:\pythonProgram\learning_log>pip install django
Collecting django
  Obtaining dependency information for django from https://files.pythonhosted.org/packages/2d/6d/e87236e3c7b2f5911d132034177aebb605f3953910cc429df8061b13bf10/Django-4.2.7-py3-none-any.whl.metadata
  Downloading Django-4.2.7-py3-none-any.whl.metadata (4.1 kB)
Collecting asgiref<4,>=3.6.0 (from django)
  Obtaining dependency information for asgiref<4,>=3.6.0 from https://files.pythonhosted.org/packages/9b/80/b9051a4a07ad231558fcd8ffc89232711b4e618c15cb7a392a17384bbeef/asgiref-3.7.2-py3-none-any.whl.metadata
  Downloading asgiref-3.7.2-py3-none-any.whl.metadata (9.2 kB)
Collecting sqlparse>=0.3.1 (from django)
  Using cached sqlparse-0.4.4-py3-none-any.whl (41 kB)
Collecting tzdata (from django)
  Using cached tzdata-2023.3-py2.py3-none-any.whl (341 kB)
Downloading Django-4.2.7-py3-none-any.whl (8.0 MB)
   ---------------------------------------- 8.0/8.0 MB 94.5 kB/s eta 0:00:00
Using cached asgiref-3.7.2-py3-none-any.whl (24 kB)
Installing collected packages: tzdata, sqlparse, asgiref, django
Successfully installed asgiref-3.7.2 django-4.2.7 sqlparse-0.4.4 tzdata-2023.3

[notice] A new release of pip is available: 23.2.1 -> 23.3.1
[notice] To update, run: python.exe -m pip install --upgrade pip

5.在Django中创建项目

新建一个项目,名为learning_log 。

命令末尾的句点让新项目使用合适的目录结构,这样开发完成后可轻松地将应用程序部署到服务器。

manage.py接受命令并将其交给Django地相关部分运行。使用这些命令管理使用数据库和运行服务器等任务。

目录learning_log中包含四个文件,最重要的是setting.py、urls.py和wsgi.py。

文件setting.py指定Django如何与系统交互以及如何管理项目。

文件urls.py告诉Django,应创建哪些页面来响应浏览器请求。

文件wsgi.py帮助Django提供它创建的文件,这个文件是Web服务器网关接口(Web server gateway interface)的首字母缩写。

(learning_log_env) S:\pythonProgram\learning_log>django-admin startproject learning_log .
(learning_log_env) S:\pythonProgram\learning_log>dir
 驱动器 S 中的卷是 Study
 卷的序列号是 B801-2FFF

 S:\pythonProgram\learning_log 的目录

2023/11/29  22:35    <DIR>          .
2023/11/29  22:35    <DIR>          ..
2023/11/29  22:35    <DIR>          learning_log
2023/11/29  22:28    <DIR>          learning_log_env
2023/11/29  22:35               690 manage.py
2023/11/29  21:30               758 学习笔记.md
               2 个文件          1,448 字节
               4 个目录 384,842,285,056 可用字节
(learning_log_env) S:\pythonProgram\learning_log>dir learning_log
 驱动器 S 中的卷是 Study
 卷的序列号是 B801-2FFF

 S:\pythonProgram\learning_log\learning_log 的目录

2023/11/29  22:35    <DIR>          .
2023/11/29  22:35    <DIR>          ..
2023/11/29  22:35               417 asgi.py
2023/11/29  22:35             3,362 settings.py
2023/11/29  22:35               790 urls.py
2023/11/29  22:35               417 wsgi.py
2023/11/29  22:35                 0 __init__.py
               5 个文件          4,986 字节
               2 个目录 384,842,285,056 可用字节

6.创建数据库

Django将大部分与项目有关的信息存储在数据库中,因此需要创建一个供Django使用的数据库。

修改数据库称为迁移数据库。首次执行命令migrate时,将让Django确保数据库与项目的当前状态匹配。

在使用SQLite的新项目中首次执行这个命令时,Django将新建一个数据库。

SQLite是一种使用单个文件的数据库。

(learning_log_env) S:\pythonProgram\learning_log>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 admin.0003_logentry_add_action_flag_choices... 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 auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
(learning_log_env) S:\pythonProgram\learning_log>dir
 驱动器 S 中的卷是 Study
 卷的序列号是 B801-2FFF

 S:\pythonProgram\learning_log 的目录

2023/11/29  22:39    <DIR>          .
2023/11/29  22:39    <DIR>          ..
2023/11/29  22:39           131,072 db.sqlite3
2023/11/29  22:39    <DIR>          learning_log
2023/11/29  22:28    <DIR>          learning_log_env
2023/11/29  22:35               690 manage.py
2023/11/29  21:30               758 学习笔记.md
               3 个文件        132,520 字节
               4 个目录 384,842,137,600 可用字节

7.查看项目

使用命令runserver查看项目的状态。

Django启动了一个名为development server的服务器,让你能够查看系统中的项目,了解其工作情况。

如果你在浏览器中输入URL以请求页面,该Django服务器将进行响应:生成合适的页面,并将其发送给浏览器。

URL http://127.0.0.1:8000/表明项目将在你的计算机(即localhost)的端口8000上侦听请求。

localhost指的是只处理当前系统发出的请求,而不允许其他任何人查看你正在开发的页面的浏览器。

如果出现错误信息 That port is already in use(指定端口被占用),执行命令python manage.py runserver 8001,让Django

使用另外一个端口。如果这个端口也不可用,不断执行上述命令,并逐级增大其中的端口号,直到找到可用的端口。

(learning_log_env) S:\pythonProgram\learning_log>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
November 29, 2023 - 22:42:17
Django version 4.2.7, using settings 'learning_log.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

2.创建应用程序

Django项目由一系列应用程序组成,它们协同工作让项目成为一个整体。

再打开一个终端窗口,运行下列命令。

命令startapp learning_logs让Django搭建创建应用程序所需的基础设施,其会新创建一个learning_logs的文件夹。

文件夹中models.py定义了要在应用程序中管理的数据。

(learning_log_env) S:\pythonProgram\learning_log>python manage.py startapp learning_logs

(learning_log_env) S:\pythonProgram\learning_log>dir
 驱动器 S 中的卷是 Study
 卷的序列号是 B801-2FFF

 S:\pythonProgram\learning_log 的目录

2023/11/30  15:43    <DIR>          .
2023/11/30  15:43    <DIR>          ..
2023/11/29  23:26                 0 cd
2023/11/29  22:39           131,072 db.sqlite3
2023/11/29  22:39    <DIR>          learning_log
2023/11/30  15:43    <DIR>          learning_logs
2023/11/29  22:28    <DIR>          learning_log_env
2023/11/29  22:35               690 manage.py
2023/11/29  23:31             6,190 学习笔记.md
               4 个文件        137,952 字节
               5 个目录 384,842,125,312 可用字节
               
(learning_log_env) S:\pythonProgram\learning_log>dir learning_logs\
 驱动器 S 中的卷是 Study
 卷的序列号是 B801-2FFF

 S:\pythonProgram\learning_log\learning_logs 的目录

2023/11/30  15:43    <DIR>          .
2023/11/30  15:43    <DIR>          ..
2023/11/30  15:43                66 admin.py
2023/11/30  15:43               163 apps.py
2023/11/30  15:43    <DIR>          migrations
2023/11/30  15:43                60 models.py
2023/11/30  15:43                63 tests.py
2023/11/30  15:43                66 views.py
2023/11/30  15:43                 0 __init__.py
               6 个文件            418 字节
               3 个目录 384,842,125,312 可用字节

1.定义模型

导入模块models,创建模型。模型告诉Django如何处理应用程序中存储的数据。

在代码层面,模型就是一个类,包含属性和方法。

Topic类,继承Model,即Django中定义了模型基本功能的类。

​ 其中有两个属性

​ 属性text是一个CharField——由字符组成的数据,即文本。存储少量文本,如名称、标题或城市时,可使用。

​ 定义CharField属性时,必须告诉Django在数据库中预留多少空间,使用max_length。

​ 属性date_added是一个DateTimeField——记录日期和时间的数据。

​ 实参auto_now_add=True表示,每次创建新主题时,将这个属性自动设置为当前日期和时间。

​ Django调用方法_str__()来显示模型的简单表示。

Models | Django documentation | Django (djangoproject.com)

from django.db import models

# Create your models here.
class Topic(models.Model):
	"""用户学习的主题"""
	text = models.CharField(max_length=200)
	date_added = models.DateTimeField(auto_now_add=True)

	def __str__(self):
		"""返回模型的字符串表示。"""
		return self.text

2.激活模型

要使用模型,必须让Django将应用程序包含到项目中

打开settings.py(位于目录learning_log\learning_log中),片段INSTALLED_APPS告诉Django哪些应用程序被安装到了项目中并将协同工作。

通过将应用程序编组,在项目不断增大,包含更多的应用程序时,有助于对应用程序进行跟踪。

务必将自己创建的应用程序放在默认应用程序前面,这样能覆盖默认应用程序的行为。

INSTALLED_APPS = [
    # 我的应用程序
    'learning_logs',

    # 默认添加的应用程序
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

让Django修改数据库,使其能够存储与模型Topic相关的信息。

命令makemigrations让Django确定该如何修改数据库,使其能够存储与前面定义的新模型相关联的数据。

输出表明Django创建了一个名为0001_initial.py的迁移文件,其将在数据库中为模型Topic创建一个表。

(learning_log_env) S:\pythonProgram\learning_log>python manage.py makemigrations learning_logs
Migrations for 'learning_logs':
  learning_logs\migrations\0001_initial.py
    - Create model Topic

然后应用这种迁移,让Django修改数据库

(learning_log_env) S:\pythonProgram\learning_log>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
  Applying learning_logs.0001_initial... OK

每次修改管理的数据时,都采取如下三个步骤:修改models.py,对learning_logs调用makemigrations,以及让Django迁移项目。

3.Django管理网站

Django提供的管理网站能够轻松地处理模型。网站管理员可以使用管理网站,但普通用户不能使用。

1.创建超级用户

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

执行命令createsuperuser时,Django提示输入超级用户地用户名,输入电子邮箱地址,电子邮箱可以为空,输入两次密码。

Django并不存储你输入的密码,而是存储从该密码派生出来的一个字符串,称为散列值。每当你输入密码时,Django都计算其散列值,并将结果与存储的散列值进行比较。如果这两个散列值相同,就通过了身份验证。在网站配置正确的情况下,几乎无法根据散列值推导出原始密码。

(learning_log_env) S:\pythonProgram\learning_log>python manage.py createsuperuser
Username (leave blank to use 'zxc'): learningl_log_admin
Email address:
Password:
Password (again):
Superuser created successfully.
账号:learningl_log_admin
密码:123456789
2.向管理网站注册模型

Django自动在管理网站中添加了一些模型,如User和Group,但自己创建的模型,必须手工进行注册。

导入要注册的模型Topic,models前面的句点让Django在admin.py所在的目录查找models.py。

admin.site.register()让Django通过管理网站管理模型。

注册完后,使用超级用户账户访问管理网站:访问http://localhost:8000/admin/,并输入刚创建的超级用户的用户名和密码。

当前页面能够添加和修改用户和用户组,还可管理与刚才定义的模型Topic相关的数据。

from django.contrib import admin

from .models import Topic, Entry
# Register your models here.
admin.site.register(Topic)
3.添加主题

单击Topics进入主题页面,它几乎是空的,因为还没有添加任何主题。单击Add,将出现一个用于添加新主题的表单。在第一个方框中输入Chess,再单击Save回到主题管理页面,其中包含刚创建的主题。再创建一个Rock Climbing的主题。

4.定义模型Entry

每个条目都与特定主题相关联,这种关系成为多对一关系,即多个条目可关联到同一个主题。

Entry继承了Django基类Model。

​ 第一个属性topic是个ForeignKey实例。

​ 外键是一个数据库术语,它指向数据库中的另一条记录。

​ 创建每个主题时,都分配了一个键(ID)。需要在两项数据之间建立联系时,Django使用与每项信息相关联的键。

​ 实参on_delete=models.CASCADE让Django在删除主题的同时删除所有与之相关联的条目,称为级联删除。

​ 属性text是一个TextField实例。这种字段的长度不受限制。

​ 属性date_added按创建顺序呈现条目,并在每个条目旁边放置时间戳。

Entry类中嵌套了Meta类。Meta存储用于管理模型的额外信息。

​ 在这里,设置了一个特殊属性,让Django在需要时使用Entries表示多个条目。如果没有这个类,Django将使用Entrys来表示多个条目。

方法_str__()告诉Django,呈现条目时应显示哪些信息。

class Entry(models.Model):
	"""学到的有关某个主题的具体知识。"""
	topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
	text = models.TextField()
	date_added = models.DateTimeField(auto_now_add=True)

	class Meta:
		verbose_name_plural = 'entries'

	def __str__(self):
		"""返回模型的字符串表示。"""
		if len(self.text)>50 :
			return f"{self.text[:50]}..."
		else: return f"{self.text}"

5.迁移模型Entry

(learning_log_env) S:\pythonProgram\learning_log>python manage.py makemigrations learning_logs
Migrations for 'learning_logs':
  learning_logs\migrations\0002_entry.py
    - Create model Entry

(learning_log_env) S:\pythonProgram\learning_log>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
  Applying learning_logs.0002_entry... OK

6.向管理网站注册Entry

from django.contrib import admin

from .models import Topic, Entry
# Register your models here.
admin.site.register(Topic)
admin.site.register(Entry)

返回到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.(当然,这些只是指导原则。学习什么 情况下遵守这些原则、什么情况下不用遵守很重要。)

再来创建一个国际象棋(Chess)条目。

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.(在国际象棋的开局阶段,将象和马调出来很重要。这些棋子威力大,机动性强,在开局阶段扮演着重要角色。)

在创建一个攀岩(Rock Climbing)条目。

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. (最重要的攀岩概念之一是尽可能让双脚承受体重。有人误认为攀岩者能依靠手臂的力量坚持一整天。实际上,优秀的攀岩者都经过专门训练,能够尽可能让双脚承受体重。)

7.Django shell

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

在活动状态的虚拟环境中执行时,命令python manage.py shell 启动Python解释器,探索存储在项目数据库中的数据。

这里导入模块learning_logs.models 中的模型t Topic,再使用方法Topic.objects.all()获取模型Topic的所有实例,返回一个称为查询集的列表。可以像遍历列表一样遍历查询集。

(learning_log_env) S:\pythonProgram\learning_log>python manage.py shell
Python 3.11.5 | packaged by Anaconda, Inc. | (main, Sep 11 2023, 13:26:23) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from learning_logs.models import Topic
>>> Topic.objects.all()
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]>

将返回的查询集存储在topics中,再打印每个主题的id属性和字符串表示。

>>> topics = Topic.objects.all()
>>> for topic in topics:
...     print(topic.id, topic)
...
1 Chess
2 Rock Climbing

知道主题对象的ID后,就可使用方法Topic.objects.get()获取该对象并查看其属性。

>>> t = Topic.objects.get(id=1)
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2023, 11, 30, 8, 17, 54, 19694, tzinfo=datetime.timezone.utc)

查看与主题相关联的条目。

Django能够获取与特定主题相关联的所有条目。

要通过外键关系获取数据,可使用相关模型的小写名称、下划线和单词set。

>>> t.entry_set.all()
<QuerySet [<Entry: 。。。。...>, <Entry: ..............>, <Entry: ,,,,,,,,,,,...>]>
>>> ^Z

now exiting InteractiveConsole...

(learning_log_env) S:\pythonProgram\learning_log>

如果代码在shell中的行为符合预期,那么它们在项目文件中也能正确地工作。如果代码引发了错误或获取的数据不符合预期,那么在简单的shell环境中排除故障要比在生成页面的文件中排除故障容易得多。

退出shell会话,按Ctr+Z,再按回车键。

3.创建页面:学习笔记主页

使用Django创建页面的过程分为三个阶段:定义URL,编写视图和编写模板。

URL模式描述了URL是如何设计的,让Django知道如何将浏览器请求与网站URL匹配,以确定返回哪个页面。每个URL都被映射到特定的视图——视图函数获取并处理页面所需的数据。视图函数通常使用模板来渲染页面,而模板定义页面的总体结构。

1.映射URL

用户通过在浏览器中输入URL以及单击链接来请求页面,因此要确认项目需要哪些URL。

主页的URL最重要,它hi用户用来访问项目的基础URL。

打开learning_log中的文件urls.py,在这个针对整个项目的urls.py文件中,变量urlpatterns包含项目中应用程序的URL。其中包含模块admin.site.urls,定义了可在管理网站中请求的所有URL。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path('', include('learning_logs.urls')),
]

默认的urls.py包含在文件夹learning_log中,在文件夹learning_logs中再创建一个urls.py文件。

为指出当前位于哪个urls.py文件中,在该文件开头添加一个文档字符串。

导入函数path,需要使用它将URL映射到视图。

导入模块views,其中的句点让Python从当前urls.py模块所在的文件夹导入view.py。

变量app_name让Django能够将这个urls.py文件同项目内其他应用程序中的同名文件区分开来。

变量urlpatterns是一个列表,包含可在应用程序learning_logs中请求的页面。

实际的URL模式是对函数path()的调用,其接受三个参数。

​ 第一个是一个字符串,帮助Django正确路由请求。收到请求的URL后,Django力图将请求路由给一个视图。为此,它搜索所有的URL模式,找到与当前请求匹配的那个。Django忽略项目的额基础URL(http://localhost:8000/),因此空字符串(‘’)与基础URL匹配。如果请求的URL与任何既有的URL模式不匹配,Django将返回一个错误页面。

​ 第二个实参指定要调用view.py中的哪个函数。

​ 第三个参数将这个URL模式的名称指定为index,让我们能够在代码的其他地方引用它。每当需要提供这个主页的链接时,都将使用这个名称,而不编写URL。

"""定义learning_logs的URL模式。"""

from django.urls import path

from . import views

app_name = 'learning_logs'
urlpatterns = [
	# 主页
	path('', views.index, name='index'),
]

2.编写视图

视图函数接受请求中的信息,准备好生成页面所需的数据,再将这些数据发送给浏览器——这通常是使用定义页面外观的模板实现的。

函数render(),它根据视图提供的数据渲染响应。

这里向函数render()提供了两个实参:对象request以及一个可用于创建页面的模板。

from django.shortcuts import render

from .models import Topic
# Create your views here.
def index(request):
	"""学习笔记的主页。"""
	return render(request, 'learning_logs/index.html')

3.编写模板

模板定义页面的外观,而每当页面被请求时,Django将填入相关的数据。模板让你能够访问视图提供的任何数据。

在文件夹learning_logs中新建一个文件夹,并将其命名为templates。在文件夹templates中,再新建一个文件夹,并将其命名为learning_logs。其建立了Django能够明确解读的结构。

在最里面的文件夹learning_logs中,新建一个文件,命名为index.html。

<p>Learning Log</p>

<p>Learning Log helps you keep track of your learning, for any topic you're learning about.</p>

4.创建其他页面

创建一个父模板,项目中其他模板继承它。

1.模板继承

创建网站时,一些通用元素几乎会在所有页面中出现。在这种情况下,可编写一个包含通用元素的父模板,并让每个页面都继承这个模板,而不必在每个页面中重复定义这些通用元素。

1.父模板

创建一个名为base.html的模板,并将其存储在index.html所在的目录中,这个模板包含所有页面都有的元素,而其他模板都继承它。

为创建链接,使用了一个模板标签,它是用花括号和百分号({% %})表示的。

模板标签是一小段代码,生成要在页面中显示的信息。

这里的模板标签{% url ‘learning_logs:index’ %}生成一个URL,该URL与在learning_logs/urls.py中定义的名为’index’的URL模式匹配。

在本例中,learning_logs是一个命名空间,而index是该命名空间中一个名称独特的URL模式。

这个命名空间来自在文件learning_logs/urls.py中赋给app_name的值。

通过使用模板标签来生成URL,能很容易地确保链接是最新的:只需修改urls.py中的URL模式,Django就会在页面下次被请求时自动插入修改后的URL。

接着插入一对块标签。块名为content,是一个占位符,其中包含的信息由子模板指定。

子模板并非必须定义父模板中的每个块,因此在父模板中,可使用任意多个块来预留空间,而子模板可根据需要定义相应数量的块。

<p>
	<a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>

{% block content %}{% endblock content %}
2.子模板

重写index.html。

子模板的第一行必须包含标签{% extends %},让Django知道它继承了哪个父模板。

接着插入一个名为content的{% block %}标签,以定义content块。使用标签{% endblock %}指出了内容定义的结束位置。在标签{% endblock %}中,并非必须指定块名,但如果模板存在多个块,指定块名有助于确定结束的是哪个块。

在大型项目中,通常有一个用于整个网站的父模板base.html,且网站的每个主要部分都有一个父模板。每个部分的父模板都继承base.html,而网站的每个页面都继承相应部分的父模板。

{% extends "learning_logs/base.html" %}

{% block content %}
<p>Learning Log helps you keep track of your learning, for any topic you're learning about.</p>
{% endblock content %}

2.显示所有主题的页面

1.URL模式

定义显示所有主题的页面的URL,使用一个简单的URL片段来指出页面显示的信息,这里使用单词topics。

这里在用于主页URL的字符串参数中添加topics/。Django检查请求的URL时,这个模式与如下URL匹配:基础URL后面跟着topics。可在末尾包含斜杠,也可省略,但单词topics后面不能有任何东西,否则就与该模式不匹配。

	# 显示所有的主题。
	path('topics/', views.topics, name='topics'),
2.视图

函数topics()需要从数据库中获取一些数据,并将其交给模板。

在view.py中添加如下代码。

首先导入与所需数据相关联的模型。

函数topics()包含一个形参:Django从服务器收到的request对象。

接着查询数据库——请求提供Topic对象,并根据属性date_added进行排序。

然后定义一个将发送给模板的上下文。上下文是一个字典,其中的键是将在模板中用来访问数据的名称,而值是要发送给模板的数据。

from .models import Topic

def topics(request):
	"""显示所有的主题。"""
	topics = Topic.objects.order_by('date_added')
	context = {'topics': topics}
	return render(request, 'learning_logs/topics.html', context)
3.模板

模板接受字典context,以便使用topics()提供的数据。

创建一个名为topics.html的文件,存储到index.html所在的目录。

这个页面的主体是一个项目列表。

使用一个相当于for循环的模板标签,遍历字典context中的列表topics。

模板代码与Python代码的差别:在模板中,每个for循环都必须使用{% endfor %}标签来显示地指出其结束位置。

要在模板中打印变量,需要将变量名用双花括号括起。

使用模板标签{% empty %}告诉Django在列表topics为空时该如何办。

{% extends "learning_logs/base.html" %}

{% block content %}

	<p>Topics</p>

	<ul>
		{% for topic in topics %}
			<li>{{ topic }}</li>
		{% empty %}
			<li>No topics have been added yet.</li>
		{% endfor %}
	</ul>

{% endblock content %}

修改父模板,使其包含显示到所有主题地页面地链接。

<a href="{% url 'learning_logs:topics' %}">Topics</a>

3.显示特定主题的页面

1.URL模式

使用主题id的属性来指出请求的是哪个主题。

在learning_logs/urls.py加入下面代码

/<int:topic_id>/与包含在两个斜杠内的整数匹配,并将这个整数存储在一个名为topic_id的实参中,传递给视图函数topic(),如http://loaclhost:8000/topics/1/。

# 特定主题的详细页面。
	path('topics/<int:topic_id>/', views.topic, name='topic'),
2.视图

函数topic()需要从数据库中获取指定的主题以及与之相关联的所有条目。

这个函数接受表达式/<int:topic_id>/捕获的值,并将其存储到topic_id中。

使用get()来获取指定的主题。

然后获取与该主题相关联的条目,并根据date_added进行排序:date_added前面的减号指定按降序排列,即先显示最近的条目。

比起先编写视图和模板、再在浏览器中检查结果,在shell中执行代码可更快获得反馈。

def topic(request, topic_id):
	"""显示单个主题及其所有的条目。"""
	topic = Topic.objects.get(id=topic_id)
	entries = topic.entry_set.order_by('-date_added')
	context = {'topic':topic, 'entries': entries}
	return render(request, 'learning_logs/topic.html', context)
3.模板

这个模板显示主题的名称和条目的内容,如果当前主题不包含任何条目,向用户指出。

显示当前主题,存储在模板变量{{ topic }}中,模板变量包含在字典context中。

每个项目列表都列出两项信息:条目的时间戳和完整的文本。

​ 为列出时间戳,显示属性date_added的值。

​ 在Django模板中,竖线|表示模板过滤器,即对模板变量的值进行修改的函数。

​ 过滤器date:'M d, Y H:i’以类似于January 1, 2018 23:00的格式显示时间戳。

​ 过滤器linebreaks将包含换行符的长条目转换为浏览器能够理解的格式。

{% extends "learning_logs/base.html" %}

{% block content %}

	<p>Topic: {{ topic }}</p>

	<p>Entries:</p>
	<ul>
	{% for entry in entries %}
		<li>
			<p>{{ entry.date.added|date:'M d, Y H:i' }}</p>
			<p>{{ entry.text|linebreaks }}</p>
		</li>
		{% empty %}
			<li>There are no entries for this topic yet.</li>
		{% endfor %}
	</ul>
	
{% endblock content %}
4.将显示所有主题的页面中的主题设置为链接

修改模块topics.html,让每个主题都连接到相应的页面。

使用模板标签url根据learning_logs中名为’topic’的URL模式生成了合适的链接。

这个URL模式要求提供实参topic_id,因此在模板标签url中添加了属性topic.id

{% for topic in topics %}
	<!-- <li>{{ topic }}</li> -->
	<li>
    	<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
	</li>
{% empty %}

2.用户账户

实现一个用户身份验证系统。

1.让用户输入数据

使用Django的表单创建工具来创建让用户能够输入数据的页面。

1.添加新主题

与前面创建页面的主要差别是导入包含表单的模块forms.py

1.用于添加主题的表单

让用户输入并提交信息的页面都是表单。

用户输入信息时,需要进行验证,确认提供的信息是正确的数据类型,而不是恶意的信息,如中断服务器的代码。然后,对这些有效信息进行处理,并将其保存到数据库的合适地方。这些工作很多都是由Django自动完成。

在Django中,创建表单的最简单方式是使用ModelForm,它根据定义的模型中的信息自动创建表单。

命名一个forms.py的文件,存储到models.py所在的目录。

导入forms以及要使用的模型Topic。

定义了一个名为TopicForm的类,继承了forms.ModelForm。

最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪个模型创建表单以及在表单中包含哪些字段。

接着,根据模型Topic创建表单,其中只包含字段text,让Django不要为字段text生成标签。

from django import forms

from .models import Topic

class TopicForm(forms.ModelForm):
	class Meta:
		model = Topic
		fields = ['text']
		labels = {'text': ''}
2.URL模式new_topic

新页面的URL应简短且具有描述性,因此当用户要添加新主题时,切换到http://localhost:8000/new_topic/。

将下列代码添加到learning_logs/urls.py

# 用于添加新主题的页面。
path('new_topic/', views.new_topic, name='new_topic'),
3.视图函数

函数new_topic需要处理两种情形。一是刚进入new_topic页面(在这种情况下应显示空表单);二是对提交的表单数据进行处理,并将用户重定向到页面topics。

导入函数redirect,用户提交主题后将使用这个函数重定向到页面topics。函数redirect将视图名作为参数,并将用户重定向到这个视图。

from django.shortcuts import render, redirect

from .forms import TopicForm

def new_topic(request):
	"""添加新主题。"""
	if request.method != 'POST':
		# 未提交数据:创建一个新表单。
		form = TopicForm()
	else:
		# POST提交的数据:对数据进行处理。
		form = TopicForm(data=request.POST)
		if form.is_valid():
			form.save()
			return redirect('learning_logs:topics')

	# 显示空表单或指出表单数据无效。
	context = {'form': form}
	return render(request, 'learning_logs/new_topic.html', context)
4.GET请求和POST请求

创建Web应用程序时,将用到的两种主要请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户需要通过表单提交信息时,通常使用POST请求。

3中函数new_topic()将请求对象作为参数。用户初次请求该页面时,其浏览器将发送GET请求;用户填写并提交表单时,其浏览器将发送POST请求。根据请求的类型,可确定用户请求的是空表单(GET请求)还是要求对填写好的表单进行处理(POST请求)。

如果请求方法不是POST,请求可能是GET,返回一个空表单。创建一个TopicForm实例,将其赋给变量form,再通过上下文字典将这个表单发送给模块。由于实例化TopicForm时没有指定任何实参,Django创建一个空表单,供用户填写。

如果请求方法是POST,对提交的表单数据进行处理。使用用户输入的数据(存储再request.POST中)创建一个TopicForm实例。

要将提交的信息保存到数据库,必须先通过检查确定它们是有效的。方法is_valid()核实用户填写了所有必不可少的字段(表单字段默认都是必不可少的),其输入的数据与要求的字段类型一致。这种自动验证避免了我们去做大量工作。如果所有字段都有效,调用save(),将表单中的数据写入数据库。

保存数据后,使用redirect()将用户的浏览器重定向到页面topics。

用户提交的表单数据无效时,将显示一些默认的错误信息,帮助用户提供有效的数据。

5.模板new_topic

这个模板继承了base.html。

定义了一个HTML表单。

实参action告诉服务器将提交的表单数据发送到哪里。这里将它发回给视图函数new_topic()。

实参method让浏览器以POST请求的方式提交数据。

Django使用模板标签{% csrf_token %}来防止攻击者利用表单来获得对服务器未经授权得访问(这种攻击称为跨站请求伪造)。

Django完成显示表单等任务只需包含模块变量{{ form.as_p }},自动创建显示表单所需得全部字段。修饰符as_p让Django以段落格式渲染所有表单元素。

{% extends "learning_logs/base.html" %}

{% block content %}
	<p>Add a new topic:</p>

	<form action="{% url 'learning_logs:new_topic' %}" method="post">
		{% csrf_token %}
		{{ form.as_p }}
		<button name="submit">Add topic</button>
	</form>
	
{% endblock content %}
6.链接到页面new_topic

在页面topics中添加到页面new_topic的链接。这个链接放在既有的主题列表的后面。添加几个新主题:dance,sing。

<a href="{% url 'learning_logs:new_topic' %}">Add a new topic</a>

2.添加新条目

1.用于添加新条目的表单

创建一个与模型Entry相关联的表单。

其中,Meta类定义了属性widgets。小部件(widget)是一个HTML表单元素。通过设置属性widgets,可覆盖Django选择的默认小部件。通过让Django使用forms.Textarea,定制字段’text’的输入小部件,将文本区域的宽度设置为80列,而不是默认的40列。

from .models import Topic, Entry

class EntryForm(forms.ModelForm):
	class Meta:
		model = Entry
		fields = ['text']
		labels = {'text': ''}
		widgets = {'text': forms.Textarea(attrs={'cols': 80})}
2.URL模式new_entry

用于添加新条目的页面的URL模式中,包含实参topic_id,因为条目必须与特定的主题相关联。

将下面添加到learning_logs/urls.py

这个URL模式与形如http://localhost:8000/new_entry/id/的URL匹配,其中的id是一个与主题ID匹配的数。代码int:topic_id捕获一个数值,并将其赋给变量topic_id。请求的URL与这个模式匹配时,Django将请求和主题ID发送给函数new_entry()。

# 用于添加新条目的页面。
path('new_entry/<int:topic_id>/', views.new_entry, name='new_entry'),
3.视图函数new_entry()

在views.py中添加如下代码

调用save()时,传递实参commit=False,让Django创建一个新的条目对象,并将其赋给new_entry,但不保存到数据库中。将属性topic设置为在这个函数开头从数据库中获取的主题,再调用save()且不指定任何实参。这将条目保存到数据库,并将其与正确的主题相关联。

调用redirect(),它要求提供两个参数:要重定向到的视图和要给视图函数提供的参数。

from .forms import TopicForm, EntryForm

def new_entry(request, topic_id):
	"""在特定主题中添加新条目。"""
	topic = Topic.objects.get(id=topic_id)

	if request.method != 'POST':
		# 未提交数据:创建一个空表单。
		form = EntryForm()
	else:
		# POST提交的数据:对数据进行处理。
		form = EntryForm(data=request.POST)
		if form.is_valid():
			new_entry = form.save(commit=False)
			new_entry.topic = topic
			new_entry.save()
			return redirect('learning_logs:topic', topic_id=topic_id)

	# 显示空表单或指出表单数据无效。
	context = {'topic': topic, 'form': form}
	return render(request, 'learning_logs/new_entry.html', context)
4.模板new_entry

创建模板new_entry.html

表单实参action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题。

{% extends "learning_logs/base.html" %}

{% block content %}
	<p><a href="{% url 'learning_logs:topic' topic.id %">{{ topic }}</a> </p>

	<p>Add a new entry:</p>
	<form action="{% url 'learning_logs:new_entry' topic.id %}" method="post">
		{% csrf_token %}
		{{ form.as_p }}
		<button name="submit">Add entry</button>
	</form>
	
{% endblock content %}
5.链接到页面new_entry

在topic.html中添加到页面new_entry的链接。

<p>
	<a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a>
</p>

3.编辑条目

1.URL模式edit_entry

这个页面的URL需要传递要编辑的条目的ID。

在URL(如http://localhost:8000/edit_entry/1/)中传递的ID存储在形参entry_id中。

# 用于编辑条目的页面。
path('edit_entry/<int:entry_id>/', views.edit_entry, name='edit_entry'), 
2.视图函数edit_entry()

页面edit_entry收到GET请求时,edit_entry()将返回一个表单,让用户能够对条目进行编辑;收到POST请求(条目文本经过编辑)时,将修改后的文本保存到数据库。

使用参数instance=entry创建一个实例,这个实参让Django创建一个表单,并使用既有条目对象中的信息填充它。用户将看到既有的数据,并能够编辑。

from .models import Topic, Entry

def edit_entry(request, entry_id):
	"""编辑既有条目。"""
	entry = Entry.objects.get(id=entry_id)
	topic = entry.topic

	if request.method != 'POST':
		# 初次请求:使用当前条目填充表单。
		form = EntryForm(instance=entry)
	else:
		# POST提交的数据:对数据进行处理。
		form = EntryForm(instance=entry, data=request.POST)
		if form.is_valid():
			form.save()
			return redirect('learning_logs:topic', topic_id=topic.id)

	context = {'entry':entry, 'topic': topic, 'form': form}
	return render(request, 'learning_logs/edit_entry.html', context)
3.模板edit_entry

创建模板edit_entry.html

{% extends "learning_logs/base.html" %}

{% block content %}

	<p><a href="{% url 'learning_logs:topic' topic.id %">{{ topic }}</a> </p>

	<p>Edit entry:</p>

	<form action="{% url 'learning_logs:edit_entry' entry.id %}" method="post">
		{% csrf_token %}
		{{ form.as_p }}
		<button name="submit">Save changes</button>
	</form>
	
{% endblock content %}
4.链接到页面edit_entry

在topic.html加入下列代码。

<p>
	<a href="{% url 'learning_logs:edit_entry' entry.id %}">Edit entry</a>
</p>

2.创建用户账户

建立用户注册和身份验证系统,让用户能够注册用户,进而登录和注销。

使用Django自带的用户身份验证系统来完成工作。

1应用程序users

使用命令startapp创建一个名为users的应用程序。


(learning_log_env) S:\pythonProgram\learning_log>python manage.py startapp users

(learning_log_env) S:\pythonProgram\learning_log>dir
 驱动器 S 中的卷是 Study
 卷的序列号是 B801-2FFF

 S:\pythonProgram\learning_log 的目录

2023/12/01  18:54    <DIR>          .
2023/12/01  18:54    <DIR>          ..
2023/11/29  23:26                 0 cd
2023/12/01  18:31           143,360 db.sqlite3
2023/11/29  22:39    <DIR>          learning_log
2023/12/01  17:45    <DIR>          learning_logs
2023/11/29  22:28    <DIR>          learning_log_env
2023/11/29  22:35               690 manage.py
2023/12/01  18:54    <DIR>          users
2023/12/01  16:32            34,558 学习笔记.md
               4 个文件        178,608 字节
               6 个目录 384,786,116,608 可用字节

(learning_log_env) S:\pythonProgram\learning_log>dir users
 驱动器 S 中的卷是 Study
 卷的序列号是 B801-2FFF

 S:\pythonProgram\learning_log\users 的目录

2023/12/01  18:54    <DIR>          .
2023/12/01  18:54    <DIR>          ..
2023/12/01  18:54                66 admin.py
2023/12/01  18:54               148 apps.py
2023/12/01  18:54    <DIR>          migrations
2023/12/01  18:54                60 models.py
2023/12/01  18:54                63 tests.py
2023/12/01  18:54                66 views.py
2023/12/01  18:54                 0 __init__.py
               6 个文件            403 字节
               3 个目录 384,786,116,608 可用字节

(learning_log_env) S:\pythonProgram\learning_log>

2.将users添加到settings.py中

将应用程序users包含到项目中。

INSTALLED_APPS = [
    # 我的应用程序
    'learning_logs',
    'users',

    # 默认添加的应用程序
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

3.包含users的URL

修改项目根目录中的urls.py,使其包含为应用程序users定义的URL。

urlpatterns = [
    path("admin/", admin.site.urls),
    path('users/', include('users.urls')),
    path('', include('learning_logs.urls')),
]

4.登录页面

使用Django提供的默认视图login实现登录页面。

在目录learning_log/users/中,新建一个名为urls.py的文件,添加如下代码。

导入函数path和include,以便包含Django定义的一些默认的身份验证URL。这些默认的URL包含具名的URL模式,如’login’和’logout’。

将变量app_name设置成’users’,让Django能够将这些URL与其他应用程序的URL区分开来。即便是Django提供的默认URL,将其包含在应用程序users的文件中后,也可通过命名空间users进行访问。

登录页面的URL模式与URL http://loaclhost:8000/users/login/匹配。这个URL中的单词users让Django在users/urls.py中查找,而单词login让它将请求发送给Django的默认视图login。

"""为应用程序users定义URL模式。"""

from django.urls import path, include

from . import views

app_name = 'users'
urlpatterns = [
	# 包含默认的身份验证URL。
	path('', include('django.contrib.auth.urls')),
]
1.模板login.html

用户请求登录页面时,Django将使用一个默认的视图函数,但仍需要为这个页面提供模板。

默认的身份验证视图在文件夹registration中查找模板,因此创建这个文件夹。

在目录learning_log/users/中新建一个名为templates的目录,再在这个目录中新建一个名为registration的目录。

在文件夹registration中建立模板login.html,代码如下

这个模板继承了base.html,旨在确保登录页面的外观与网站的其他页面相同。

一个应用程序中的模板可继承另一个应用程序中的模板。

设置表单的errors属性,显示一条错误信息,指出输入的用户名密码对与数据库中存储的任何用户名密码对都不匹配。

input中包含了一个隐藏的表单元素’next’,其中的参数value告诉Django在用户成功登录后将其重定向到什么地方。

{% extends "learning_logs/base.html" %}

{% block content %}

	{% if form.errors %}
		<p>Your username and password didn't match. Please try again.</p>
	{% endif %}

	<form method="post" action="{% url 'users:login' %}">
		{% csrf_token %}
		{{ form.as_p }}

		<button name="submit">Log in</button>
		<input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
	</form>
	
{% endblock content %}
2.链接到登录页面

在base.html中添加到登录页面的链接,让所有页面都包含它。

用户已登录时,我们不想显示这个链接,因此将它嵌套在一个{% if %}标签中。

在Django身份验证系统中,每个模块都可使用变量user。这个变量有一个is_authenticated属性:如果用户已登录,该属性将为True,否则为False。

{% if user.is_authenticated %}
	Hello, {{ user.username }}.
{% else %}
	<a href="{% url 'users:register' %}">Register</a>
	<a href="{% url 'users:login' %}">Log in</a>
{% endif %}
3.使用登录页面

注销后,访问http://localhost:8000/users/login/进行登录

5.注销

1.在base.html中添加注销链接

默认的具名注销URL模式为’logout’。

{% if user.is_authenticated %}
	Hello, {{ user.username }}.
	<a href="{% url 'users:logout' %}">Log out</a>
{% else %}
	<a href="{% url 'users:register' %}">Register</a>
	<a href="{% url 'users:login' %}">Log in</a>
{% endif %}
2.注销确认页面

默认的注销视图使用模板logged_out.html渲染注销确认页面。

在registration目录中新建logged_out.html。

{% extends "learning_logs/base.html" %}

{% block content %}

	<p>Your have been logged out. Thank you for visiting!</p>
	
{% endblock content %}

6.注册页面

使用Django提供的表单UserCreationForm

1.注册页面的URL模式

在users/urls.py中添加以下代码

from . import views

# 注册页面
path('register/', views.register, name='register'),
2.视图函数register()

在users/views.py添加如下代码

导入函数login(),以便在用户正确填写了注册信息时让其自动登录,函数login传入对象request和new_user,为用户创建有效的会话,从而让其自动登录。

就本例而言,is_valid()检查有效是指用户名未包含非法字符,输入的两个密码相同,以及用户没有试图做恶意的事情。

如果提交的数据有效,就调用表单的方法save(),将用户名和密码的散列值保存到数据库中。

from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm

# Create your views here.
def register(request):
	"""注册新用户。"""
	if request.method != 'POST':
		# 显示空的注册表单。
		form = UserCreationForm()
	else:
		# 处理填写好的表单。
		form = UserCreationForm(data=request.POST)

		if form.is_valid():
			new_user = form.save()
			# 让用户自动登录,再重定向到主页。
			login(request, new_user)
			return redirect('learning_logs:index')
	# 显示空表单或指出表单无效。
	context = {'form': form}
	return render(request, 'registration/register.html', context)
3.注册模板

在login.html所在目录创建register.html。

这里使用了方法as_p,让Django在表单中正确地显示所有地字段,包括错误信息——如果用户没有正确地填写表单。

{% extends "learning_logs/base.html" %}

{% block content %}

	<form method="post" action="{% url 'users:register' %}">
		{% csrf_token %}
		{{ form.as_p }}

		<button name="submit">Register</button>
		<input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
	</form>
	
{% endblock content %}
4.链接到注册页面

在base.html添加代码,在用户没有登录时显示到注册页面的链接

这里的注册系统允许用户创建任意数量的账户。有些系统要求用户确认其身份:发送一封确认邮件,用户回复后账户才生效。通过这样做,这些系统会比本例的简单系统生成更少的垃圾账户。

<a href="{% url 'users:register' %}">Register</a>
用户名:Luffy
密码:Luffy12345

3.让用户拥有自己的数据

用户应该能够输入其专有的数据,创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。

1.使用@login_required限制访问

Django提供了装饰器@login_required,只允许已登录用户访问某些页面。装饰器是放在函数定义前面的指令,Python在函数允许前根据它来修改函数代码的行为。

1.限制访问显示所有主题的页面

修改learning_logs/views.py

导入函数login_required(),将login_required()作为修饰器应用于视图函数topics()——在它前面加上符号@和login_required,让Python在运行topics()的代码之前运行login_required()的代码。

login_required()的代码检查用户是否已登录,仅当用户已登录时,Django才运行topics()的代码。如果用户未登录,就重定向到登录页面。

from django.contrib.auth.decorators import login_required

@login_required
def topics(request):
	"""显示所有的主题。"""

为实现这种重定向,需要修改settings.py,让Django知道到哪里去查找登录页面。在settings.py末尾添加如下代码。

# 我的设置
LOGIN_URL = 'users:login'

现在如果未登录的用户请求装饰器@login_required保护的页面,Django重定向到settings.py中的LOGIN_URL指定的URL。

2.全面限制对项目“学习笔记”的访问

在learing_logs/views.py中,对除index()外的每个视图应用装饰器@login_required

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import Http404

from .models import Topic, Entry
from .forms import TopicForm, EntryForm
# Create your views here.
def index(request):
	"""学习笔记的主页。"""
	return render(request, 'learning_logs/index.html')


@login_required
def topics(request):
	"""显示所有的主题。"""

@login_required
def topic(request, topic_id):
	"""显示单个主题及其所有的条目。"""

@login_required
def new_topic(request):
	"""添加新主题。"""

@login_required
def new_entry(request, topic_id):
	"""在特定主题中添加新条目。"""
	
@login_required
def edit_entry(request, entry_id):
	"""编辑既有条目。"""

2.将数据关联到用户

需要将数据关联到提交它们的用户。只需将最高层的数据关联到用户,更底层的数据就会自动关联到用户。

修改模型Topic,在其中添加一个关联到用户的外键。这样做之后,必须对数据库进行迁移。最后,必须修改某些视图,使其只显示与当前登录的用户相关联的数据。

1.修改模型Topic

对models.py修改

导入django.contrib.auth中的模型user,然后在Topic中添加字段owner,它建立到模型user的外键关系。用户被删除时,所有与之相关联的主题也会被删除。

from django.contrib.auth.models import User

# Create your models here.
class Topic(models.Model):
	"""用户学习的主题"""
	text = models.CharField(max_length=200)
	date_added = models.DateTimeField(auto_now_add=True)
	owner =models.ForeignKey(User, on_delete=models.CASCADE)

	def __str__(self):
		"""返回模型的字符串表示。"""
		return self.text
2.确定当前有哪些用户

为执行迁移,Django需要知道将各个既有主题关联到哪个用户。

启动一个Django shell会话,查看已创建的所有用户ID。

(learning_log_env) S:\pythonProgram\learning_log>python manage.py shell
Python 3.11.5 | packaged by Anaconda, Inc. | (main, Sep 11 2023, 13:26:23) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: learningl_log_admin>, <User: Luffy>]>
>>> for user in User.objects.all():
...     print(user.username, user.id)
...
learningl_log_admin 1
Luffy 2
>>>
3.迁移数据库

迁移数据库时,Python将询问吗是要暂时将模型Topic关联到特定用户,还是在文件models.py中指定默认用户。选择第一项。

为将所有既有主题都关联到管理用户learningl_log_admin,输入用户ID值1.

(learning_log_env) S:\pythonProgram\learning_log>python manage.py makemigrations learning_logs
It is impossible to add a non-nullable field 'owner' to topic without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit and manually define a default value in models.py.
Select an option: 1
Please enter the default value as valid Python.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
>>> 1
Migrations for 'learning_logs':
  learning_logs\migrations\0003_topic_owner.py
    - Add field owner to topic

(learning_log_env) S:\pythonProgram\learning_log>
(learning_log_env) S:\pythonProgram\learning_log>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
  Applying learning_logs.0003_topic_owner... OK

(learning_log_env) S:\pythonProgram\learning_log>
(learning_log_env) S:\pythonProgram\learning_log>python manage.py shell
Python 3.11.5 | packaged by Anaconda, Inc. | (main, Sep 11 2023, 13:26:23) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from learning_logs.models import Topic
>>> for topic in Topic.objects.all():
...     print(topic, topic.owner)
...
Chess learningl_log_admin
Rock Climbing learningl_log_admin
sing learningl_log_admin
dance learningl_log_admin
>>>

如果你想要一个全新的数据库,可执行命令python manage.py flush,这将重建数据库的结构。如果这样做,就必须重新创建超级用户,且原来的所有数据都将丢失。

3.只允许用户访问自己的主题

在views.py中,修改函数topics()

用户登录后,request对象将有一个user属性,其中存储了有关该用户的信息。查询Topic.objects.filter(owner=request.user)让Django只从数据库中获取owner属性为当前用户的Topic对象。

def topics(request):
	"""显示所有的主题。"""
	topics = Topic.objects.filter(owner=request.user).order_by('date_added')
	context = {'topics': topics}
	return render(request, 'learning_logs/topics.html', context)

4.保护用户的主题

在视图函数topic获取请求的条目前执行检查

服务器上没有请求的资源时,标准的做法是返回404响应。

这里导入了异常Http404,并在用户请求其不应查看的主题时引发这个异常。

from django.http import Http404

@login_required
def topic(request, topic_id):
	"""显示单个主题及其所有的条目。"""
	topic = Topic.objects.get(id=topic_id)
	# 确认请求的主题属于当前用户。
	if topic.owner != request.user:
		raise Http404
	entries = topic.entry_set.order_by('-date_added')
	context = {'topic':topic, 'entries': entries}
	return render(request, 'learning_logs/topic.html', context)

5.保护页面edit_entry

@login_required
def edit_entry(request, entry_id):
	"""编辑既有条目。"""
	entry = Entry.objects.get(id=entry_id)
	topic = entry.topic
	if topic.owner != request.user:
		raise Http404

	if request.method != 'POST':
		# 初次请求:使用当前条目填充表单。
		form = EntryForm(instance=entry)
	else:
		# POST提交的数据:对数据进行处理。
		form = EntryForm(instance=entry, data=request.POST)
		if form.is_valid():
			form.save()
			return redirect('learning_logs:topic', topic_id=topic.id)

	context = {'entry':entry, 'topic': topic, 'form': form}
	return render(request, 'learning_logs/edit_entry.html', context)

6.将新主题关联到当前用户

@login_required
def new_topic(request):
	"""添加新主题。"""
	if request.method != 'POST':
		# 未提交数据:创建一个新表单。
		form = TopicForm()
	else:
		# POST提交的数据:对数据进行处理。
		form = TopicForm(data=request.POST)
		if form.is_valid():
			# form.save()
			new_topic = form.save(commit=False)
			new_topic.owner = request.user
			new_topic.save()
			return redirect('learning_logs:topics')

	# 显示空表单或指出表单数据无效。
	context = {'form': form}
	return render(request, 'learning_logs/new_topic.html', context)

7.保护页面new_entry

@login_required
def new_entry(request, topic_id):
	"""在特定主题中添加新条目。"""
	topic = Topic.objects.get(id=topic_id)
	# 确认请求的主题属于当前用户。
	if topic.owner != request.user:
		raise Http404

	if request.method != 'POST':
		# 未提交数据:创建一个空表单。
		form = EntryForm()
	else:
		# POST提交的数据:对数据进行处理。
		form = EntryForm(data=request.POST)
		if form.is_valid():
			new_entry = form.save(commit=False)
			new_entry.topic = topic
			new_entry.save()
			return redirect('learning_logs:topic', topic_id=topic_id)

	# 显示空表单或指出表单数据无效。
	context = {'topic': topic, 'form': form}
	return render(request, 'learning_logs/new_entry.html', context)

3.设置应用程序的样式并部署

Bootstrap库,一组工具,用于为Web应用程序设置样式。

把项目部署到Heroku,这个网站能够将项目推送到其服务器,让任何有互联网连接的人都可以使用它。

1.设置项目“学习笔记”的样式

1.应用程序django-bootstrap4

使用django-bootstrap4将Bootstrap集成到项目中。这个应用程序下载必要的Bootstrap文件,将其放到项目的合适位置,让你能够在项目的模板中使用样式设置指令。

安装django-bootstrap4

(learning_log_env) S:\pythonProgram\learning_log>pip install django-bootstrap4
Collecting django-bootstrap4
  Obtaining dependency information for django-bootstrap4 from https://files.pythonhosted.org/packages/54/a1/58a3aae4c35a2649f9abc172b34533844caf284e6942689edd017fa0c0d7/django_bootstrap4-23.2-py3-none-any.whl.metadata
  Downloading django_bootstrap4-23.2-py3-none-any.whl.metadata (3.8 kB)
Collecting beautifulsoup4>=4.8.0 (from django-bootstrap4)
  Downloading beautifulsoup4-4.12.2-py3-none-any.whl (142 kB)
     ---------------------------------------- 143.0/143.0 kB 134.8 kB/s eta 0:00:00
Requirement already satisfied: django>=3.2 in s:\pythonprogram\learning_log\learning_log_env\lib\site-packages (from django-bootstrap4) (4.2.7)
Collecting soupsieve>1.2 (from beautifulsoup4>=4.8.0->django-bootstrap4)
  Obtaining dependency information for soupsieve>1.2 from https://files.pythonhosted.org/packages/4c/f3/038b302fdfbe3be7da016777069f26ceefe11a681055ea1f7817546508e3/soupsieve-2.5-py3-none-any.whl.metadata
  Downloading soupsieve-2.5-py3-none-any.whl.metadata (4.7 kB)
Requirement already satisfied: asgiref<4,>=3.6.0 in s:\pythonprogram\learning_log\learning_log_env\lib\site-packages (from django>=3.2->django-bootstrap4) (3.7.2)
Requirement already satisfied: sqlparse>=0.3.1 in s:\pythonprogram\learning_log\learning_log_env\lib\site-packages (from django>=3.2->django-bootstrap4) (0.4.4)
Requirement already satisfied: tzdata in s:\pythonprogram\learning_log\learning_log_env\lib\site-packages (from django>=3.2->django-bootstrap4) (2023.3)
Downloading django_bootstrap4-23.2-py3-none-any.whl (24 kB)
Downloading soupsieve-2.5-py3-none-any.whl (36 kB)
Installing collected packages: soupsieve, beautifulsoup4, django-bootstrap4
Successfully installed beautifulsoup4-4.12.2 django-bootstrap4-23.2 soupsieve-2.5

[notice] A new release of pip is available: 23.2.1 -> 23.3.1
[notice] To update, run: python.exe -m pip install --upgrade pip

(learning_log_env) S:\pythonProgram\learning_log>

在settings的INSTALLED_APPS添加如下代码

新建一个名为“第三方应用程序”的片段,用于指定其他开发人员开发的应用程序,并在其中添加’bootstrap4’。务必将这个片段放在“我的应用程序”和“Django默认添加的应用程序”之间。

INSTALLED_APPS = [
    # 我的应用程序
    'learning_logs',
    'users',

    # 第三方应用程序
    'bootstrap4',
    
    # 默认添加的应用程序
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

2.使用Bootstrap设置项目“学习笔记”的样式

Bootstrap是一个大型样式设置工具集,还提供了大量模板,可应用于项目以创建独特的总体风格。

官网:Bootstrap · The most popular HTML, CSS, and JS library in the world. (getbootstrap.com)

访问官网,单击Examples并找到Navbars。使用模板Navbars static,它提供了简单的顶部导航栏以及用于放置页面内容的容器。

3.修改base.html

使用上述模板修改base.html

1.定义HTML头部

删除base.html的全部代码,输入下面的代码

首先,加载django-bootstrap4中的模板标签集

接着,将这个文件声明为使用英语编写的HTML文档。HTML文件分为两个主要的部分:头部和主体。

HTML文件头部不包含任何内容,只是向浏览器提供正确显示页面所需的信息。

title元素,在浏览器打开网站页面时,浏览器的标题栏将显示该元素的内容。

接着,使用django-bootstrap4的一个自定义模板标签,让Django包含所有的Bootstrap样式文件。

{% load bootstrap4 %}

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<title>Learning Log</title>

	{% bootstrap_css %}
	{% bootstrap_javascript jquery='full' %}

</head>
2.定义导航栏

定义页面顶部导航栏的代码需要同时支持较窄的手机屏幕和较宽的台式计算机显示器。

HTML文件的主体包含用户将在页面上看到的内容。

nav表示页面的导航链接部分。对于这个元素内的所有内容,都将根据此处的navbar和navbar-expand-md等选择器定义的Bootstrap样式规则来设置样式。选择器决定了样式规则将应用于页面上的哪些元素。选择器navbar-light和bg-light使用一种浅色主题来设置导航栏的颜色。mb-4中的mb表示下边距,这个选择器确保导航栏和页面其他部分之间有一些空白区域。选择器border在浅色背景周围添加很细的边框,将导航栏与页面其他部分分开。

接着,指定在导航栏最左端显示项目名,并将其设置为到主页的链接。选择器navbar-brand设置这个链接的样式,使其比其他链接更显眼。

然后定义了一个按钮,它将在浏览器窗口太窄、无法水平显示整个导航栏时显示出来。如果用户单击这个按钮,将出现一个下拉列表,其中包含所有的导航元素。在用户缩小浏览器窗口或在屏幕较小时的移动设备上显示网站时,collapse会导致导航栏折叠起来。

<body>

	<nav class="navbar navbar-expand-md navbar-light bg-light mb-4 border">
		
		<a class="navbar-brand" href="{% url 'learning_logs:index'%}">Learning Log</a>

		<button class="navbar-toggler" type="button" data-toggle="collapse"
				data-target="#navbarCollapse" aria-controls="navbarCollapse"
				aria-expanded="false" aria-label="Toggle navigation">
			<span class="navbar-toggler-icon"></span></button>

div标签指定了屏幕或窗口太窄时将折叠起来的导航栏部分的起始位置。

接着定义了一组链接。Bootstrap将导航元素定义为无序列表项。导航栏中的每个链接或元素都以列表项的方式定义。

<div class="collapse navbar-collapse" id="navbarCollapse">
			<ul class="navbar-nav mr-auto">
				<li class="nav-item">
					<a class="nav-link" href="{% url 'learning_logs:topics'%}">Topics</a>
				</li>
			</ul>

选择器ml-auto表示自动左边距,它根据导航栏包含的其他元素设置左边距,确保这组链接位于屏幕右边。

<ul class="navbar-nav ml-auto">
				{% if user.is_authenticated %}
					<li class="nav-item">
						<span class="navbar-text">Hello, {{ user.username }}.</span>
					</li>
					<li class="nav-item">
						<a class="nav-link" href="{% url 'users:logout' %}">Log out</a>
					</li>
				{% else %}
					<li class="nav-item">
						<a class="nav-link" href="{% url 'users:register' %}">Register</a>
					</li>
					<li class="nav-item">
						<a class="nav-link" href="{% url 'users:login' %}">Log in</a>
					</li>
				{% endif %}
			</ul>
		</div>

	</nav>
3.定义页面的主要部分

main元素用于定义页面主体的最重要部分。选择器container对页面元素进行编组的简单方式。

在大多数页面中使用page_header来指定标题。为突出标题,设置内边距。

内边距指的是元素内容和边框之间的距离。选择器pb-2将元素的下内边距设置为适度的值。

外边距指的是元素边框与其他元素之间的距离。

选择器border-bottom在page_header块的下面添加较细的边框。

<main role="main" class="container">
		<div class="pb-2 mb-2 border-bottom">
			{% block page_header %}{% endblock page_header %}
		</div>
		<div>
			{% block content %}{% endblock content %}
		</div>
	</main>

</body>
</html>

4.使用jumbotron设置主页的样式

使用Bootstrap元素jumbotron来修改主页index.html。

jumbotron元素是一个大框,在页面中显得鹤立鸡群。可以包含任何东西,通常用于在主页中呈现简要的项目描述和让用户行动起来的元素。

选择器display-3让标题显得更在更高。

代码 & raquo;是一个HTML实体,表示两个右尖括号(»)

{% extends "learning_logs/base.html" %}
{% block page_header %}
	<div class="jumbotron">
		<h1 class="display-3">Track your learning.</h1>

		<p class="lead">Make your own Learning Log, and keep a list of the
			topics you're learning about. Whenever you learn something new
			about a topic, make an entry summarizing what you've learned.</p>

		<a class="btn btn-lg btn-primary" href="{% url 'users:register' %}" role="button">Register &raquo;</a>
	</div>
{% endblock page_header %}

5.设置登录页面的样式

修改login.html。

定义page_header块,指出这个页面是做什么用的。

从模板中删除了代码块{% if form.errors %},因为django-bootstrap4会自动管理表单错误。

模板标签{% bootstrap_form form %}替换{{form.as_p}},模板标签{% bootstrap_form form %}将Bootstrap样式规则应用于各个表单元素。

bootstrap4起始模板标签{% buttons %},将Bootstrap样式应用于按钮。

{% extends "learning_logs/base.html" %}
{% load bootstrap4 %}

{% block page_header %}
	<h2>Log in to your account.</h2>
{% endblock page_header %}

{% block content %}

	<form method="post" action="{% url 'users:login' %}" class="form">
		{% csrf_token %}
		{% bootstrap_form form %}
		{% buttons %}
			<button name="submit" class="btn btn-primary">Log in</button>
		{% endbuttons %}

		<input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
	</form>
	
{% endblock content %}

6.设置显示所有主题的页面的样式

修改topics.html

{% extends "learning_logs/base.html" %}

{% block page_header %}
	<h1>Topics</h1>
{% endblock page_header %}

{% block content %}

	<ul>
		{% for topic in topics %}
			<li><h3>
				<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
			</h3></li>
		{% empty %}
			<li><h3>No topics have been added yet.</h3></li>
		{% endfor %}
	</ul>

	<h3><a href="{% url 'learning_logs:new_topic' %}">Add a new topic</a></h3>

{% endblock content %}

7.设置显示单个主题的页面中的条目样式

使用Bootstrap的卡片(card)组件来突出每个条目。卡片是带灵活的预定义样式的div。

标签small时其中内容比旁边内容小一点

{% extends "learning_logs/base.html" %}

{% block page_header %}
	<h3>{{ topic }}</h3>
{% endblock page_header %}

{% block content %}

	<p>
		<a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a>
	</p>
	{% for entry in entries %}
		<div class="card mb-3">
			<h4 class="card-header">
				{{ entry.date_added|date:'M d, Y H:i' }}
				<small>
					<a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a>
				</small>
			</h4>
			<div class="card-body">
				{{ entry.text|linebreaks }}
			</div>
		</div>
		{% empty %}
			<p>There are no entries for this topic yet.</p>
		{% endfor %}
	
{% endblock content %}

8.修改new_topic页面、new_entry页面、edit_entry页面和注册页面

{% extends "learning_logs/base.html" %}
{% load bootstrap4 %}

{% block page_header %}
	<h3>Add a new topic:</h3>
{% endblock page_header %}

{% block content %}

	<form action="{% url 'learning_logs:new_topic' %}" method="post">
		{% csrf_token %}
		{% bootstrap_form form %}
		{% buttons %}
			<button name="submit">Add topic</button>
		{% endbuttons %}
		
	</form>
	
{% endblock content %}
{% extends "learning_logs/base.html" %}
{% load bootstrap4 %}

{% block page_header %}
	<h3><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></h3>
{% endblock page_header %}

{% block content %}
	<h4>Add a new entry:</h4>
	<form action="{% url 'learning_logs:new_entry' topic.id %}" method="post">
		{% csrf_token %}
		{% bootstrap_form form %}
		{% buttons %}
			<button name="submit">Add entry</button>
		{% endbuttons %}
		
	</form>
	
{% endblock content %}
{% extends "learning_logs/base.html" %}
{% load bootstrap4 %}

{% block page_header %}
	<h3><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></h3>
{% endblock page_header %}

{% block content %}

	<h4>Edit entry:</h4>

	<form action="{% url 'learning_logs:edit_entry' entry.id %}" method="post">
		{% csrf_token %}
		{% bootstrap_form form %}
		{% buttons %}
			<button name="submit">Save changes</button>
		{% endbuttons %}
		
	</form>
	
{% endblock content %}
{% extends "learning_logs/base.html" %}
{% load bootstrap4 %}

{% block content %}

	<form method="post" action="{% url 'users:register' %}">
		{% csrf_token %}
		{% bootstrap_form form %}
		{% buttons %}
			<button name="submit">Register</button>
			<input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
		{% endbuttons %}
	</form>
	
{% endblock content %}

2.部署学习笔记

Heroku是一个基于Web的平台,供我们管理Web应用程序的部署。

1.建立Heroku账户

Heroku提供的免费试用服务能够将项目部署到服务器并对其进行测试。Heroku提供的免费试用服务存在一些限制,如可部署的应用程序数量以及用户访问应用程序的频率。但这些限制都很宽松,能够在不支付任何费用的情况下练习部署应用程序。

2.安装Heroku CLI

访问Heroku Dev Center网站的The Heroku CLI页面,根据操纵系统安装Heroku CLI。

3.安装必要的包

安装三个包,以便在服务器上支持Django项目提供的服务。

为管理Heroku使用的数据库,psycopg2包必不可少。

django-heroku包用于管理应用程序的各种配置,使其能够在Heroku服务器上正确地运行。包括管理数据库,以及静态文件存储到合适的地方,以便妥善地提供它们。静态文件包括样式规则和Javascript文件。

gunicorn包让服务器能够实时地支持应用程序。

(learning_log_env) S:\pythonProgram\learning_log>pip install psycopg==2.7.*
(learning_log_env) S:\pythonProgram\learning_log>pip install django-heroku
(learning_log_env) S:\pythonProgram\learning_log>pip install gunicorn

4.创建文件requirements.txt

Heroku需要知道项目依赖于哪些包。

命令freeze让pip将项目中当前安装的所有包的名称都写入文件requirements.txt。

(learning_log_env) S:\pythonProgram\learning_log>pip freeze > requirements.txt

5.指定Python版本

如果没有指定Python版本,Heroku将使用其当前的Python默认版本。下面确保Heroku使用我们使用的

(learning_log_env) S:\pythonProgram\learning_log>python --version
Python 3.11.5

(learning_log_env) S:\pythonProgram\learning_log>

在manage.py所在的文件夹中新建一个名为runtime.txt的文件,输入下面内容。

确保输入小写的python,在它后面输入一个连字符,再输入由三部分组成的版本号。

如果出现错误信息,指出不能使用指定的Python版本,访问Heroku Dev Center 网站的Language Support页面,再单击到Specifying a Python Runtime的链接。浏览打开的文章,了解支持的Python版本,并使用与你使用的Python版本最接近的版本。

python-3.11.5

6.为部署到Heroku而修改settings.py

在settings.py末尾添加一个片段,指定一些Heroku环境设置。

这里导入了模块django_heroku并调用了函数settings()。这个函数将一些设置修改为Heroku环境要求的值。

# Heroku设置
import django_heroku
django_heroku.settings(locals())

7.创建启动进程的Procfile

Profile告诉Heroku应该启动哪些进程,以便正确地提供项目需要的服务。

在manage.py所在的目录中创建Profile文件,不指定文件扩展名。

Heroku将Gunicorn用作服务器,并使用learning_log/wsgi.py中的设置来启动应用程序。标识log-file告诉Heroku应将哪些类型的事情写入日志。

web: gunicorn learning_log.wsgi--log-file -

8.使用Git跟踪项目文件

Git是一个版本控制程序,让你能够在每次成功实现新功能后都拍摄项目代码的快照。无论出现什么问题(如实现新功能时不小心引入了bug),都可轻松地恢复到最后一个可行地快照。每个快照都称为提交。使用Git意味着在尝试实现新功能时无须担心破坏项目。将项目部署到服务器时,需要确保部署地是可行版本。

1.安装Git

要确定是否安装了Git,打开一个新的终端,执行如下命令。

(learning_log_env) S:\pythonProgram\learning_log>git --version
git version 2.42.0.windows.2

(learning_log_env) S:\pythonProgram\learning_log>
2.配置Git

Git跟踪是谁修改了项目,即便项目由一个人开发亦如此。为进行跟踪,Git需要知道你地用户名和电子邮箱。因此,必须提供用户名,但对于练习项目,可以编造一个电子邮箱。

(learning_log_env) S:\pythonProgram\learning_log>git config --global user.name "zxc"

(learning_log_env) S:\pythonProgram\learning_log>git config --global user.email "zxc@example.com"
3.忽略文件

无须让Git跟踪项目中地每个文件,因此让它忽略一些文件。在manage.py所在地文件夹中创建一个名为.gitignore的文件(这个文件名以句点打头,且不包含扩展名),输入如下代码。

Git忽略目录ll_env,因为随时可自动重新创建它。还指定不跟踪目录_pycache__,这个目录包含Django运行.py文件时自动创建的.pyc文件。没有跟踪对本地数据库的修改,因为这是个坏习惯:如果在服务器上使用的是SQLite,将项目推送到服务器时,可能会不小心用本地测试库覆盖在线数据库。*.sqlite3让Git忽略所有扩展名为.sqlite3的文件。

ll_env/
__pycache__/
*.sqlite3
4.显示隐藏的文件

大多数操纵系统都会隐藏名称以句点打头的文件和文件夹,如.gitignore,默认看不到隐藏的文件。

不过作为程序员,你需要看到它们。

打开资源管理器,再打开一个文件夹。单击标签View(查看),并确保选中了复选框File name extensions(文件扩展名)和Hidden items(隐藏的项目)。

5.提交项目

初始化一个Git仓库,将所有必要的文件都加入该仓库,并提交项目的初始状态。

执行命令git init,在“学习笔记”所在的目录中初始化一个空仓库。

执行命令git add .(千万别忘了这个句点),将未被忽略的文件都加入这个仓库。

执行git commit -am commit message,其中的标志-a让Git在这个提交中包含所有修改过的文件,而标志-m让Git记录一条日志信息。

执行命令git status,输出表明当前位于分支master,而工作树是干净的。每当要将项目推送到Heroku是,我们都希望看到这样的状态。

(learning_log_env) S:\pythonProgram\learning_log>git init
Initialized empty Git repository in S:/pythonProgram/learning_log/.git/
(learning_log_env) S:\pythonProgram\learning_log>git add .
(learning_log_env) S:\pythonProgram\learning_log>git commit -am "Ready for deployment to  heroku."
(learning_log_env) S:\pythonProgram\learning_log>git status
On branch master
nothing to commit, working tree clean

(learning_log_env) S:\pythonProgram\learning_log>

9.推送到Heroku

执行命令heroku login,打开浏览器并在其中显示一个页面,登录Heroku。

执行命令heroku create,让Heroku创建一个空项目。Heroku生成的项目名由两个单词和一串数字组成,但以后可修改这个名称。

执行命令git push heroku master,让Git将项目的分支master推送到Heroku刚才创建的仓库中。Heroku将使用这些文件在其服务器上创建项目。执行后列出了用于访问这个项目的URL,但这个URL和项目名都是可以修改的。

这些上述命令后,项目就部署好了,但未做全面配置。为核实正确地启动了服务器进程,执行命令heroku ps,输出指出了在接下来的一个月内,项目还可在多长时间内处于活动状态,以及启动了Profile指定的进程。

使用命令heroku open在浏览器中打开这个应用程序,将看到“学习笔记”的主页,其样式设置正确无误,但还无法使用这个应用程序,因为尚未建立数据库。

10.在Heroku上建立数据库

为建立在线数据库,需要再次执行命令migrate,并应用在开发期间生成的所有迁移。要对Heroku项目执行Django和Python命令,可使用命令Heroku run。

执行命令heroku run python manage.py migrate。Heroku随后创建一个终端会话来执行命令migrate。Django应用默认迁移以及在开发“学习笔记”期间生成的迁移。

现在如果访问这个部署的应用程序,将能够像在本地系统上一样使用它,但看不到在本地部署中输入的任何数据(包括超级用户账户),因为它们还没有被复制到在线服务器。通常,不将本地数据复制到在线部署中,因为本地数据通常是测试数据。

11.改进Heroku部署

1.在Heroku上创建超级用户

在链接到Heroku服务器的情况下,使用命令heroku run bash 打开Bash终端会话。Bash是众多Linux终端运行的语言。

注意:即使使用的是Windows系统,也应使用这里列出的命令,这里是通过远程连接运行的Linux终端。

执行命令ls,以查看服务器上有哪些文件和目录。服务器包含的文件和目录应与本地系统相同。可像遍历其他文件系统一样遍历这个文件系统。

执行命令python manage.py createsuperuser,创建超级用户。

执行命令exit返回到本地系统的终端会话。

2.在Heroku上创建对用户友好的URL

使用命令heroku apps:rename learning-log,重命名应用程序。给应用程序命名时,可使用字母、数字和连字符,并且想怎么命名都可以,只要指定的名称未被别人使用就行。

12.确保项目的安全

当前,部署的项目存在严重的安全问题:settings.py包含设置DEBUG=True,指定在发送错误时显示调试信息。开发项目时,Django的错误页面显示了重要的调试信息,如果将项目部署到服务器后还保留这个设置,将给攻击者提供大量可利用的信息。

在在线项目中,设置一个环境变量,以控制是否显示调试信息。环境变量是在特定环境中设置的值。这是在服务器上存储敏感信息并将其与项目代码分开的方式之一。

修改settings.py,在末尾加入如下代码,使其在项目于Heroku上运行时检查一个环节变量。

方法os.environ.get()从项目当前所处的环境中读取与特定环境变量相关联的值。如果设置了这个环境变量,就返回它的值;如果没有设置,就返回None。使用环境变量来存储布尔值时,必须小心应对,因为在大多数情况下,环境变量存储的都是字符串。字符串‘False’对应的布尔值为True,因为非空字符串对应的布尔值都为True。Django读取Heroku中键位’DEBUG’的环境变量时,如果其值为’TRUE’,就将DEBUG设置为True;如果其值为’FALSE’,就将DEBUG设置为False。

if os.environ.get('DEBUG') == 'TRUE':
    DEBUG = True
elif os.environ.get('DEBUG') == 'FALSE':
    DEBUG = False

13.提交并推送修改

对settings.py所做的修改提交到Git仓库,再将修改推送到Heroku。

执行命令git commit,并指定一条简短而有描述性的提交信息。标志-am让Git提交所有修改过的文件,并记录一条日志信息。Git找出唯一修改过的文件,并将所做的修改提交到仓库,如执行命令git commit -am “Set DEBUG based on environment variables.”。

执行命令git status,查看状态。如果显示的状态表明,当前位于仓库的分支master,没有任何未提交的修改,则推送到Heroku。推送到Heroku前,必须检查状态并查看刚才所说的信息。如果没有看到这样的信息,就说明有未提交的修改,而这些修改将不会推送到服务器。在这种情况下,可尝试再次执行命令commit。

执行命令git push heroku master将修改后的仓库推送到Heroku。Heroku发现仓库发生了变化,因此重建项目,确保所有的修改都生效。它不会重建数据库,因此这次无须执行命令migrate。

14.在Heroku上设置环境变量

执行命令heroku config:set DEBUG=FALSE,命令heroku config:set设置一个环境变量。每当你在Heroku上设置环境变量时,Heroku都将重启项目,让环境变量生效。

如果你在部署应用程序时遇到麻烦,需要排除故障,可执行命令heroku config:set DEBUG=‘TRUE’,以便访问在线项目时能够看到完整的错误报告。成功地排除故障后,务必将这个环境变量重置为’FALSE’。另外,请务必小心,一旦有用户经常访问这个在线项目,就不要这样做。

15.创建自定义错误页面

404错误通常意味着Django代码是正确的,但请求的对象不存在。500错误通常意味着代码有问题,如views.py中的函数有问题。

1.创建自定义模板

在最外层的文件夹learning_log中,新建一个文件夹,并将其命名为templates。然后在这个文件夹中新建一个名为404.html的文件,内容如下:

这个简单的模板指定了通用的404错误页面包含的信息,但该页面的外观与网站其他部分一致。

{% extends "learning_logs/base.html" %}

{% block page_header %}
	<h2>The item you requested is not available. (404)</h2>
{% endblock page_header %}

再创建一个名为500.html的文件,内容如下:

{% extends "learning_logs/base.html" %}

{% block page_header %}
	<h2>There has been an internal error. (500)</h2>
{% endblock page_header %}

对settings.py做细微的修改,让Django在根目录中查找错误页面模板。

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [os.path.join(BASE_DIR, 'templates')],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]
2.在本地查看错误页面

将项目推送到Heroku前,如果要在本地查看错误页面是什么样的,首先需要在本地设置中设置Debug=False,以禁止显示默认的Django调试页面。对settings.py做如此修改(请确保修改的是settings.py中用于本地环境的部分,而不是用于Heroku的部分):

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

现在请求不属于你的主题或条目,以查看404错误页面。然后请求不存在的主题或条目,以查看500错误页面。查看错误页面后,将本地DEBUG的值重新设置为True,为后续发展提供方便。(在管理Heroku设置的部分,确保处理DEBUG的方式不变。)

注意:500错误页面不会显示任何有关当前用户的信息,因为发送服务器错误时,Django不会通过响应发送任何上下文信息。

3.将修改推送到Heroku

执行命令git add .,因为在项目中创建了一些新文件,需要让Git跟踪它们。

执行命令git commit -am “Added custom 404 and 500 error pages.”,提交所做的修改。

执行命令git push heroku master,将修改后的项目推送到Heroku。

4.使用方法get_object_or_404()

如果用户手工请求不存在的主题或条目,将导致500错误。Django尝试渲染不存在的页面,但没有足够的信息来完成这项任务,进而引发了500错误。对于这种情形,将其视为404错误更合适。为此可使用Django快捷函数get_object_or_404()。这个函数尝试从数据库获取请求的对象,如果这个对象不存在,就引发404异常。在view.py中导入这个函数,并用其替换函数get():

from django.shortcuts import render, redirect, get_object_or_404

@login_required
def topic(request, topic_id):
	"""显示单个主题及其所有的条目。"""
	# topic = Topic.objects.get(id=topic_id)
	topic = get_object_or_404(Topic, id=topic_id)
	# 确认请求的主题属于当前用户。
	if topic.owner != request.user:
		raise Http404
	entries = topic.entry_set.order_by('-date_added')
	context = {'topic':topic, 'entries': entries}
	return render(request, 'learning_logs/topic.html', context)

@login_required
def new_entry(request, topic_id):
	"""在特定主题中添加新条目。"""
	# topic = Topic.objects.get(id=topic_id)
	topic = get_object_or_404(Topic, id=topic_id)
	# 确认请求的主题属于当前用户。
	if topic.owner != request.user:
		raise Http404

	if request.method != 'POST':
		# 未提交数据:创建一个空表单。
		form = EntryForm()
	else:
		# POST提交的数据:对数据进行处理。
		form = EntryForm(data=request.POST)
		if form.is_valid():
			new_entry = form.save(commit=False)
			new_entry.topic = topic
			new_entry.save()
			return redirect('learning_logs:topic', topic_id=topic_id)

	# 显示空表单或指出表单数据无效。
	context = {'topic': topic, 'form': form}
	return render(request, 'learning_logs/new_entry.html', context)


@login_required
def edit_entry(request, entry_id):
	"""编辑既有条目。"""
	# entry = Entry.objects.get(id=entry_id)
	entry = get_object_or_404(Entry, id=entry_id)
	topic = entry.topic
	if topic.owner != request.user:
		raise Http404

	if request.method != 'POST':
		# 初次请求:使用当前条目填充表单。
		form = EntryForm(instance=entry)
	else:
		# POST提交的数据:对数据进行处理。
		form = EntryForm(instance=entry, data=request.POST)
		if form.is_valid():
			form.save()
			return redirect('learning_logs:topic', topic_id=topic.id)

	context = {'entry':entry, 'topic': topic, 'form': form}
	return render(request, 'learning_logs/edit_entry.html', context)

为部署这里所做的修改,再次提交,并将项目推送到Heroku。

16.继续开发

更新项目的过程:

首先,对本地项目做必要的修改。如果在修改过程中创建了新文件,使用命令git add .(千万别忘记末尾的句点)将其加入Git仓库中。如果有修改要求迁移数据库,也需要执行这个命令,因为每个迁移都将生成新的迁移文件。

然后,使用命令git commit -am “commit message” 将修改提交到仓库,再使用命令git push heroku master将修改推送到Heroku。如果在本地迁移了数据库,也需要迁移在线数据库。为此,可使用一次性命令 heroku run python manage.py migrate,也可使用 heroku run bash打开远程终端会话,并在其中执行命令 python manage.py migrate。然后访问在线项目,确认期望看到的修改已生效。

17.设置SECRET_KEY

Django根据settings.py中设置SECRET_KEY的值来实现大量的安全协议。在这个项目中,提交到仓库的设置文件包含设置SECRET_KEY。

18.将项目从Heroku删除

在Heroku网站登录后,将重定向到一个页面,其中列出了你托管的所有项目。单击要删除的项目,你将看到另一个页面,其中显示了有关这个项目的信息。单击链接 Settings,再向下滚动,找到用于删除项目的链接并单击它。这种操作是不可撤销的,因此Heroku让你手工输入要删除的项目的名称,确认你确实要删除它。

如果喜欢在终端中工作,也可使用命令destroy 来删除项目:heroku apps:destroy–app appname

appname是要删除的项目的名称,可能类似于secret-lowlands-82594,也可能类似于learning-log(如果你重命名了项目)。你将被要求再次输入项目名,确认你确实要删除它。

注意:删除Heroku上的项目对本地项目没有任何影响。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值