目录
一、创建虚拟环境(Windows)
二、创建项目
三、创建应用程序
四、创建网页:学习笔记主页
五、创建其他网页
六、用户输入数据
七、用户账户
八、让用户拥有自己的数据
九、设置应用程序样式
十、部署“学习笔记”
一、创建虚拟环境(Windows)
1、python语言有很多很多的库,但是python有2和3不兼容的情况,而且很多框架,如django框架的不同版本之间也会有些兼容问题,所以有时在开发或维护不同版本的框架时就会造成冲突。而虚拟环境很好的解决了这个问题。虚拟环境就像是一个容器,我们可以在这个容器内安装自己需要的包和模块,并且对外界没有任何影响。
2、如果系统中只有python2或只有python3,可以直接打开命令行,输入pip install virtualenv 来下载创建虚拟环境的包(如果提示pip不是内部命令,把python文件下的Scripts的文件路径加入计算机环境变量即可可)
3、选择一个或创建一个文件夹用来存放创建的虚拟环境(以E盘下的E:\Python\learning_log为例)。
4、进入cmd窗口,使用cd导航来到这个文件夹内
5、使用命令:virtualenv 虚拟环境名 来创建一个虚拟环境,创建成功后如下图:
6、cd 进入虚拟环境下的Scripts文件夹,输入命令activate 激活虚拟环境
当命令行前面有(ll_env)时,就表示现在处于虚拟环境里了,在这里使用pip安装自己要使用的模块即可
7、想要退出虚拟环境时 使用deactivate
如果系统中py2和py3同时都有,那么使用pip2和pip3分别安装好py2和py3的virtualenv包后,可以将python2目录下的Scripts目录里的virtualenv.exe改为virtualenv2.exe,在保证python的环境变量都加到了计算机环境变量的情况下,我们就可以使用’virtualenv2 虚拟环境名’ 来创建py2的虚拟环境,用’virtualenv3 虚拟环境名’ 创建py3的虚拟环境了。
二、创建项目
1、要使用Django,首先需要创建虚拟工作环境。虚拟环境是系统的一个位置,你可以在其中安装包,并将其与其他Python包隔离,为了将项目部署到服务器,隔离是必须的。
2、首先要新建一个文件夹E:\Python\learning_log,在终端中导航到该文件夹,创建一个名为ll_env的虚拟环境,前提是你安装了模块virtualenv,然后导航到ll_env文件夹下的Scripts文件夹,运行activate激活虚拟环境,然后就可以在环境中安装Django:pip install Django,Django只有在虚拟环境处于活动状态时才能用。
3、导航到E:\Python\learning_log,确保环境处于活动状态,新建一个名为learning_log的项目,这个命令末尾的句点让新项目使用合适的目录结构。learning_log文件夹中出现四个文件,其中settings.py指定Django如何与你的系统交互以及如何管理项目,文件urls.py告诉Django应该创建哪些网页来响应浏览器请求;文件wsgi.py帮助Django提供它创建的文件,这个文件名是webserver gateway interface (Web服务器网关接口)的缩写。
4、Django将大部分与项目相关的信息都存储在数据库中,因此我们需要创建一个供Django使用的数据库。
- 我们将修改数据库称为迁移数据库。首次执行命令migrate时,将让Django确保数据库与项目的当前状态匹配,operations to perform表示它将创建必要的数据库表,用于存储我们将在这个项目中使用的信息,再确保数据库结构与当前代码匹配。又出现了一个新的文件db.sqlite3,SQLite是一种使用单个文件的数据库,是编写简单应用程序的理想选择,因为它让你不用关注数据库管理的问题。
5、核实Django是否正确创建了项目,可执行命令python manage.py runserver,如下图所示:
- 项目的URL为http://127.0.0.1:8000/,表明项目在你的计算机(即localhost)的端口8000上侦听。localhost是一种只处理当前系统发出的请求,而不允许其他人查看你正在开发的网页的服务器。现在打开一款web浏览器,并输入http://127.0.0.1:8000/,出现下图:
注意: 如果出现错误消息‘that port is already in use’(指定端口已被占用),请执行命令python manage.py runserver8001,让Django使用另一个端口,如果还不行,不断执行上述命令,知道找到可用端口。
6、Django项目由一系列应用程序组成,他们协同工作,让项目成为一个整体,暂时只创建一个应用程序,它将完成大部分工作。刚才的终端窗口还运行着runserver,所以请再打开一个窗口,导航到manage.py所在文件夹,激活虚拟环境,再执行命令startapp:
- 命令startapp appname 让Django建立创建应用程序所需的基础设施。新增文件夹learning_logs,我们使用其中的文件models.py来定义我们要在应用程序中管理的数据。
三、创建应用程序
1、定义模型:打开learning_logs文件夹中的models.py,编辑如下:
from django.db import models
class Topic(models.Model):
'''用户学习主题的类'''
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
'''返回模型的字符串表示'''
return self.text
- 创建一个继承models.Model的类,Model是Django中一个定义了模型基本功能的类,该类只有两个属性:text和date_added。text是一个CharField——由字符或文本组成的数据,需要存储少量的文本,如名称、标题或城市时,可使用CharField。定义CharField属性时,必须告诉Django要在数据库中预留多少空间,这里设置为200个字符,对应存储名称标题已经足够了。date_added是一个DateTimeField——记录日期和时间的数据,实参auto_now_add=True,每当用户创建新主题时,Django都将这个属性自动设置成系统当前的日期和时间。
- 我们需要告诉Django,默认使用哪一个属性来显示有关主题的信息,Django调用方法__str__()来显示模型的简单表示。在这里,我们调用方法__str__(self)返回存储在属性text中的字符串。
- 提示:如果你想知道可在模型中使用的各种字段,请参阅Django model field reference(Django模型字段参考),网址为:https://docs.djangoproject.com/en/1.8/ref/models/fields/
2、激活模型:要使用模型,必须让Django将应用程序包含到项目中。打开E:\Python\learning_log\learning_log中的settings.py,你将看到这样一个片段,即告诉Django哪些应用程序安装在项目中:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#我的应用程序
'learning_logs'
]
- 这是一个列表,告诉Django项目是由哪些应用程序组成的,将当前的learning_logs应用程序添加到这个列表中。接下来,需要让Django修改数据库,使其能够存储与模型Topic相关的信息,在终端执行下面的命令:
- 命令makegrations让Django确定该如何修改数据库,输出表明Django创建了一个名为0001_initial.py的迁移文件,这个文件将在数据库中为模型Topic创建一个表。
- 下面来应用这种迁移,让Django替我们修改数据库:
- 每当需要修改‘学习笔记’管理的数据时,都采用如下三个步骤:修改models.py,对learning_logs调用makemigrations,让Django迁移项目。
3、创建超级用户
- Django允许你创建一个拥有所有权限的用户——超级用户。创建超级用户的命令如下:
- 用户名可以任意输入,邮件地址可以为空,密码需要输入两次,且两次相同,否则会报错,如上图所示。
- 注意:可能会对网站管理员隐藏有些敏感信息。例如,Django并不存储你输入的密码,而是存储从改密码派生出来的一个字符串——散列值。每当你输入密码时,Django都计算其散列值,并将结果与存储的散列值进行对比,如果这连个散列值相同,就通过了身份验证。通过存储散列值,即便黑客获得了网站数据库的访问权,也只能获取其中的散列值,而无法获得密码。在网站配置正确的情况下,几乎无法根据散列值推导出密码。
4、向管理网站注册模型
- 我们创建应用程序learning_logs时,Django在models.py所在文件夹创建了一个admin.py文件,打开输入一
下代码:
from django.contrib import admin
from learning_logs.models import Topic
admin.site.register(Topic)
- 这些代码导入我们要注册的模型Topic,再使用admin.site.register()让Django通过管理网站管理我们的模型。
- 现在使用超级用户访问管理网站:http://127.0.0.1:8000/admin/,结果如下图所示
- 注意:如果你在浏览器上看到http://127.0.0.1:8000/ 拒绝了我们的连接请求,请确认你在终端窗口中是否运行
着Django服务器,如果没有,请激活虚拟环境,并执行命令python manage.py runserver
5、添加主题
- 点击Topics后面的加号,在text方框中输入chess,保存。
6、定义模型Entry
- 要记录学习到的攀岩和国际象棋的知识,需要为用户可在学习笔记中添加的条目定义模型。
- 每个条目都与特定的主题相关联,这种关系被称为多对一关系,即多个条目可关联到同一个主题
from django.db import models
class Topic(models.Model):
'''用户学习主题的类'''
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
'''返回模型的字符串表示'''
return self.text
class Entry(models.Model):
'''学到的有关某个主题的具体知识'''
topic = models.ForeignKey(Topic,on_delete=models.DO_NOTHING)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
return self.text[:50] + '...'
- 像Topic一样,Entry也继承了Django基类Model。
- 第一个属性topic是一个ForeignKey(外键)实例,外键是一个数据库术语,它引用了数据库中的另一条记录;这些代码将每个条目关联到特定的主题,每个主题创建时,都给他分配一个键(或ID)。需要在两项数据之间建立联系时,Django使用与每项信息相关联的键。定义外键和一对一关系的时候需要加on_delete=models.DO_NOTHING,此参数为了避免两个表里的数据不一致问题,不然会报错:TypeError: __init__() missing 1 required positional argument: 'on_delete'。
- 属性text是一个TextField实例,这种字段不需要长度限制,因为我们不想限制条目的长度。
- 属性date_added让我们能够按创建顺序呈现条目,并在每个条目旁边设置时间戳。
- 接着我们在Entry类中嵌套了Meta类,该类存储用于管理模型的额外信息,在这里,它让我们能够设置一个特殊属性,让Django在需要时使用entries来表示多个条目,如果没有这个类,Django将使用entrys来表示多个条目。
- 最后,方法__str__()告诉Django,呈现条目时应显示哪些信息。由于条目包含的文本可能很长,我们让Django只显示text的前50个字符,我们还添加了个省略号,表示显示的并非整个条目。
7、迁移模型
- 由于又添加了一个模型,因此需要再次迁移数据库。还是之前说过的三个步骤:修改models.py,执行python manage.py makemigrations,再执行python manage.py migrate。下面来迁移数据库并查看输出:
- 生成了一个新的迁移文件——0002_entry.py,它告诉Django如何修改数据库,使其能够存储与模型Entry
有关的信息。
8、向管理网站注册Entry
from django.contrib import admin
from learning_logs.models import Topic,Entry
admin.site.register(Topic)
admin.site.register(Entry)
9、Django shell交互式环境
- 通过交互式终端会话以编程方式查看输入的数据
- 在活动的虚拟环境中执行命令python manage.py shell,会启动一个Python编译器,可使用它来探索存储在项目中的数据。接下来我们导入了模块learning_logs.models中的模型Topic,然后使用方法Topic.objects.all()来获取模型中所有实例,返回一个列表,称为查询集(QuerySet)
- 查看每个主题的ID,知道ID后,就可以获取该对象并查看其任何属性
- 前面我们给模型Entry定义了属性topic,这是一个ForeignKey(外键),将条目与主题关联起来。
- 为通过外键获取数据,我们可使用相关模型的小写名称、下划线和单词set。例如,假设你有模型Pizza和Topping,而Topping通过一个外键关联到Pizza;如果你有一个名为my_pizza的对象,表示一张披萨,就可以使用代码my_pizza.topping_set.all()来获取这张披萨的所有配料。
- 编写用户可请求的网页时,我们将使用这种方法。确认代码能够获取所需的数据时,shell很有帮助,如果代码在shell中的行为符合预期,那么他们在项目文件中也能正常工作。如果代码引发了错误或获取的数据不符合预期,那么在简单的shell环境中排除故障要比在生成网页的文件中排除故障容易的多。
- 注意:每次修改模型后,都要重启shell,这样才能看到修改的效果。要退出shell会话,可按Ctrl+D;如果是Windows,Ctrl+Z,再按回车。
四、创建网页:学习笔记主页
1、映射URL
- 用户通过在浏览器中输入URL以及单击链接来请求网页,因此我们需要确定项目需要哪些URL。
- 主页的URL最重要,它是用户用来访问项目的基础URL,当前基础URL(http://127.0.0.1:8000/)返回默认的Django网站,让我们知道正确的建立了项目,我们将修改这一点,将这个基础URL映射到‘学习笔记’的主页。
- 打开项目主文件夹learning_log中的文件urls.py:
1 from django.contrib import admin
2 from django.urls import path
3 from django.conf.urls import include,url
4
5 urlpatterns = [
6 path('admin/', admin.site.urls),
7 path('',include('learning_logs.urls', namespace='learning_logs')),
8 ]
- 前两行导入了为项目和管理网站URL的函数和模块,在这个针对整个项目的urls.py文件中,变量urlpatterns包含项目中的应用程序的URL。
- 第6行代码包含模块admin.site.urls,该模块定义了可在管理网站中请求的所有URL。
- 第7行代码,我们添加了一行包含模块learning_logs.urls的代码。其中实参namespace让我们能够将应用程序learning_logs的URL同项目中的其他URL区分开来,对项目扩展很有帮助。
- 接下来我们需要在learning_logs文件夹中创建另一个urls.py文件。
'''定义learning_logs的URL模式'''
from django.conf.urls import url
from . import views
app_name='[app_name]'
urlpatterns = [
# 主页
url(r'^$',views.index,name='index'),
]
- 导入函数url,用它来讲URL映射到视图。
- 导入模块view,其中的句点表示从当前的urls.py所在的文件夹中导入view.py文件。这个模块中变量urlpatterns是一个列表,包含可在应用程序learning_logs中请求的网页。
- app_name指定该应用程序的名称。python3 Django 环境下,如果你遇到namespace没有注册以及在根目录下urls.py中的include方法的第二个参数namespace添加之后就出错的问题。请在[app_name]目录下的urls.py中的urlpatterns前面加上app_name='[app_name]',[app_name]代表你的应用的名称。
- 实际的URL模式是一个对函数url()的调用,这个函数接受三个实参,第一个是一个正则表达式r'^$',其中r让Python将接下来的字符串视为原始字符串,而引号告诉Python正则表达式始于和终于何处。脱字符(^)让Python查看字符串的开头,美元符号让Python查看字符串的结尾,总体而言,这个表达式让Python查找开头和结尾之间没有任何东西的URL。Python忽略项目的基础URL(http://127.0.0.1:8000/),因此这个表达式和基础URL匹配。如果请求的URL不与任何URL模式匹配,Django将返回一个错误页面。
- url()的第二个实参,指定了要调用的视图函数。请求的URL与正则表达式匹配时,Django将调用views.index。
- 第三个实参指定这个URL模式的名称为index,让我们在代码的其他地方可以引用它,每当需要提供这个主页的链接时,都使用这个名称,而不编写URL
- 注意:正则表达式一般被称为regex,几乎每种语言都使用它。
2、编写视图
- 视图函数接受请求中的信息,准备好生成网页所需的数据,再将这些数据发给浏览器。
- 打开learning_logs文件夹中的view.py文件,为主页编写视图命令如下:
1 from django.shortcuts import render
2 def index(request):
3 '''学习笔记的主页'''
4 return render(request,'learning_logs/index.html')
- URL请求与我们刚才定义的模式匹配时,Django将在文件view.py中查找函数index(),再将请求对象传递给这个视图函数。
- 函数render()根据视图提供的数据渲染响应。其中两个实参:原始请求对象以及一个可用于创建网页的模板。
3、编写模板
- 模板定义了网页的结构,模板能让你访问视图提供的任何数据。
- 在文件夹learning_logs中新建文件夹templates,在templates文件夹中新建文件夹learning_logs,在最里面的learning_logs
- 文件夹中新建文件index.html,在文件中编写如下命令:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Learning Log</title>
</head>
<body>
<p>Learning Log</p>
<p>Learning log help you keep track of your learning,for any topic you'are
learning about.</p>
</body>
</html>博客地址:
- 两个<head>之间的表示网站名称,接下来两行代码分别表示可以输入中文,两个<title>之间的代码表示网站的名称。
- 两个<body>之间的是网站首页的内容
- <p></p>标识段落,<p>指出了段落的开头位置,</p>指出段落的结束位置,这里定义了两个段落,一个充当标题一个阐述可以用学习笔记来干什么。
- </html>后面可以放链接。重新访问主页:
五、创建其他网页
1、模板继承:父模板
- 创建网站时,几乎都有一些所有网页都包含的元素,我们可以编写一个包含通用元素的父模板,并让每个网页都继承这个模板。
- 首先,我们来创建一个base.html文件,放在index.html所在文件夹中,当前,所有页面都包含的元素只有顶端的标题,因此我们将标题设置为到主页的链接:
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Learning Log</title>
6 </head>
7 <body>
8 <p>
9 <a href="{% url 'learning_logs:index' %}">Learning Log</a>
10 </p>
11 {% block content %}{% endblock content %}
12 </body>
13 </html>
- 第9行代码创建了一个包含项目名的段落,该段落也是一个到主页的链接。我们使用了一个模板标签来创建链接,它是用大括号和两个%表示的;模板标签是一小段代码,生成要在网页中显示的信息,在这个实例中,模板标签生成一个URL,该URL与learning_logs\urls.py中定义的名为index的URL模式匹配。在这个实例中,learning_logs是一个命名空间,而index是该命名空间中一个名称独特的URL模式。
- 在简单的HTML页面中,链接是使用锚标签定义的:<a href="link_url">link text</a>
- 注意:让模板标签来生成URL,可让链接保持最新容易的多,要修改项目中的URL,只需要修改urls.py中的URL模式。在我们的项目中,每一个网页都继承base.html,因此从现在开始,每个网页都包含回到主页的链接。
- 第11行我们插入了一对块标签,这个块名为content,是一个占位符,其中包含的信息将由子模板指定。子模板并非必须定义父模板中的每个块,因此在父模板中可使用任意多个块来预留空间,而子模板可根据需要定义相应数量的块。
- 注意:在Python代码中,我们几乎总是缩进四个空格,而模板文件的缩进层级更多,因此每个层级通常只缩进两个空格。
2、模板继承:子模板
- 先在要重新编写index.html文件,使其继承base.html:
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Learning Log</title>
6 </head>
7 <body>
8 {% extends 'learning_logs\base.html' %}
9 {% block content %}
10 <p>Learning log help you keep track of your learning,for any topic you'are
11 learning about.</p>
12 {% endblock content %}
13 </body>
14 </html>
- 子模板的第一行必须包含标签{% extends %},让Django知道它继承了哪个父模板,extends后面加上父模板的路径,这行代码导入父模板中的所有内容,让子模板能够指定要在content块预留的空间中添加的内容。
- 第9行我们插入了一个名为content的{% block %}标签,以定义content块。不是从父模板中继承的内容都包含在这个content块中,标签{% endblock content %}指出了内容定义的结束位置。
- 注意:在大型项目中,通常有一个用于整个网站的父模板——base.html,且网站的每个主要部分都有一个父模板。每个部分的父模板都继承base.html,而网站的每个网页都继承相应部分的父模板。
3、显示所有主题的页面
(1)定义URL模式,使用单词topics,因此http://127.0.0.1:8000/topics/将返回显示所有主题的页面。
- 修改文件urls.py如下:
1 '''定义learning_logs的URL模式'''
2 from django.conf.urls import url
3 from . import views
4 app_name='[app_name]'
5 urlpatterns = [
6 # 主页
7 url(r'^$',views.index,name='index'),
8 #显示所有主题的页面
9 url(r'^topics/$',views.topics,name='topics'),
10 ]
- 我们只是在正则表达式中添加了topics/,Django检查请求的URL时,这个模式与这样的URL匹配:基础URL后面跟着topics,可以在末尾处包含斜杠,也可以省略它,但单词topics后面不能有任何东西,否则就与该模式不匹配。其URL与该模式匹配的请求都将交给view.py中的函数topics()来处理。
(2)修改视图view.py
1 from django.shortcuts import render
2 from .models import Topic
3 def index(request):
4 '''学习笔记的主页'''
5 return render(request,r'learning_logs\index.html')
6 def topics(request):
7 '''显示所有主题的函数'''
8 topics = Topic.objects.order_by('date_added')
9 context = {'主题':topics}
10 return render(request,r'learning_logs\topics.html',context)
- 首先导入与所需数据相关的模型,函数topics包含一个形参:Django从服务器那里收到的request对象(见6)。在8处,我们查询数据库——请求提供Topic对象,并按属性date_added对他们排序,将返回的查询存入topics。
- 在9处,我们定义了一个将要发给模板的上下文,上下文是一个字典,其中的键是我们将在模板中用来访问数据的名称,而值是我们要发送给模板的数据,这里只有一个键值对,它包含我们将在网页中显示的一组主题。创建使用数据的网页时,除对象request和模板的路径外,我们还将变量context传递给render().
(3)创建显示所有主题的HTML模板文件:topics.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Learning Log</title>
6 </head>
7 <body>
8 {% extends 'learning_logs\base.html' %}
9 {% block content %}
10 <p>Topics</p>
11 <ul>
12 {% for topic in topics %}
13 <li>{{ topic }}</li>
14 {% empty %}
15 <li>No topics have added yet.</li>
16 {% endfor %}
17 </ul>
18 {% endblock content %}
19 </body>
20 </html>
- 首先使用标签{% extends %}来继承base.html,再开始定义content块。这个网页的主题是一个项目列表,其中列出了用户输入的主题,在标准HTML中,项目列表被称为无需列表,用标签对<ul></ul>表示,包含所有主题的项目列表始于11处。
- 在12处,我们使用了一个相当于for循环的模板标签,它遍历字典context中列表topics,模板中的for循环都要使用{% endfor %}来指出结束位置。
- 在循环中,我们要将每个主题转换为一个项目列表项。要在模板中打印变量,需要将变量名用双花括号括起来。每次循环,{{ topic }}都被替换为topic的当前值。<li></li>表示一个项目列表项,在标签对<ul></ul>内部,位于标签<li></li>之间的内容都是一个项目列表项。
- 标签{% empty %}告诉Django,列表为空时该怎么办,这里是打印一个消息,告诉用户还没有添加任何主题。
- 最后结束for循环和项目列表
(4)修改父模板,使其包含到显示所有主题的页面的链接
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Learning Log</title>
6 </head>
7 <body>
8 <p>
9 <a href="{% url 'learning_logs:index' %}">学习笔记</a> -
10 <a href="{% url 'learning_logs:topics' %}">Topics</a>
11 </p>
12 {% block content %}{% endblock content %}
13 </body>
14 </html>
- 这里我们在主页链接后面添加了一个连字符(-),然后添加了显示所有主题的链接。
- 刷新浏览器中的主页,点击Topics,就看到所有主题了
4、显示特定主题的页面
(1)URL模式
- 与前面的所有URL模式都不同,它将使用主题的ID属性来指出请求的是哪个主题。例如,主题chess的ID为1,URL为http://127.0.0.1:8000/topics/1/,下面修改learning_logs中的urls.py:
#特定主题的详细页面
url(r'^topics/(?P<topic_id>\d+)/$',views.topic,name='topic'),
- 正则表达式的第二部分(/(?P<topic_id>\d+)/)与包含在两个斜杠内的整数匹配,并将这个整数存储在一个名为topic_id的实参中;这部分表达式两边的括号捕获URL中的值;?P<topic_id>将匹配的值存储到topic_id中,而表达式\d+与包含在两个斜杠内的任何数字都匹配,不管这个数字为多少位。
- 发现URL与这个模式匹配时,Django将调用视图函数topic(),并将存储在topic_id中的值作为他的实参传递给它,在这个函数中,我们间使用topic_id来获取相应的主题。
(2)视图
- 在view.py文件中创建topic()函数
1 def topic(request,topic_id):
2 '''显示单个主题及其所有的条目'''
3 topic = Topic.objects.get(id=topic_id)
4 entries = topic.entry_set.order_by('-date_added')
5 context = {'topic':topic,'entries':entries}
6 return render(request,r'learning_logs\topic.html',context)
- 这个函数接受正则表达式(?P<topic_id>\d+)捕获的值,并将其存储到topic_id中,在3中我们使用get()来获取指定的主题。在4处我们获得与该主题相关的条目,并将它们按时间排序,-表示降序排列,即先显示最近的条目。我们将主题和条目都存储在字典中,并将字典发送给模板topic.html。
- 注意:3和4处的代码被称为查询,因为它们向数据库查询特定的信息。在自己的项目中编写这样的查询时,最好先在Django shell中尝试,在shell中执行代码可更快的获得反馈。
(3)模板
- 这个模板需要显示主题的名称和所有条目,如果当前主题不包含任何条目,我们还需要向用户指出这一点。
- topic.html
1 {% extends 'learning_logs\base.html' %}
2 {% block content %}
3 <p>Topics: {{ topic }}</p>
4 <p>Entries:</p>
5 <ul>
6 {% for entry in entries %}
7 <li>
8 <p>{{ entry.date_added|date:'M d,Y H:i' }}</p>
9 <p>{{ entry.text|linebreaks }}</p>
10 </li>
11 {% empty %}
12 <li>There are no entries for this topic yet.</li>
13 {% endfor %}
14 </ul>
15 {% endblock content %}
- 3中显示当前的主题,它存储在变量{{ topic }}中,变量topic包含在字典context中,所以这里可以使用。接下来,定义一个显示每个条目的项目列表,遍历每个主题的所有条目。
- 每个项目列表都列出两项信息:条目的时间戳和完整的文本。竖线(|)表示模板过滤器——对模板变量的值进行修改的函数,过滤器date:'M d,Y H:i' 以这样的方式显示时间,接下来的一行显示完整的文本,而不再是前50个字符。过滤器linebreaks将包含换行符的长条目转换为浏览器能够理解的格式,以免显示为一个不间断的文本块。
(4)将每个主题都设置为链接
- 在浏览器中查看特定主题的页面前,我们需要修改topics.html,让每个主题都链接到相应的网页。
1 {% extends 'learning_logs\base.html' %}
2 {% block content %}
3 <p>Topics</p>
4 <ul>
5 {% for topic in topics %}
6 <li>
7 <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
8 </li>
9 {% empty %}
10 <li>No topics have been added yet.</li>
11 {% endfor %}
12 </ul>
13 {% endblock content %}
- 我们使用模板标签url根据learning_logs中名为topic的URL模式来生成合适的链接。这个模式要求提供实参top_id,因此我们在模板标签中添加了属性topic_id。现在,主题列表的每个主题都是一个链接,链接到相应主题的页面,如http://127.0.0.1:8000/topics/6/
- 注意:少一个空格都不行
- 刷新浏览器
六、用户输入数据
1、添加新主题
(1)创建用于添加主题的表单:forms.py,存储到models.py所在文件夹。
1 from django import forms
2 from .models import Topic
3 class TopicForm(forms.ModelForm):
4 class Meta:
5 model = Topic
6 fields = {'text'}
7 labels = {'text':''}
- 最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。
- 这里我们根据模型Topic创建一个表单,该表单只包含字段text(见6),7处的代码让Django不要为字段text生成标签。
(2)URL模式:new_topic
- 用户要添加主题时,我们要切换到http://127.0.0.1:8000/new_topic/,下面将new_topic的URL模式添加到learning_logs\urls.py:
#添加新主题页面
url(r'^new_topic/$',views.new_topic,name='new_topic'),
(3)修改视图views.py:添加函数new_topic()
- 函数new_topic()需要处理两种情形:刚进入new_topic网页,应显示一个空表单;对提交的表单数据进行处理,并将用户重定向到网页topics。
1 from django.shortcuts import render
2 from django.http.response import HttpResponseRedirect
3 from django.urls import reverse
4 from .models import Topic
5 from .forms import TopicForm
6
7 def new_topic(request):
8 '''添加新主题'''
9 if request.method != 'POST':
10 #未提交数据,创建一个新表单
11 form = TopicForm()
12 else:
13 #POST提交的数据,对数据进行处理
14 form = TopicForm(request.POST)
15 if form.is_valid():
16 form.save()
17 return HttpResponseRedirect(reverse('learning_logs:topics'))
18 context = {'form':form}
19 return render(request,r'learning_logs\new_topic.html',context)
- 导入HttpResposeRedirect类,用户提交主题后,我们将使用这个类将用户重定向到网页topics;
- 函数reverse()根据指定的URL模型确定URL,这意味着Django将在页面被请求时生成URL;
- 记得导入刚才创建的空表单TopicForm()
(4)GET请求和POST请求
- 创建WEB应用时,我们将用到两种请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户需要表单提交信息时,通常使用POST请求。函数new_topic( )将请求对象作为参数,用户初次请求该网页时,浏览器将发送GET请求;用户填写并提交表单时,其浏览器将发送POST请求;根据请求类型,我们可以确定用户请求的是空表单(GET请求)还是要求对填写好的表单进行处理(POST请求)。
- 这里我们使用request.method判断请求类型,如果不是POST请求,我们就返回一个空表单(即便请求是其他类型的,返回一个空表单也不会有问题),我们创建一个TopicForm实例,将其存储在变量form中,再通过上下文字典将这个表单发送给模板,由于我们实例化TopicForm时没有指定任何实参,Django将创建一个可供用户填写的空表单。
- 如果请求是POST类型,对提交的表单数据进行处理,我们使用用户输入的数据(它们存储在request.POST中)创建一个TopicForm实例,这样对象form将包含用户提交的信息。
- 要将提交的数据保存到数据库,必须确定他们是有效的。函数is_valid()核实用户填写了所有必不可少的字段(表单默认字段都是必不可少的),且输入的数据与要求的字段类型一致(例如,字段text少于200个字符,这是我们在models.py中指定的),如果所有字段都有效,我们就可以保存数据。
- 函数reverse( )获取页面topics的URL,并将其传递给HttpResposeRedirect(),函数将用户的浏览器重定向到页面topics,在topics页面中,用户将在主题列表中看到刚才输入的主题。
(5)模板:new_topic.html
1 {% extends 'learning_logs\base.html' %}
2 {% block content %}
3 <p>Add a new topic:</p>
4 <form action="{% url 'learning_logs:new_topic' %}" method="post">
5 {% csrf_token %}
6 {{ form.as_p }}
7 <button name="submit">提交</button>
8 </form>
9 {% endblock content %}
- 4处的action告诉服务器将提交的表单数据发送到哪里,这里我们将它发送给视图函数new_topic( ),实参method让浏览器以POST请求的方式提交数据。
- 模板标签{% csrf_token %}用来防止攻击者利用表单来获取对服务器未经授权的访问,这种攻击被称为跨站请求伪造。
- {{ form.as_p }}用来显示表单,修饰符as_p让Django以段落格式渲染所有表单元素,这是一种整洁的显示表单的简单方式。
- 7处的代码为表单创建提交按钮。
(6)链接到页面new_topic
- 在topics.html中添加一个到new_topic的链接
{% extends 'learning_logs\base.html' %}
{% block content %}
<p>Topics</p>
<ul>
--snip--
</ul>
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
{% endblock content %}
2、为主题添加新条目
(1)用于添加新条目的表单
1 from django import forms
2 from .models import Topic,Entry
3 class TopicForm(forms.ModelForm):
4 --snip--
5 class EntryForm(forms.ModelForm):
6 class Meta:
7 model = Entry
8 fields = {'text'}
9 labels = {'text':''}
10 widgets = {'text':forms.Textarea(attrs={'cols':80})}
- 小部件widget是一个HTML表单元素,如单行文本框、多行文本区域或下拉列表。通过设置属性widgets,可覆盖Django选择的默认小部件。form.Textarea( )制定了字段text的输入小部件,将文本区域的宽度设置为80列,而不是默认的40列,cols代表列数。
(2)URL模式new_entry:需要包含实参topic_id,因为条目必须与特定的主题相关联。
#添加新条目的页面
url(r'^new_entry/(?P<topic_id>\d+)/$',views.new_entry,name='new_entry'),
(3)视图函数:new_entry( )
1 --snip--
2 from .forms import TopicForm,EntryForm
3 --snip--
4 def new_entry(request,topic_id):
5 '''添加新条目'''
6 topic = Topic.objects.get(id=topic_id)
7 if request.method != 'POST':
8 #未提交数据,创建一个新表单
9 form = EntryForm()
10 else:
11 #POST提交的数据,对数据进行处理
12 form = EntryForm(request.POST)
13 if form.is_valid():
14 new_entry = form.save(commit=False)
15 new_entry.topic = topic
16 new_entry.save()
17 return HttpResponseRedirect(reverse('learning_logs:topic',
18 args=[topic_id]))
19 context = {'topic':topic,'form': form}
20 return render(request, r'learning_logs\new_entry.html', context)
- 调用reverse()时,需要两个实参:需要根据它来生成URL的URL模式的名称;列表args包含要包含在URL中的所有实参,这里只有一个实参topic_id。
(4)模板:new_entry.html
1 {% extends 'learning_logs\base.html' %}
2 {% block content %}
3 <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>
4 <p>Add a new entry:</p>
5 <form action="{% url 'learning_logs:new_entry' topic.id %}" method="post">
6 {% csrf_token %}
7 {{ form.as_p }}
8 <button name="submit">提交</button>
9 </form>
10 {% endblock content %}
- 3处在页面顶端显示了主题,让用户知道自己在哪个主题下添加条目;该主题也是一个链接,可返回到该主题的主页面。
(5)链接到页面new_entry:在显示特定主题的页面中添加到页面new_entry的链接。
--snip--
<p>Entries:</p>
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a>
</p>
<ul>
--snip--
刷新浏览器:
3、编辑条目
(1)URL模式:edit_entry
#用于编辑条目的页面
url(r'^edit_entry/(?P<entry_id>\d+)/$',views.edit_entry,name='edit_entry'),
(2)视图函数:edit_entry( )
1 --snip--
2 from .models import Topic,Entry
3 from .forms import TopicForm,EntryForm
4 --snip--
5 def edit_entry(request,entry_id):
6 '''编辑既有条目'''
7 entry = Entry.objects.get(id=entry_id)
8 topic = entry.topic
9 if request.method != 'POST':
10 #初次请求,使用当前条目填充表单
11 form = EntryForm(instance=entry)
12 else:
13 #POST提交的数据,对数据进行处理
14 form = EntryForm(instance=entry,data=request.POST)
15 if form.is_valid():
16 form.save()
17 return HttpResponseRedirect(reverse('learning_logs:topic',
18 args=[topic.id]))
19 context = {'entry':entry,'topic': topic, 'form': form}
20 return render(request, r'learning_logs\edit_entry.html', context)
- 在9的if语句中,我们使用实参instance=entry创建一个EntryForm实例,这个实参让Django创建一个表单,并使用既有条目对象中的信息填充它,用户能看到既有条目信息,并能够编辑它们。
- 处理POST请求时,我们传递实参instance=entry,data=request.POST,让Django根据既有条目创建一个表单实例,并根据request.POST中的相关数据对其进行修改。
(3)模板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">保存</button>
</form>
{% endblock content %}
(4)链接到页面:在显示特定主题的页面中添加到页面edit_entry的链接。
--snip--
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:'M d,Y H:i' }}</p>
<p>{{ entry.text|linebreaks }}</p>
<p>
<a href="{% url 'learning_logs:edit_entry' entry.id %}">Edit entry</a>
</p>
</li>
--snip--
七、用户账户
1、应用程序users
(1)使用命令startapp 来创建一个名为users的应用程序:
(2)将应用程序users添加到settings.py中
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'learning_logs'
'users'
]
(3)修改项目根目录learning_log中的urls.py,添加应用程序users的URL
path(r'^users/',include('users.urls', namespace='users')),
2、登陆页面
(1)在users中新建urls.py,使用默认登陆视图,编辑如下:
1 '''为应用程序users定义URL模式'''
2 from django.conf.urls import include,url
3 from django.contrib.auth.views import LoginView
4 from . import views
5 app_name = 'users'
6 urlpatterns = [
7 #登陆页面
8 url(r'^login/$', LoginView.as_view(template_name = 'users\login.html'),
9 name='login'),
10 ]
(2)模板login.html:在users文件夹下新建一个templates目录,并在其中新建users文件夹,在其中新建login.html
1 {% extends 'learning_logs\base.html' %}
2 {% block content %}
3 {% if form.errors %}
4 <p>用户名或密码错误!</p>
5 {% endif %}
6 <form method="post" action="{% url 'users:login' %}">
7 {% csrf_token %}
8 {{ form.as_p }}
9 <button name="submit">登陆</button>
10 <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
11 </form>
12 {% endblock content %}
- 这个模板继承了base.html,确保登陆界面的外观和网站的其他页面相同。
- 我们要让登陆视图处理表单,因此将实参action设置为登陆页面的URL。
- 在10处,我们隐藏了一个表单元素next,其中的实参value告诉Django在用户登陆成功后将其重新定位到什么地方,这里是主页。
(3)链接到登陆页面
- 修改文件夹learning_log\learning_logs\templates\learning_logs中的base.html,添加到登陆页面的链接,让所有页面都包含它,用户已登陆时,我们不想显示这个链接,因此将它嵌套在一个{% if %}标签中。
1 <p>
2 <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
3 <a href="{% url 'learning_logs:topics' %}">Topics</a> -
4 {% if user.is_authenticated %}
5 Hello,{{ user.username }}.
6 {% else %}
7 <a href="{% url 'users:login' %}">登陆</a>
8 {% endif %}
9 </p>
10 {% block content %}{% endblock content %}
- 在Django身份验证系统中,每个模板都可以使用变量user,这个变量有一个属性is_authenticated:如果用户已登录,该属性将为True,则显示登陆成功,否则就显示一个登陆界面的链接。
先登陆http://127.0.0.1:8000/admin/,这里是以管理员身份登陆的,先注销
- 重新登陆http://127.0.0.1:8000
- 点击登陆
3、注销页面
(1)URL模式
#注销页面
url(r'^logout/$', views.logout_view,name='logout'),
(2)视图logout_view
from django.http.response import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth import logout
def logout_view(request):
'''注销用户'''
logout(request)
return HttpResponseRedirect(reverse('learning_logs:index'))
(3)链接到注销视图:在base.html中添加注销链接
--snip--
{% if user.is_authenticated %}
Hello,{{ user.username }}.
<a href="{% url 'users:logout' %}">注销</a>
{% else %}
<a href="{% url 'users:login' %}">登陆</a>
{% endif %}
--snip--
4、注册页面
(1)注册页面的URL模式
#注册页面
url(r'^register/$', views.register,name='register'),
(2)视图函数register( )
1 from django.shortcuts import render
2 from django.urls import reverse
3 from django.http.response import HttpResponseRedirect
4 from django.contrib.auth import logout,login,authenticate
5 from django.contrib.auth.forms import UserCreationForm
6 --snip--
7 def register(request):
8 '''注册新用户'''
9 if request.method != 'POST':
10 #显示空的注册表单
11 form = UserCreationForm()
12 else:
13 #处理填好的注册表单
14 form = UserCreationForm(data=request.POST)
15 if form.is_valid():
16 new_user = form.save()
17 #让用户自动登陆,再重定向到主页
18 authenticated_user = authenticate(username=new_user.username,
19 password=request.POST['password'])
20 login(request,authenticated_user)
21 return HttpResponseRedirect(reverse('learning_logs:index'))
22 context = {'form':form}
23 return render(request, r'users\register.html', context)
(3)注册模板
{% extends 'learning_logs\base.html' %}
{% block content %}
{% if form.errors %}
<p>用户名或密码错误!</p>
{% endif %}
<form method="post" action="{% url 'users:register' %}">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">注册</button>
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
</form>
{% endblock content %}
(4)链接到注册页面
--snip--
{% if user.is_authenticated %}
Hello,{{ user.username }}.
<a href="{% url 'users:logout' %}">注销</a>
{% else %}
<a href="{% url 'users:register' %}">注册</a>
<a href="{% url 'users:login' %}">登陆</a>
{% endif %}
--snip--
八、让用户拥有自己的数据
1、使用@login_required限制访问
- 我们将创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。
- 限制对topics页面的访问。在learning_log\learning_logs\views.py中添加如下代码:
1 from django.urls import reverse
2 from django.contrib.auth.decorators import login_required
3 from .models import Topic,Entry
4 --snip--
5 @login_required
6 def topics(request):
7 '''显示所有主题的函数'''
8 --snip--
- 导入函数login_required(),我们把该函数作为装饰器放在显示所有主题的函数前,每次先检查用户是否已经登录,只有登录时,Django才运行topics()。如果未登录,就重定向到登陆页面,为实现这种定向,需要修改settings.py,让Django知道去哪里查找登陆页面。在settings.py末尾添加如下代码:
--snip--
#我的设置
LOGIN_URL = '/%5Eusers/login/'
- 先查看自己的登陆界面的URL,我的登陆界面:http://127.0.0.1:8000/%5Eusers/login/,再设置LOGIN_URL。
- 我们可以在learning_log\learning_logs\views.py文件中除了index()外的每个视图都应用装饰器@login_required(),如果你在未登录状态下,尝试访问这些页面,将会被重定向到登陆页面。
2、将数据关联到用户
- 我们只需要将最高层的数据关联到用户,这样更底层的数据会自动关联到用户。
(1)修改模型Topic。打开models.py,编辑如下:
from django.db import models
from django.contrib.auth.models import User
class Topic(models.Model):
'''用户学习主题的类'''
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User)
--SNIP--
(2)确定当前有哪些用户
- 启动一个Django shell会话,查看已经创建的用户的ID。
(3)windows系统,ctrl+z,然后回车,退出shell会话,然后迁移数据库
- You are trying to .....这里Django指出你试图给既有模型Topic添加一个必不可少(不能为空)的字段,而该字段默认值为空,Django给我们提供两个选择,要么现在提供默认值,要么突出并在models.py中添加默认值,我选择了第一个选项,因此让我输入默认值。
- 为了将既有主题全部关联到超级用户,就是ID为1的用户,我输入了用户ID值1,Django使用这个值来迁移数据,并生成了迁移文件0003,它在模型Topic中添加字段owner。
- 现在执行迁移:
- 验证迁移是否符合预期:
- 如果你想重置数据库,可执行命令python manage.py flush,这样将得到一个全新的数据库,你必须重建超级用户,且原来的所有数据都将丢失。
(3)只允许用户访问自己的主题
'''显示所有主题的函数'''
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
- 用户登录后,request将有一个user属性,这个属性存储了有关该用户的信息,代码让Django只从数据库中获取owner属性为当前用户的Topic对象。
(4)保护用户的主题
1 --snip--
2 from django.http.response import HttpResponseRedirect,Http404
3 --snip--
4 @login_required
5 def topic(request,topic_id):
6 '''显示单个主题及其所有的条目'''
7 topic = Topic.objects.get(id=topic_id)
8 #确认请求的主题属于当前用户
9 if topic.owner != request.user:
10 raise Http404
- 导入异常Http404,如果请求的主题不属于当前用户,就引发该异常,让Django返回一个404错误页面。
(5)保护页面edit_entry:禁止用户输入类似http://127.0.0.1:8000/edit_entry/2/这样的URL来访问别人的条目。
--snip--
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
# 确认请求的主题属于当前用户
if topic.owner != request.user:
raise Http404
--snip--
(6)将新主题关联到当前用户。
- 当前用于添加新主题的页面存在问题,因为他没有将新主题关联到特定用户,如果你添加新主题,会看到错误IntegrityError,learning_logs_topic.user_id不能为空,意思是说创建新主题时,必须指定其owner字段的值。
--snip--
if form.is_valid():
new_topic = form.save(commit=False)
new_topic.owner = request.user
new_topic.save()
return HttpResponseRedirect(reverse('learning_logs:topics'))
--snip--
- 向函数save()传递实参commit=False,当你通过表单获取你的模型数据,但是需要给模型里null=False字段添加一些非表单的数据,该方法会非常有用。如果你指定commit=False,那么save方法不会理解将表单数据直接存储到数据库,而是给你返回一个当前对象,这时你可以添加表单以外的额外数据,再一起存储。这里我们先将新主题的owner属性设置为当前用户,再对刚才定义的主题实例调用save()。
九、设置应用程序样式
1、在活动的虚拟环境下,执行pip install django-bootstrap3
2、设置settings.py
- 我们需要让django-bootstrap3包含jQuery,这是一个JavaScript库,让你能够使用Bootstrap模板提供的一些交互元素。
--snip--
#我的应用程序
'learning_logs',
'users',
#第三方应用程序
'bootstrap3'
]
--snip--
#我的设置
LOGIN_URL = '/%5Eusers/login/'
#django-bootstrap3的设置
BOOTSTRAP3 = {
'include_jquery':True,
}
3、使用Bootstrap来设置项目‘学习笔记’的样式
- Bootstrap基本上就是一个大型的样式设置工具集,它还提供了大量的模板,对于初学者来说这些模板比各个样式设置工具用起来要方便的多。要查看Bootstrap提供的模板,可访问网站http://getbootstrap.com/,单击Examples,找到Navbars ,这里我们将使用模板Navbar static。它提供了简单的顶部导航条、页面标题和和放置页面内容的容器。模板如下:
(1)修改base.html:定义HTML头部
1 {% load bootstrap3 %}
2 <!DOCTYPE html>
3 <html lang="en">
4 <head>
5 <meta charset="UTF-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
7 <meta name="viewport" content="width=device-width,initial-scale=1">
8 <title>学习笔记</title>
9 {% bootstrap_css %}
10 {% bootstrap_javascript %}
11 </head>
- 在1处,加载了django-bootstrap3中的模板标签集,3处将这个文件声明为使用英语编写的HTML文档,HTML分为两个主要部分:头部(head)和主体(body),这里头部只包含了一个标题和其他正确显示页面所需的信息。
- 在9处,使用了django-bootstrap3的一个自定义模板标签,它将让Django包含所有的Bootstrap样式文件,10处的标签启用接下来可能在页面中使用的所有交互式行为,例如可折叠的导航栏。
(2)定义导航栏
1 --snip--
2 <body>
3 <!-- Static navbar -->
4 <nav class="navbar navbar-dafault navbar-static-top">
5 <div class="container">
6 <div class="navbar-header">
7 <button type="button" class="navbar-toggle collapsed"
8 data-toggle="collapse" data-target="#navbar"
9 aria-expanded="false" aria-controls="navbar">
10 </button>
11 <a class="navbar-brand" href="{% url 'learning_logs:index' %}">
12 Learning Log</a>
13 </div>
14 <div id="navbar" class="navbar-collapse collapse" >
15 <ul class="nav navbar-nav">
16 <li><a href="{% url 'learning_logs:topics' %}">Topics</a></li>
17 </ul>
18 <ul class="nav navbar-nav navbar-right">
19 {% if user.is_authenticated %}
20 <li><a>Hello,{{ user.username }}.</a></li>
21 <li><a href="{% url 'users:logout' %}">注销</a></li>
22 {% else %}
23 <li><a href="{% url 'users:register' %}">注册</a></li>
24 <li><a href="{% url 'users:login' %}">登陆</a></li>
25 {% endif %}
26 </ul>
27 </div><!--/.nav-collapse -->
28 </div>
29 </nav>
- 主体包括用户在页面上看到的内容。4处是一个<nav>元素,表示页面的导航链接部分,对于这个元素内的所有内容,都将根据选择器(selector)navbar、navbar-dafault和 navbar-static-top定义的Bootstrap样式规则来设置样式,选择器决定了特定样式规则将应用于页面上的哪些元素。
- 在7处,模板定义了一个按钮,它将在浏览器窗口太窄、无法水平显示整个导航栏时显示出来,如果用户单击这个按钮,将出现一个下拉列表,其中包括所有的导航元素,在用户缩小浏览器窗口或在较小屏幕上显示网站时,collapse将会使导航栏折叠起来。
- 在11处,在导航栏的最左边显示项目名,并将其设置为到主页的链接,因为它将出现在这个项目的每个页面中。
- 在14处,定义了一组能让用户在网站中导航的链接。导航栏其实就是一个以<ul>打头的列表,其中每个链接都是一个列表项(<li>)。要插入更多的链接,可插入更多使用下面结构的行:
<li><a href="{% url 'learning_logs:topics' %}">Title</a></li>
- 在18处我们插入了另一个导航链接列表,这里使用的选择器为navbar-right设置一组链接的样式,使其出现在导航栏右边——登陆链接和注册链接通常出现在这里,
(3)定义页面的主要部分
1 --snip--
2 </nav>
3 <div class="container">
4 <div class="page-header">
5 {% block header %}{% endblock header %}
6 </div>
7 <div>
8 {% block content %}{% endblock content %}
9 </div>
10 </div><!--/.nav-collapse -->
11 </body>
12 </html>
- 3处是一个div起始标签,其class属性为container。div是网页的一部分,可用于任何目的,并可通过边框、元素周围的空间(外边距)、内容和边框之间的边距(内边距)、背景色和其他样式规则来设置其样式。这个div是一个容器,其中包含连个元素:header的块和content块,header块的内容告诉用户页面包含哪些信息以及用户可在页面上执行哪些操作;其class属性值page-header将一系列样式应用于这个块;content块是一个独立的div,未使用class属性指定样式。
- 重新加载学习笔记主页:
4、使用jumbotron设置主页的样式
- 下面来使用新定义的header块以及另个名为jumbotron的Bootstrap元素修改主页。jumbotron元素是一个大框,相比于页面的其他部分显的鹤立鸡群,你想在其中包含什么东西都可以;它通常用于在主页中呈现项目的简要描述,还可以修改主页显示的消息,index.html代码修改如下:
1 {% extends 'learning_logs\base.html' %}
2 {% block header %}
3 <div class="jumbotron">
4 <h1>Track your learning.</h1>
5 </div>
6 {% endblock header %}
7 {% block content %}
8 <h2>
9 <a href="{% url 'users:register' %}">Register an account</a>to make
10 your own Learning Log,and list the topics you're learning about.
11 </h2>
12 <h2>
13 Whenever you learn something new about a topic,make an entry
14 summarizing what you've learned.
15 </h2>
16 {% endblock content %}
- 在2处,定义header包含的内容,在一个jumbotron元素中(见3),放置一条简短的标语Track your learning,让首次访问者大致知道‘学习笔记’是做什么的。
- 在7和16之间,通过添加一些文本,做了更详细的说明。邀请用户建立账户,并描述了用户可以进行的两种操作:添加新主题以及在主题中添加条目。刷新浏览器看到下面的页面:
5、设置登录界面的样式
- 修改login.html
1 {% extends 'learning_logs\base.html' %}
2 {% load bootstrap3 %}
3 {% block header %}
4 <h2>Log in to your account.</h2>
5 {% endblock header %}
6 {% block content %}
7 <form method="post" action="{% url 'users:login' %}" class="form">
8 {% csrf_token %}
9 {% bootstrap_form form %}
10 {% buttons %}
11 <button name="submit" class="btn btn-primary">登录</button>
12 {% endbuttons %}
13 <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
14 </form>
15 {% endblock content %}
- 在2处首先导入了bootstrap3模板标签,3处定义了header块,描述了这个页面是做什么的,我删除了{% if form.errors %}代码块,因为django-bootstrap3会自动管理表单错误。
- 7处添加了class="form",然后使用模板标签{% bootstrap_form form %}来显示表单,这个标签替换了之前使用的{{ form.as_p }};模板标签{% bootstrap_form form %}将Bootstrap样式规则应用于各个表单元素,10处是bootstrap3起始标签模板{% buttons %},它将Bootstrap样式应用于按钮。现在打开登陆页面:
6、设置new_topic页面的样式
- 修改new_topic.html
1 {% extends 'learning_logs\base.html' %}
2 {% load bootstrap3 %}
3 {% block header %}
4 <h2>Add a new topic.</h2>
5 {% endblock header %}
6
7 {% block content %}
8 <p>Add a new topic:</p>
9 <form action="{% url 'learning_logs:new_topic' %}" method="post"
10 class="form">
11 {% csrf_token %}
12 {% bootstrap_form form %}
13 {% buttons %}
14 <button name="submit" class="btn btn-primary">add topic</button>
15 {% endbuttons %}
16 </form>
17 {% endblock content %}
7、设置topics页面的样式
- 修改topics.html
1 {% extends 'learning_logs\base.html' %}
2 {% block header %}
3 <h1>Topics.</h1>
4 {% endblock header %}
5 {% block content %}
6 <ul>
7 {% for topic in topics %}
8 <li>
9 <h3>
10 <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
11 </h3>
12 </li>
13 {% empty %}
14 <li>No topics have been added yet.</li>
15 {% endfor %}
16 </ul>
17 <h3><a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a></h3>
18 {% endblock content %}
- 为设置每个主题的样式,我将它们都设置为<h3>元素,让他们在页面上显的大些;在显示主题的页面,我们依然对显示添加新主题的链接做了同样的处理(见17)。
8、设置topic页面的样式
- 修改topic.html,topic页面包含的内容比其他大部分页面都要多,因此要做的样式设置工作也多。我将使用Bootstrap面板(panel)来突出每个条目。面板是一个带预定义样式的div,非常适合用于显示主题的条目:
1 {% extends 'learning_logs\base.html' %}
2 {% block header %}
3 <h2>{{ topic }}</h2>
4 {% endblock header %}
5 {% block content %}
6 <p>
7 <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
8 </p>
9 {% for entry in entries %}
10 <div class="panel panel-default">
11 <div class="panel-heading">
12 <h3>
13 {{ entry.date_added|date:'M d,Y H:i' }}
14 <small>
15 <a href="{% url 'learning_logs:edit_entry' entry.id %}">
16 edit entry</a>
17 </small>
18 </h3>
19 </div>
20 <div class="panel-body">
21 {{ entry.text|linebreaks }}
22 </div>
23 </div><!-- panel -->
24 {% empty %}
25 There are no entries for this topic yet.
26 {% endfor %}
27 {% endblock content %}
- 刷新浏览器:
注意:要使用其他Bootstrap模板,可采用类似的流程:将这个模板复制到base.html中,并修改包含实际内容的元素,以使用该模板来显示项目的信息,然后,使用Bootstrap的样式设置工具来设置这个各个页面中内容的样式。
十、部署“学习笔记”
1、建立Heroku账户
- 访问https://www.heroku.com/,注册账户,免费使用服务有一些限制,比如可部署的应用程序数量和用户访问应用程序的频率。
- 注册时需要使用代理才能显示下面的验证码,邮箱要使用外国邮箱,如@gmail注册之后需要到邮箱验证,才能设置密码
2、安装Heroku Toolbelt
https://devcenter.heroku.com/articles/heroku-cli#download-and-install
3、安装必要的包
1 (ll_env) E:\Python\learning_log>pip install dj-database-url
2 (ll_env) E:\Python\learning_log>pip install dj-static
3 (ll_env) E:\Python\learning_log>pip install static3
4 (ll_env) E:\Python\learning_log>pip install gunicorn
- 第一个包帮助Django与Heroku使用的数据库进行通信,第二个和第三个帮助Django正确的管理静态文件,第四个是一个服务器软件,能够在在线环境中支持应用程序提供的服务。(静态文件包括样式规则和JavaScript文件)
4、创建包含包列表的文件requirements.txt
- Heroku需要知道我们的项目依赖于哪些包,因此需要使用pip来生成一个文件,其中包含了这些包。
(ll_env) E:\Python\learning_log>pip freeze > requirements.txt
- freeze让项目中当前安装的所有包都的名称都写入到文件requirements.txt中。
- 我们部署“学习笔记”时,heroku会安装上述所有的包,从而创建一个环境,其中包含我们在本地使用的所有包。
- 我们需要在txt中添加一个包名称:psycopg2>=2.7.5,>表示如果有更高版本,heroku会自动下载高版本,它帮助heroku管理活动数据库。
5、指定Python版本
- 在虚拟环境中输入python,查看当前的默认Python版本
- 然后在manage.py所在文件夹,新建一个runtime.txt的文件,写入python-3.7.1
6、修改settings.py,在文件末尾添加一段:
1 #Heroku设置
2 if os.getcwd() == '/app':
3 import dj_database_url
4 DATABASES = {
5 'default':dj_database_url.config(default='postgress://localhost')
6 }
7 #让request.is_secure()承认X-FORWARDED_PROTO头
8 SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
9 #支持所有的主机头(host header)
10 ALLOWED_HOSTS = ['*']
11 # 静态资产配置
12 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
13 STATIC_ROOT = 'staticfiles'
14 STATICFILES_DIRS = (
15 os.path.join(BASE_DIR,'static'),
16 )
- 2处使用os.getcwd()获取当前的工作目录(当前运行的文件所在的目录)。在Heroku部署中,这个目录总是'/app',这个if语句确保仅当部署到heroku时才运行这个代码块。这种结构让我们能够将同一设置文件用于本地开发环境和在线服务器。
- 3处导入dj_database_url,用于配置服务器。Heroku使用PostgreSQL(也叫Postgres)——一种比SQLite更高级的数据库。这些设置对项目进行配置,使其在Heroku上使用PostgreSQL数据库;其他设置分别如下:支持HTTPS请求;让Django能够使用Heroku的URL来提供项目提供的服务;设置项目,使其能够在Heroku上正确的提供静态文件。
7、创建启动进程的Procfile
- Procfile告诉Heroku该启动哪些进程。这个文件只有一行内容,将其命名为Procfile(其中P为大写),不指定文件扩展名,并保存到manage.py所在文件夹。
web:gunicorn learning_log.wsgi --log-file -
- 这行代码让Heroku将gunicorn用作服务器,并使用learning_log\wsgi.py文件中的设置来启动应用程序,标志log-file告诉Heroku应将哪些类型的事件写入日志。
8、修改文件wsgi.py
1 --snip--
2 import os
3
4 from django.core.wsgi import get_wsgi_application
5 from dj_static import Cling
6 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'learning_log.settings')
7 application = Cling(get_wsgi_application())
- 这里导入了帮助正确的提供静态文件的Cling,并使用它来启动应用程序。这些代码在本地也适用,因此无需将其放在if语句中。
9、创建用于存储静态文件的目录
- 在Heroku上,Django搜集所有的静态文件,并将他们放在一个地方,以便高效的管理它们。在learning_log\learning_log文件夹中新建一个名为static的文件夹,并在其中创建一个占位文件,因为项目被推送到Heroku时,它将不会包含原来为空的文件。在learning_log\learning_log\static中新建一个名为placeholder.text的文件,里面随便添加几句话,例如说明添加这个文件的原因:
10、在本地使用gunicorn服务器
- 如果你使用的是Linux或OS系统,可在部署到Heroku之前,在本地尝试使用gunicorn服务器,在活动的虚拟环境中,执行命令heroku local以启动Procfile指定的进程;
为停止进程,请按crtl+c;gunicorn不能再Windows上运行,直接跳过这一步。
11、使用Git跟踪项目文件
- Git是一个版本控制程序,每次成功实现新功能后都拍摄项目代码的快照,无论出现了什么问题,你都可以轻松的恢复到最后一个可行的快照,每个快照都被称为提交。
(1)查看Git版本
(2)配置Git
- Git跟踪谁修改了项目,即便又一个人开发项目时也是如此。为进行跟踪,Git需要知道你的用户名和邮箱。由于练习,可以伪造一个:
(3)忽略文件
- 无需跟踪没一个文件,因此需要忽略一些文件。在manage.py所在文件夹新建名为.gitignore的文件。文件以点打头,不包含扩展名。方法如下:在文件夹中右键打开Git bash here,然后输入touch .gitignore
- 用记事本打开写入一下内容:
ll_env\
__pycache__\
*.sqlite3
- 让Git忽略ll_env,因为我们随时可以自动重新创建它;__pycache__包含Django运行.py文件时自动创建的.pyc文件;不跟踪对本地数据库的修改,因为如果你在本地服务器上使用的是SQLite,当你推送到服务器上时,可能会不小心用本地测试数据库覆盖在线数据库。
(4)提交项目
- 首先修改配置,全局修改禁止自动转换CRLF(Windows换行符),因为在Linux系统里换行符为LF,不做这一步,执行git add .之后会报错。
- 我们需要为学习笔记初始化一个Git仓库,输入git init
- 给仓库添加文件,输入git add . 不要忘记最后面的句点,这一步需要执行个一两分钟,耐心等待。
- git commit -am commit message,其中的标志-a让Git在提交中包含所有修改过的文件,-m让Git记录一条日志消息。耐心等待。
- git status ,输出表明结果表明当前位于分支master中,而工作目录是干净的(clean)。
12、推送到Heroku
- 前面的一切终于为项目推送做好了准备,在活动的虚拟环境中依次执行如下命令:
- heroku login:登陆heroku
- heroku create:让heroku创建一个空仓库,heroku生成的项目名由两个单词和一个数字组成
- git push heroku master:它让Git将项目的分支master推送到heroku刚才创建的仓库中,这里列出了用于访问这个项目的URL。
- 核实是否正确启动服务器进程,执行命令heroku ps
- 使用heroku open在浏览器中打开这个应用程序,你将看到学习笔记的主页,但是你还无法使用,因为还没有建立数据库。
13、在Heroku上建立数据库
- 为建立在线数据库,在活动的虚拟环境中执行命令:heroku run python manage.py migrate
14、改进Heroku部署
(1)在Heroku上创建超级用户,输入命令:heroku run bash打开Bash终端会话,使用Bash终端创建超级用户,以便能够访问在线应用程序的管理网站。输入命令python manage.py createsuperuser,和之前一样输入相关信息,然后exit返回到本地系统的终端会话。现在你可以在应用程序的URL末尾添加/admin/来登陆管理网站了。如果已经有其他用户在使用这个项目,你可以访问他们的所有数据。
(2)在heroku上创建对用户有好的URL。使用命令来重命名应用程序:heroku apps:rename charliedaifu-learning-log
15、确保项目的安全
- 这个项目存在一个严重的安全问题:settings.py包含设置DEBUG=True,它在发生错误时显示调试信息,如果项目部署到服务器上之后依然保留这个设置,将给攻击者提供大量的可利用信息,我们需确保任何人都无法看到这些信息,也不能冒充项目托管网站来重定向请求。下面来修改settings.py:
#让request.is_secure()承认X-FORWARDED_PROTO头
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
#只允许heroku托管这个项目
ALLOWED_HOSTS = ['charliedaifu-learning-log.herokuapp.ocm']
DEBUG = False
# 静态资产配置
16、提交并推送修改
(1)现将对settings.py所做的修改提交到仓库
(2)将修改后的仓库提交到Heroku:git push heroku master
17、创建自定义错误页面
- 404错误通常意味着你的Django代码是正确的,但请求的对象不存在;500错误代表你的代码有问题。当前,这两种情况下,都返回通用的错误页面,我们可以编写外观与学习笔记一致的404和500错误页面模板,必须放在根模板目录中。
(1)创建自定义模板
- 在learning_log\learning_log文件夹中新建一个文件夹名叫templates,在其中新建一个名为404.html的文件,在其中输入如下内容:
{% extends 'learning_logs\base.html' %}
{% block header %}
<h2>The item you requested is not availible.</h2>
{% endblock header %}
- 新建505.html
{% extends 'learning_logs\base.html' %}
{% block header %}
<h2>There has been an internal error.</h2>
{% endblock header %}
- 修改settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,r'learning_log\templates')],
'APP_DIRS': True,
--snip--
(2)在本地查看错误页面,将ALLOWED_HOSTS指定一个主机,请求一个不属于你的主题,查看错误页面,请求保存在的URL,查看500错误页面。确定你修改的是本地环境的setting部分,而不是Heroku部分,查看过后,将DEBUG重新设置为True,以便进一步开发。
SECRET_KEY = '9(m*h6v7jouxq8a526y*r@4fkdor-381!(tm0@0#oy)ryod=b6'
# SECURITY WARNING: don't run with debug turned on in production!
#安全警告:不要在在线环境中启用调试
DEBUG = False
ALLOWED_HOSTS = ['localhost']
(3)将修改推送到Heroku
- git add .
- git commit -am "Added custom 404 and 500 error pages."
- git push heroku master
(4)使用方法get_object_or_404()
该函数尝试从数据库获取请求的对象,如果这个对象不存在,就引发404异常,我们在view.py中导入这个函数,并用它替换函数get():
topic = get_object_or_404(Topic,id=topic_id)
18、将项目从Heroku删除
第一种方法:登陆网站https://heroku.com/,点击你的项目名称,向下滚动,点击delete app,系统会提示你输入完整的项目名称。
第二种方法:在活动的虚拟环境中输入命令:heroku apps:destroy --app appname,其中appname是要删除的项目名。