Web App
A practice of Chapters 18-20 of Python Crash Course.
Virtual Environments and Packages
虚拟环境是系统的一个位置,可在其中安装包,并将之与其他 Python 包隔离。
通过执行命令行 python -m venv <env_name>
初始化虚拟环境
关于 python command options
可用的 python
命令行操作可以参考官方文档。-m
指的是将某个模块作为脚本运行
激活虚拟环境根据 终端Terminal
不同而发生变化。如果是类 bash
的终端(包括 windows
下的 git bash
),执行source ./<env_name>/Scripts|bin/activate
即可。如果是 cmd
powershell
等 windows命令行终端
,则需要执行 <env_name>/Scripts/activate
激活
停止使用虚拟环境,在激活状态下直接执行 deactivate
即可
Create Project
首先,使用 pip
安装 python
包和模块。pip
是安装和管理 Python
包和模块的一个工具。
pip 如何管理依赖?
与一般的项目不同,没有使用 package.json
,或者cargo.toml
等格式化文件记录依赖。一般通过 pip freeze > requirements.txt
记录当前项目的依赖
当使用 git
管理项目的时候,不要 提交 虚拟环境
相关的代码
pip
的常用命令有:pip install <package>
; pip list
。这里使用 pip install django
来安装 Django
模块
然后,使用 django-admin
命令初始化项目
以learning_log
项目为例:django-admin startproject learning_log .
关于django-admin
- django-admin is Django's command-line utility for administrative tasks
- django-admin startproject name [directory] Creates a Django project directory structure for the given project name in the current directory or the given destination
- if only name specified (without '.' to denote the directory), django will create a folder<name> and use that folder as the root folder of the project and create another folder<name> inside as the project package
命令执行完毕后在当前文件夹下目录如下所示:
manage.py
learning_log/
__init__.py
settings.py
asgi.py
wsgi.py
urls.py
settings.py
指定Django
如何与系统交互以及如何管理项目urls.py
告诉Django
,应创建哪些页面来响应浏览器请求wsgi.py
帮助Django
提供它创建的文件,这个文件名是 Web 服务器网关接口(Web server gateway interface)的首字母缩写
接着可以执行 python manage.py migrate
迁移数据库
还可以通过 python manage.py runserver
用来核实 Django
正确创建了项目
Create App
Django 项目由一系列应用程序组成,它们协同工作让项目成为一个整体。简单来说,一个 项目project
包含了若干个 应用application
通过执行 python manage.py startapp <app_name>
创建一个应用
Define Models
根据官方定义,
模型Model
就是能够描述数据data
的 唯一(single)准确(definitive) 的信息源。它包括了存储数据的关键字段和行为。
INFO
一般来说,每个模型都映射了一张数据库的表。在代码层面,模型就是一个类
对于 模型Model
来讲,最重要的部分————也是唯一要求必须存在的部分就是 字段Fields
。每个 field
都应该是对应 Field
的一个实例。 所有可用的 Field types
可以参考 model-filed-types
class Topic(models.Model):
"""用户学习的主题"""
# CharField - https://docs.djangoproject.com/en/4.2/ref/models/fields/#charfield
text = models.CharField(max_length=200)
# DateTimeField - https://docs.djangoproject.com/en/4.2/ref/models/fields/#datetimefield
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""返回模型的字符串表示"""
return self.text
Activate Models
使用模型需要让 Django
将 应用程序(App) 包含到 项目(Project) 中。为此,打开 项目(Project) 中的 settings.py
,修改如下字段
--snip--
INSTALLED_APPS = [
# 我的应用程序
'learning_logs',
# 默认添加的应用程序
'django.contrib.admin',
--snip--
]
--snip--
与此同时需要修改数据库,使其能够存储与 Model Topic
相关的信息。
- 执行
python manage.py makemigrations learning_logs
。该命令在learning_logs/migrations
文件夹下面创建了一个名为0001_initial.py
的迁移文件,这个文件将在数据库中为Model Topic
创建一个表 - 随后应用修改
python manage.py migrate
TIP
每当修改 Models
的时候,都需要进行如上两个步骤
Admin Site
Django
提供了 管理网站(admin site) 可以轻松处理模型。
- 通过执行命令
python manage.py createsuperuser
创建超级用户 - 通过在
learning_logs/admin.py
中注册模型,实现管理网站的模型注册
# admin.py
from django.contrib import admin
from .models import Topic
admin.site.register(Topic)
在完成超级用户的创建以及管理网站的注册后,既可以通过访问 runserver
启动的服务拼接 /admin
进入并登录管理网站。如 localhost:8080/admin
Define Model Entry
当我们想要在模型之间构建关联关系的时候,可以通过设置 ForeignKey
来关联,并通过在模型中定义 Meta
类设置模型相关信息
关于 Class Meta
Model metadata is “anything that’s not a field”, such as ordering options (ordering
), database table name (db_table
), or human-readable singular and plural names (verbose_name
and verbose_name_plural
). None are required, and adding class Meta
to a model is completely optional.
关于外键 foreign key
外键(foreign key)是一个数据库术语,它指向数据库中的另一条记录,这里是将每个条目关联到特定主题。创建每个主题时,都分配了一个键(ID)。需要在两项数据之间建立联系时,Django 使用与每项信息相关联的键。
实参 on_delete=models.CASCADE 让 Django 在删除主题的同时删除所有与之相关联的条目,这称为级联删除(cascading delete)。
比如我们想要构建上文提到的 Topic
主题相关的模型 Entry
,定义如下
class Entry(models.Model):
"""学到的具体知识"""
# https://docs.djangoproject.com/en/4.2/ref/models/fields/#foreignkey
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):
return f"{self.text[:50]}..."
Create Pages
对于网页应用来说,分为数据层和视图层两个维度。因此当我们想要构建一个网页应用的时候需要考虑到:数据、视图、交互三个层面。也就是 WEB 开发中常说的 MV*
开发模式。
而使用 Django
创建页面的过程分为三个部分:
- 构建 url 映射
- 编写视图 (models / views)
- 编写模板 (template)
关于 url 映射
可以直接在项目中的 /project/urls.py
文件内定义,也可以在对应 app 的 /app/urls.py
中定义后,在/project/urls.py
中通过 include
函数等引用其他 app
中的 urls
,如下所示:
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('learning_logs.urls'))
]
虽然其顺序无关紧要,但个人推荐的顺序是 视图 -> 模板 -> url。视图可能会涉及 model
以及 context
注入,因此这个顺序可能造成的依赖影响较小。
在定义 views 的时候可以通过 class
或 function
的方式进行定义,总体上来说没有显著区别。
# function render
from django.shortcuts import render
def index(request):
return render(request, {html_path})
# class render
from django.views.generic import TemplateView
class HomepageView(TemplateView):
template_name = "helloworld/index.html"
可以通过引入模型的方式,将模型通过 context
的方式注入到视图内
from .models import Topic
def topics(request):
"""显示所有主题"""
topics = Topic.objects.order_by("date_added")
context = {"topics": topics}
# context 注入,template 内即可使用 topics 变量
return render(request, "learning_logs/topics.html", context)
Templates Syntax
可以使用模板继承于法对其他模板进行继承,但并非是重点。这部分可以让前端工程师进行专门开发,以定制化更具交互性和观赏性的网站。
Django
可以通过内容注入的方式,使 template
能够获取读取 context
的能力。主要由三大类语法组成:
- Viriables 变量
0
- Tags 标签
{% csrf_token %}
{% url %}
{% for %}
等 - Comments
{# 单行注释 #}
{% comment 多行注释 %}
例如:
{% url 'learning_logs:index' %}
可以生成 learning_logs/urls
中名为 index
的模式相匹配的 URL
{% block {name} %}``{% endblock {name} %}
可以规定占位符,具体内容可由子模板确定。 {% extend "{html_path}" %}
用于扩展模板
{% block %} 示例
<!-- learning_logs/base.html -->
{% block content %}
<!-- 如果子模板没有相应输入则展示默认模板 -->
<p>default Template</p>
{% endblock content %}
<!-- learning_logs/index.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 %}
如上代码则表示:
- 在基础模板
base.html
中规定了名为content
的块级占位 - 在子模板
index.html
中扩展了base.html
- 使用
base.html
的模板,并将名为content
的块级占位渲染为p
元素
使用 {% for i in items %}
{% endfor %}
可以使用循环语法,使用 {% empty %}
告诉 Django
在循环为空时如何处理
{% for %} 示例
{% for item in list %}
do something with each item
{% empty %}
do something when list is empty
{% endfor %}
{% csrf_token %}
是 Django
用来防止CSRF(Cross Site Request Forgery)
攻击的模板标签
Create Forms
可以通过 Django
内置的 forms
模块快捷创建表单处理模型
# forms.py
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ["text"]
labels = {"text": "topic"}
随后就可以创建对应的新建表单视图 def new_topic
和模板 new_topic.html
了。其中视图层的 def new_topic
同时负责 模板渲染 和 数据更新
# views.py
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)
# new_topic.html
<h1>Add a new topic</h1>
<form action="{% url 'learning_logs:new_topic' %}" method="post">
{% csrf_token %} {{form.as_p}}
<button name="submit">Add topic</button>
</form>
最后在 urls.py
中补充对应的路由信息即可完成一个表单页面的创建
Authentication System
Django
内置了用户权限系统模块,用以方便的进行注册、登录和注销
修改 urls.py
可以快速引入权限相关的 url 地址,(同时需要在 /learning_log/urls.py
中引入当前 url 地址)
# /users/urls.py
from django.urls import path, include
app_name = "users"
urlpatterns = [
path("", include("django.contrib.auth.urls")),
]
include('django.contrib.auth.url') 究竟包含了哪些 URL?
accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']
更多关于权限系统的介绍可以查看官方文档 Using the Django authentication system
User System
login
用户在访问 localhost:8000/users/login
的时候,Django
会访问默认视图函数 login
,但模板仍然需要手动提供
Django
默认的权限系统会在文件夹 registration
中查找模板,因此在 /users/template/registration
中新建 login.html
以提供默认的登陆模板
{# login.html #} {% 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 %}
模板中有几点需要注意:
- 一个应用的模板可以继承另一个应用中的模板
- 模板内置
context
中有form
变量,可以参考创建表单 - 表单的提交和访问地址都是
users/login
input
标签可以通过[name="text"] [value="xxx"]
告知Django
,提交完成后的跳转页面
logout
同理可以创建 logged_out.html
用以在登出后通知用户操作成功。需要注意的是,即使不创建这个页面, Django@4.2.9
也会默认进入一个 logged_out
页面
{# logged_out.html #} {% extends "learning_logs/base.html" %} {% block content
%}
<p>You have been logged out. Thank you for visiting!</p>
{% endblock content %}
register
借助 UserCreateForm
,Django
可以快速创建用户
# 空的注册表单
form = UserCreationForm()
在 views
中创建相应视图并注册数据,然后按照[创建表单](#Create Forms)的思路进行 register
注册页面的编写即可
Access Control
装饰器(decorator)是放在函数定义前面的指令,Python 在函数运行前根据它来修改函数代码的行为
Django
提供了装饰器@login_required
,让你能够轻松地只允许已登录用户访问某些页面
通过在 views
前增加 @login_required
,可以限制对应的视图只能在登陆状态下访问
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .models import Topic, Entry
@login_required
def topics(request):
"""显示所有的主题。"""
未登录的用户请求装饰器@login_required
保护的页面时,Django
会重定向到settings.py
中的LOGIN_URL
指定的URL
。如果未指定,则会报一个 404
的错误
指定LOGIN_URL = 'users:login'
代码后,将自动跳转到 /users
应用下的 login
视图
User Related Model
要将数据关联到提交它们的用户。只需将最高层的数据关联到用户,更低层的数据就会自动关联到用户
from django.db import models
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)
迁移数据库时,Django 将对数据库进行修改,使其能够存储Topic
和 User
之间的关联。但既有 Topic
要如何处理呢?最简单的办法是将既有主题都关联到同一个用户,如最开始创建的 superuser
。
在 [Django Shell](#Django Shell) 中可以快速查看当前所有用户的信息
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: ll_admin>, <User: eric>, <User: willie>]>
>>> for user in User.objects.all():
... print(user.username, user.id)
...
ll_admin 1
获取用户 ID 后,就可以迁移数据库了。通过执行 python manage.py makemigrations learning_logs
进行数据库迁移,根据指示迁移完毕后,就可以应用迁移了 python manage.py migrate
Specified User Access Control
用户在登陆后,视图的 request
参数将有一个 user
属性,其中存储了有关该用户的信息。
因此在 Django
中可以通过对模型对象操作filter
实现快捷限制访问的目的,使 Topic
只能被当前关联用户访问
--snip--
@login_required
def topics(request):
"""显示所有的主题。"""
topics = Topic.objects.order_by('date_added')
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)
--snip--
对于所有的视图,如果有指定用户的需求,都需要在代码中有针对性的修改。例如单个主题,如果没有做限制,那么任何用户都可以通过指定 URL (localhost:8000/topics/1/)
来访问对应页面。为此需要对 topic view
进行修改
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import Http404
--snip--
@login_required
def topic(request, topic_id="1"):
"""显示单个主题"""
topic = Topic.objects.get(id=topic_id)
# 确认请求的主题属于当前用户
if topic.owner != request.user:
raise Http404
context = {"topic": topic, "entries": entries}
return render(request, "learning_logs/topic.html", context)
--snip--
服务器上没有请求的资源时,标准的做法是返回 404 响应。这里导入了异常 Http404
,并在用户请求时判断用户与当前主题,如果不匹配则引发异常,Django
会返回一个 404 页面
TIP
关键字raise
的作用是主动引发异常,与 javascript
中的 throw
类似
Django Shell
通过命令行 python manage.py shell
, 输入一些数据后可以通过类似交互式终端的方式查看数据,类似 Read-Eval-Print-Loop (REPL)
。
一般在编写用户可请求的页面时,使用这种语法可以确认代码能获取所需的数据。
可以参考 Django
官方的Making queries查看模型操作相关方法