初始教程2
初始教程2
View 层用于处理用户的 web 请求,根据不同的 URL 调用不同的 html Template ,再进而根据 html Template 中的变量去找 Model 获取数据,生成完整的 html 之后,响应 web 请求。
写第一个页面
映射 URL
Django 项目(django-admin.py startproject 创建的这个 project)的主目录内,有预先由 Django 创建的 urls.py 这个文件。在这个文件内默认就配置了 admin/ 这个 path 会去调用 admin.site.urls 这个 view,从而当我们访问 admin/ 的时候可以去找下游来响应我们的请求。
learning_log/urls.py
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
那我们的下一步就是在这个文件中添加URL模型,来访问对应的页面。而我们在一个Django项目内,会有多个应用(app),每一个app又有多个具体的URL来访问不同的页面。一般建议引入 include 方法,将有着相同 path 前缀 (prefix) 的交由 app 目录内的 urls.py 处理改调取指定的 Template 模板来生成 html 页面。这样一来是项目目录下的 urls.py 文件简洁美观,二来各个 app 目录下的 urls.py 导入的都是相对目录的 views.py 逻辑上也统一,目前还看不出效果,但在项目开始扩展时很有帮助。
learning_log/urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include(('learning_logs.urls', 'learning_logs'), namespace='learning_logs')),
]
上面 include() 的参数是是一个元祖,元祖的第一个元素是 'learning_logs.url' ,意思导入就是 learning_logs 这个 app 目录下的 urls 模块(在 python 中就是标准的模块表示方法,实际上就是 learning_logs/urls.py 这个路径)。但是当前 3.2.4 的 Django 在初始化app (startapp)时,并没有在 app 目录下创建 urls.py 文件,所以需要自己创建。然后告诉他这个 learning_logs.urls 对应的 app 名字为 ' learning_logs' (因为 learning_logs/urls.py 是我们手动创建在这个路径,其实也可以是放在 learning_logs 这个 app 之外可以跟这个 app 目录没关系,所以需要这个元祖的第二个元素指定 app 的名字),第二个参数 namespace 也是一个新概念,但是通过参数名可以知道,做隔离用。让 home/ 这个URL 区别于项目中其他的 URL。
learning_logs/urls.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^index$', views.index, name='index'),
]
这里导入了 re_path 方法,支持正则表达式的 URL pattern,例如上面的 r'^$' 这样就用到正则,来匹配 localhost:8000/home/ 后面为空这条 URL。当然也可以写死 'index' 具体的字符串来匹配 localhost:8000/home/index 这条URL,他们的下游都是调用当前目录下 view.py 的 index 方法,那这个方法就开始需要我们自己的写了。
编写视图 View
**learning_logs/views.py **
from django.shortcuts import render
## Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
django 已经帮我们导入了 render 方法,那我们直接借助 render 开始写上面 urls.py 要用到的 index 方法。
index 方法有一个请求参数,传递给 render 方法,而 render 的另一个参数就是 Django Template 文件的路径。而这个文件目前也是不存在的,那我们就开始编写模板。
编写模板 Template
在 learning_logs 这个 app 目录下新建一个 template 目录,这个目录下就是上述 render 方法第二个参数的根目录。那在这个目录下再建立一个 learning_logs 目录,提前做个分类,因为通常一个 app 会有多个类型的模板,如果都放在 template 目录下,到时候一堆 html 文件就会显得杂乱无章。那我们开始写第一个模板文件。
learning_logs/template/learning_logs/index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>Learning Log</p>
<p>学习笔记帮助你保持追踪你的学习,任何你正在学习的主题都可以</p>
</body>
</html>
这个时候访问 /home (301 到 /home/) 或者 /home/index 就能看到这个模板的内容了,因为在上述映射 URL 这块就是这样配置的。当然上述例子不是一个真正意义上的模板文件,就是一个写死了的 html 文件,需要把部分数据变成变量就是一个真正的模板了。
模板继承
web 上的某一类页面的整体结构肯定是类似的,用代码的话来说就是一堆 html 文件中的部分代码是重复的。那也不能说为每一个结构相似的页面都写一个模板,而应该把这些共有的 html 元素提炼出来作为基础模板,然后下一级页面继承这个模板,从而复用相同的 html 元素。
父模板
创建一个名为 base.html 的模板
learning_logs/template/learning_logs/base.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>
{% block content %}{% endblock content %}
</body>
</html>
learning_logs:index 这个就是 <namespace_name>:<urlpattern_name> ,上述设置的命名空间终于在这里用上了。模板标签({% %})里写 url 'learning_logs:index' 就会在命名空间里找名为 index 的 urlpattern,那么就会在 url.py 里找到对应的 path: /home/index 最终替换这里的模板标签。
注意:如果 url.py 里的 urlpatterns 列表里面有 name 重名的 urlpattern 则取最下面的一个
learning_log/urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include(('learning_logs.urls', 'learning_logs'), namespace='learning_logs')),
]
learning_logs/urls.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^index$', views.index, name='index'),
]
这个例子就会取下面一个的 path: /home/index
子模板
learning_logs/template/learning_logs/index.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>学习笔记帮助你保持追踪你的学习,任何你正在学习的主题都可以</p>
{% endblock content %}
- 子模板的第一行必须包含标签
{% extends %}
- 父/子模板的 {% block %} 标签只能出现一次,不能出现同名的 {% block %} 标签(上述示例代码 content 这个名字已经被使用了),不然 Django 会报模板语法错误(TemplateSyntaxError)
- 子模板可以多写或少写父模板中已留好的 block 坑,不会报错只是匹配不到就不生成这部分的 html 代码
再写一个页面
再写一个页面重复一下配 URL 写 view 里的方法,及 Template 的过程。
加一个 urlpattern
learning_logs/urls.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^index$', views.index, name='index'),
re_path(r'^topics$', views.topics, name='topics'),
]
这次就加了个 topics 的 path,映射到 views 模块里的 topics 方法,再给这个映射命名为 topics。
马上在 views.py 写一个 topics 方法跟上上面的调用!
from django.shortcuts import render
from .models import Topic
## Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
def topics(request):
topics = Topic.objects.order_by('date_added')
context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)
继续继续,在 learning_logs/templates/learning_logs/ 下加一个 html 模板 topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>{{ topic.text }}</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
{% endblock content %}
重复一套下来,就应该理清了从 Django 项目里的 urls.py 开始,web 访问 path 到 html 模板的调用链路了。
我们再来看一下代码,views.topics return 的 render() 多了个上下文参数,这个上下文要求是个 dict ,其中的 key 会传递给 html 模板中的变量,而 key 的值就是数据模型的所有实例了,上下文承上启下地封装了数据模型实例作为了 render() 的第三个参数
动态 URL 匹配一个具体 topic 页面
上述的 topics 页面已经是个动态页面了,根据数据库中实际的 Topic 数据的不同生成内容不同的 html 文件返回给 /home/topics/ 这个 path。现在让 url 也动态起来,上代码
learning_logs/url.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^index$', views.index, name='index'),
re_path(r'^topics$', views.topics, name='topics'),
re_path(r'^topics/(?P<topic_id>\d+)', views.topic, name='topic'),
]
可以看到一个全新的表达式——(?P<topic_id>\d+)
- ( ) 括号表示捕获 URL 中的两个斜杠 / / 之间的值
- ?P<topic_id> 表示将值存到名为 topic_id 的实参中
- \d+ 表示包含在 / / 之间的所有数字都匹配,+ 表示1 位或多位
learning_logs/views.py
from django.shortcuts import render
from .models import Topic
## Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
def topics(request):
topics = Topic.objects.order_by('date_added')
context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)
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)
learning_logs/template/learning_logs/topic.html
{% 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, H:i' }}</p>
<p>{{ entry.text|linebreaks }}</p>
</li>
{% empty %}
<li>There are no entries for this topic yet</li>
{% endfor %}
</ul>
{% endblock content %}
这样一套 链接(URL)视图(view)模板(template)三连下来,用户就可以动态的访问到具体的 Topic 内容了,/home/topics/1, /home/topics/2, /home/topics/N...
当然现代社会了,不会让用户手动输入 URL 里面的参数的,都是做成页面上的链接去点击就可以了。那现在来改下 topics.html 模板
learning_logs/template/learning_logs/topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic.text }}</a>
</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
{% endblock content %}
这样就给 topic.text 加上了一个链接,注意 url 'learning_logs:topic' 里的 topic 和引号 之外的 topic 不是一回事哦,外面的 topic 是整个页面中 topics 的一个元素,而 topics 是 views.topics 传进来的一组对象。learning_logs:topic 是只 lerarning_logs 这个 namespace 里名为 topic 的 url pattern (urls.py),上述也讲过了,这里估计又忘了再次提一下。
总结:链接(URL)视图(view)模板(template)三连