Python编程:用户账户
Web应用程序的核心是让任何用户都能够注册账户并能够使用它,不管用户身处何方。让用户能够添加主题和条目,以及编辑既有的条目。目标—实现一个用户身份验证系统。
1. 让用户能够输入数据
建立用于创建用户账户的身份验证系统之前,先来添加一个页面,让用户能够输入数据。让用户能够添加新主题、添加新条目以及编辑既有条目。使用Django的表单工具来创建让用户能够输入数据的页面。
1.1 添加新主题
工作流程是:定义一个URL------编写一个视图函数------编写一个模板。
1. 用于添加新主题的表单
在Django中,创建表单的最简单方式是使用ModelForm,创建一个名为forms.py的文件,将其存储到models.py所在的目录中,并在其中编写第一个表单:
forms.py
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
model = Topic # 根据模型Topic创建一个表单
fields = ['text']
labels = {
'text': ''}
2. URL模式new_topic
这个新网页的URL应简短而具有描述性,因此当用户要添加新主题时,切换到http://localhost:8000/new_topic/。将其添加到learning_logs/urls.py中。
urls.py
"""定义learning_logs的URL模式"""
from django.conf.urls import url
from . import views
urlpatterns = [
# 主页
url(r'^$', views.index, name='index'),
# 显示所有的主题
url(r'^topics/$', views.topics, name='topics'),
# 显示特定主题
url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# 用户添加新主题的网页
url(r'^new_topic/$', views.new_topic, name='new_topic'),
]
这个URL模式将请求交给视图函数new_topic()。
3. 视图函数new_topic()
函数new_topic()需要处理两种情形:刚进入new_topic网页(在这种情况下, 它应显示一个空表单);对提交的表单数据进行处理,并将用户重定向到网页topics:
views.py
from django.shortcuts import render
from .models import Topic
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from . forms import TopicForm
# 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)
def new_topic(request):
"""添加新主题"""
if request.method != 'POST':
# 未提交数据:创建一个新表单
form = TopicForm()
else:
# POST提交的数据,对数据进行处理
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topics'))
context = {
'form':form}
return render(request, 'learning_logs/new_topic.html', context)
导入HttpResponseRedirect类,用户提交主题后,将使用这个类将用户重定向到网页topics。函数reverse()根据指定的URL模型确定URL,这意味着Django将在页面被请求时生成URL。
4. GET请求和POST请求
创建Web应用程序时,将用到两种主要请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户需要通过表单提交信息时,通常使用POST请求。处理所有表单时,将指定使用POST方法。
5. 模板new_topic
创建新模板new_topic.html, 用于显示刚刚创建的表单:
new_topic.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>new_topic</title>
</head>
<body>
{
% 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> #Django不会为表单创建提交按钮,因此我们定义了一个这样的按 钮。
</form>
{
% endblock content %}}
</body>
</html>
这个模板继承了base.html,因此其基本结构与项目“学习笔记”的其他页面相同。Django使用模板标签{% csrf_token %}来防止攻击者利用表单来获对服务器未经授权的访问(这种攻击称为跨站请求伪造)。
6. 链接到页面new_topic
在页面topics中添加一个到页面new_topic的链接:
topics.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Topics</title>
</head>
<body>
{
% 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 }}</a></li>
{
% empty %}
<li>No topics have been added yet.</li>
{
% endfor %}
</ul>
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
{
% endblock content %}
</body>
</html>
这个链接放在了既有主题列表的后面,如下图所示:
1.2 添加新条目
现在可以添加新主题了,还需要添加新条目。再次定义URL,编写视图函数和模板,并链接到添加新条目的网页。
1. 用于添加新条目的表单
创建一个与模型Entry相关联的表单,但这个表单的定制程度比TopicForm要高些:
forms.py
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {
'text': ''}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {
'text':''} # 给字段'text'指定了空标签
widgets = {
'text': forms.Textarea(attrs={
'cols': 80})} # widget(小部件)是一个HTML表单元 素,如单行文本框、多行文本区域或下拉 列表。
对照之前forms.py中的import语句,除导入Topic外,还导入了Entry。新类EntryForm继承了forms.ModelForm。
2. URL模式new_entry
urls.py
"""定义learning_logs的URL模式"""
from django.conf.urls import url
from . import views
urlpatterns = [
# 主页
url(r'^$', views.index, name='index'),
# 显示所有的主题
url(r'^topics/$', views.topics, name='topics'),
# 显示特定主题
url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# 用户添加新主题的网页
url(r'^new_topic/$', views.new_topic, name='new_topic'),
# 用于添加新条目的页面
url(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
]
这个URL模式与形式为http://localhost:8000/new_entry/id/的URL匹配,其中id是一个与主题ID匹配的数字。
3. 视图函数new_entry()
视图函数new_entry()与函数new_topic()很像:
导入EntryForm,new_entry()的定义包含形参topic_id,用于存储从URL中获得得值。
views.py
from __future__ import unicode_literals
from django.shortcuts import render
from .models import Topic, Entry
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from . forms import TopicForm, EntryForm
# 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)
def new_topic(request):
"""添加新主题"""
if request.method != 'POST':
# 未提交数据:创建一个新表单
form = TopicForm()
else:
# POST提交的数据,对数据进行处理
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topics'))
context = {
'form':form}
return render(request, 'learning_logs/new_topic.html', context)
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 HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
context = {
'topic':topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
4. 模板new_entry
模板new_entry类似于模板new_topic:
new_entry.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>new-entry</title>
</head>
<body>
{
% 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 %}}
</body>
</html>
5.链接到页面new_entry
在显示特定主题的页面中添加到页面new_entry的链接:
topic.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>topic</title>
</head>
<body>
{
% extends 'learning_logs/base.html' %}
{
% block content %}
<p>Topic: {
{
topic }}</p>
<p>Entries:</p>
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
</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 %}
</body>
</html>
在显示条目前添加链接,因为在这种页面中,执行最常见的操作是添加新条目。下图显示了页面new_entry。现在用户可以添加新主题,还可以在每个主题中添加任意数量的条目。尝试使用一下页面new_entry。
1.3 编辑条目
下面创建一个页面,让用户能够编辑既有的条目。
1. URL模式edit_entry
这个页面的URL需要传递要编辑的条目ID。修改后的learning_logs/urls.py如下:
urls.py
"""定义learning_logs的URL模式"""
from django.conf.urls import url
from . import views
urlpatterns = [
# 主页
url(r'^$', views.index, name='index'),
# 显示所有的主题
url(r'^topics/$', views.topics, name='topics'),
# 显示特定主题
url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# 用户添加新主题的网页
url(r'^new_topic/$', views.new_topic, name='new_topic'),
# 用于添加新条目的页面
url(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
# 用于编辑条目的页面
url(r'^edit_entry/(?P<topic_id>\d+)/$', views.edit_entry, name='edit_entry'),
]
2. 视图函数edit_entry()
页面edit_entry收到GET请求时,edit_entry()将返回一个表单,让用户能够对条目进行编辑。该页面收到POST请求(条目文本经过修订)时,它将修改后的文本保存到数据库中:
views.py
from django.shortcuts import render
from .models import Topic, Entry
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from . forms import TopicForm, EntryForm
# 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)
def new_topic(request):
"""添加新主题"""
if request.method != 'POST':
# 未提交数据:创建一个新表单
form = TopicForm()
else:
# POST提交的数据,对数据进行处理
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topics'))
context = {
'form':form}
return render(request, 'learning_logs/new_topic.html', context)
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 HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
context = {
'topic':topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
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 HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
context = {
'entry':entry, 'topic':topic, 'form':form}
return render(request, 'learning_logs/edit_entry.html', context)
3. 模板edit_entry
下面是模板edit_entry.html,它与模板new_entry.html类似:
edit_entry.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>edit-entry</title>
</head>
<body>
{
% 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 %}
</body>
</html>
4. 链接到页面edit_entry
在显示特定主题的页面中,需要给每个条目添加到页面edit_entry的链接:
topic.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>topic</title>
</head>
<body>
{
% extends 'learning_logs/base.html' %}
{
% block content %}
<p>Topic: {
{
topic }}</p>
<p>Entries:</p>
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
</p>
<ul>
{
% 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>
{
% empty %}
<li>There are no entries for this topic yet.</li>
{
% endfor %}
</ul>
{
% endblock content %}
</body>
</html>
编辑链接放在每个条目的日期和文本后面。如下图所示:
- 创建用户账户
建立一个用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。创建一个新的应用程序,其中包含与处理用户账户相关的所有功能。
2.1 应用程序users
使用命令startapp来创建一个名为users的应用程序:
(11_env) (base) liuxiaowei@MacBook-Air learning_log % python manage.py startapp users
(11_env) (base) liuxiaowei@MacBook-Air learning_log % ls
11_env init.py db.sqlite3 learning_log learning_logs manage.py users
(11_env) (base) liuxiaowei@MacBook-Air learning_log % ls users
init.py admin.py apps.py migrations models.py tests.py views.py
这个命令新建一个名为users的目录,结构与应用程序learning_logs相同。
1. 将应用程序users添加到settings.py中
在settings.py中,将这个新的应用程序添加到INSTALLED_APPS中, 如下所示:
settings.py
"""
Django settings for learning_log project.
Generated by 'django-admin startproject' using Django 1.11.
For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ')-lv47!$0c!j5-7th)l_-osv&ugkws7!6pv&+oe_0=p_xo(g@_'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# My app
'learning_logs',
'users',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'learning_log.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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',
],
},
},
]
WSGI_APPLICATION = 'learning_log.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/'
2. 包含应用程序users的URL
需要修改项目根目录中的urls.py,使其包含我们将为应用程序users定义的URL:
urls.py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^users/', include('users.urls', namespace='users')),
url(r'', include('learning_logs.urls', namespace='learning_logs')),
]
接下文。Python编程--Django入门:用户账户(二)https://developer.aliyun.com/article/1618615