Python Crash Course读书笔记 - 第19章:USER ACCOUNTS
允许用户输入数据
目前用户数据Topic和Entry都是通过管理站点输入的,我们希望用户可以新增和编辑数据。
允许用户输入和提交数据的Web页面称为form,在Django中可使用ModelForm。
在应用目录下与models.py
相同目录创建文件forms.py
:
from django import formsfrom .models import Topic, Entryclass TopicForm(forms.ModelForm):class Meta:model = Topicfields = ['text']labels = {'text': ''}
其中,class Meta是嵌套类或inner class。
然后修改应用目录下的urls.py
:
"""Defines URL patterns for learning_logs."""from django.urls import pathfrom . import viewsapp_name = 'learning_logs'
urlpatterns = [# Home pagepath('', views.index, name='index'),path('topics/', views.topics, name='topics'),path('topics/<int:topic_id>/', views.topic, name='topic'),path('new_topic/', views.new_topic, name='new_topic'),
]
URL定义完,接着定义view,如urls.py
中代码,就是定义函数new_topic
。
修改应用目录下的views.py
如下:
from django.shortcuts import render
from .models import Topic
from .forms import TopicForm# Create your views here.
def index(request):"""The home page for Learning Log."""return render(request, 'learning_logs/index.html')def topics(request):"""Show all topics."""topics = Topic.objects.order_by('date_added')context = {'topics': topics}return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id):"""Show a single topic and all its entries."""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):"""Add a new topic."""if request.method != 'POST':# No data submitted; create a blank form.form = TopicForm()else:# POST data submitted; process data.form = TopicForm(data=request.POST)if form.is_valid():form.save()return redirect('learning_logs:topics')# Display a blank or invalid form.context = {'form': form}return render(request, 'learning_logs/new_topic.html', context)
view定义完,接着定义模板。在模板目录下新增文件new_topic.html
:
{% 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 %}
以上代码中,csrf_token
用以防止CSRF攻击。form.as_p
用以显示form。
然后我们添加链接,修改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 }}</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 %}
测试页面http://localhost:8000/new_topic/
如下:
接下来要要允许用户添加entry。
首先定义entry form,修改forms.py
如下:
from django import formsfrom .models import Topic, Entryclass TopicForm(forms.ModelForm):class Meta:model = Topicfields = ['text']labels = {'text': ''}class EntryForm(forms.ModelForm):class Meta:model = Entryfields = ['text']labels = {'text': 'Entry:'}widgets = {'text': forms.Textarea(attrs={'cols': 80})}
接下来为entry定义URL。修改urls.py
如下:
"""Defines URL patterns for learning_logs."""from django.urls import pathfrom . import viewsapp_name = 'learning_logs'
urlpatterns = [# Home pagepath('', views.index, name='index'),path('topics/', views.topics, name='topics'),path('topics/<int:topic_id>/', views.topic, name='topic'),path('new_topic/', views.new_topic, name='new_topic'), path('new_entry/<int:topic_id>/', views.new_entry, name='new_entry'),
]
为entry定义view,修改views.py
如下:
from django.shortcuts import render, redirectfrom .models import Topic, Entry
from .forms import TopicForm, EntryFormdef index(request):"""The home page for Learning Log."""return render(request, 'learning_logs/index.html')def topics(request):"""Show all topics."""topics = Topic.objects.order_by('date_added')context = {'topics': topics}return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id):"""Show a single topic and all its entries."""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):"""Add a new topic."""if request.method != 'POST':# No data submitted; create a blank form.form = TopicForm()else:# POST data submitted; process data.form = TopicForm(data=request.POST)if form.is_valid():form.save()return redirect('learning_logs:topics')# Display a blank or invalid form.context = {'form': form}return render(request, 'learning_logs/new_topic.html', context)def new_entry(request, topic_id):"""Add a new entry for a particular topic."""topic = Topic.objects.get(id=topic_id)if request.method != 'POST':# No data submitted; create a blank form.form = EntryForm()else:# POST data submitted; process data.form = EntryForm(data=request.POST)if form.is_valid():new_entry = form.save(commit=False)new_entry.topic = topicnew_entry.save()return redirect('learning_logs:topic', topic_id=topic_id)# Display a blank or invalid form.context = {'topic': topic, 'form': form}return render(request, 'learning_logs/new_entry.html', context)
接下来为new_entry
定义模板。添加文件new_entry.html
如下:
{% 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 %}
最后链接这个页面,修改topic.html
如下:
{% 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 %}
测试页面如下:
本节最后要允许用户编辑entry。
首先修改urls.py
如下:
$ cat urls.py
"""Defines URL patterns for learning_logs."""from django.urls import pathfrom . import viewsapp_name = 'learning_logs'
urlpatterns = [# Home pagepath('', views.index, name='index'),path('topics/', views.topics, name='topics'),path('topics/<int:topic_id>/', views.topic, name='topic'),path('new_topic/', views.new_topic, name='new_topic'), path('new_entry/<int:topic_id>/', views.new_entry, name='new_entry'),path('edit_entry/<int:entry_id>/', views.edit_entry, name='edit_entry'),
]
为编辑entry定义view,修改views.py
如下:
from django.shortcuts import render, redirectfrom .models import Topic, Entry
from .forms import TopicForm, EntryFormdef index(request):"""The home page for Learning Log."""return render(request, 'learning_logs/index.html')def topics(request):"""Show all topics."""topics = Topic.objects.order_by('date_added')context = {'topics': topics}return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id):"""Show a single topic and all its entries."""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):"""Add a new topic."""if request.method != 'POST':# No data submitted; create a blank form.form = TopicForm()else:# POST data submitted; process data.form = TopicForm(data=request.POST)if form.is_valid():form.save()return redirect('learning_logs:topics')# Display a blank or invalid form.context = {'form': form}return render(request, 'learning_logs/new_topic.html', context)def new_entry(request, topic_id):"""Add a new entry for a particular topic."""topic = Topic.objects.get(id=topic_id)if request.method != 'POST':# No data submitted; create a blank form.form = EntryForm()else:# POST data submitted; process data.form = EntryForm(data=request.POST)if form.is_valid():new_entry = form.save(commit=False)new_entry.topic = topicnew_entry.save()return redirect('learning_logs:topic', topic_id=topic_id)# Display a blank or invalid form.context = {'topic': topic, 'form': form}return render(request, 'learning_logs/new_entry.html', context)def edit_entry(request, entry_id):"""Edit an existing entry."""entry = Entry.objects.get(id=entry_id)topic = entry.topicif request.method != 'POST':# Initial request; pre-fill form with the current entry.form = EntryForm(instance=entry)else:# POST data submitted; process data.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)
接下来定义模板,新建edit_entry.html
如下,注意其和new_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 %}
链接此页面,编辑文件topic.html
如下:
{% 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 %}
测试页面如下:
设置用户账户
本节设置用户注册和认证,会借助于Django的认证系统,首先新建一个应用。
# 启动虚拟环境
$ source ll_env/bin/activate
# 进入项目目录
(ll_env) $ cd learning_log/
(ll_env) $ python manage.py startapp users
(ll_env) $ ls
db.sqlite3 learning_log learning_logs ll_env manage.py users
将新应用加入settings.py
:
...
INSTALLED_APPS = ['learning_logs','users',
...
然后修改项目目录下的urls.py
:
from django.contrib import admin
from django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('', include('learning_logs.urls')),path('users/', include('users.urls')),]
在learning_log/users/
目录下新建文件urls.py
:
"""Defines URL patterns for users"""from django.urls import path, includefrom . import viewsapp_name = 'users'
urlpatterns = [# Include default auth urls.path('', include('django.contrib.auth.urls')),
]
我们看到写法有些不通过,include
表示使用了Django默认的认证URL,即http://localhost:8000/users/login/
。这个URL会导向默认的view,默认的view会在registration
目录下寻找模板。接下来定义新建一登录模板。
(ll_env) $ mkdir -p templates/registration
(ll_env) $ cd templates/registration
(ll_env) $ cat 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 %}
这个模板的意思是登录成功就导向主页,否则报错。
然后链接此页面。修改base.html
如下:
<p><a href="{% url 'learning_logs:index' %}">Learning Log</a> -<a href="{% url 'learning_logs:topics' %}">Topics</a> -{% if user.is_authenticated %}Hello, {{ user.username }}.{% else %}<a href="{% url 'users:register' %}">Register</a> -<a href="{% url 'users:login' %}">Log in</a>{% endif %}
</p>{% block content %}{% endblock content %}
这时可以用已有用户ll_admin测试一下:
接下来定义注销页面。修改base.html
如下:
...
{% if user.is_authenticated %}Hello, {{ user.username }}.<a href="{% url 'users:logout' %}">Log out</a>
...
在与login.html
文件相同目录下创建logged_out.html
:
{% extends "learning_logs/base.html" %}{% block content %}<p>You have been logged out. Thank you for visiting!</p>
{% endblock content %}
在本节最后,我们定义用户注册页面。
首先定义URL。在users
目录下修改文件urls.py
如下:
"""Defines URL patterns for users"""from django.urls import path, includefrom . import viewsapp_name = 'users'
urlpatterns = [# Include default auth urls.path('', include('django.contrib.auth.urls')),# Registration page.path('register/', views.register, name='register'),
]
然后定义view。修改文件users/views.py
如下:
from django.shortcuts import renderfrom django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationFormdef register(request):"""Register a new user."""if request.method != 'POST':# Display blank registration form. form = UserCreationForm()else:# Process completed form.form = UserCreationForm(data=request.POST)if form.is_valid():new_user = form.save()# Log the user in and then redirect to home page.login(request, new_user)return redirect('learning_logs:index')# Display a blank or invalid form.context = {'form': form}return render(request, 'registration/register.html', context)
最后定义模板。在于login.html
相同目录下新建文件register.html
:
{% 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 %}
最后,连接注册页面。修改base.html
如下:
<p><a href="{% url 'learning_logs:index' %}">Learning Log</a> -<a href="{% url 'learning_logs:topics' %}">Topics</a> -{% 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 %}
</p>{% block content %}{% endblock content %}
测试页面http://localhost:8000/users/register/
如下:
允许用户拥有自己的数据
目前为止,用户可以直接访问Topic和Entry页面,本节我们将让Topic属于特定的用户。
使用@login_required
修饰符,Django可以方便的为限制用户访问页面。
首先修改views.py
,添加以下两句以设定需要登录:
...
from django.contrib.auth.decorators import login_required
...
@login_required
然后在settings.py
中添加一行以指定登录页面:
LOGIN_URL = 'users:login'
现在访问http://localhost:8000/topics/
就需要登录了。
以上只是限制了topics页面,我们还需要修改views.py
以限制其它页面,如新建topic,新建entry,但我们不限制主页和用户注册页面。修改文件过程略。
接下来我们需要将数据与用户管理,我们应该管理最高层级的数据。例如本例中就需要关联topic,因为entry属于topic,所以自然也关联到了用户。
修改models.py
,为Topic类添加一个owner属性:
...
from django.contrib.auth.models import User
...
class Topic(models.Model):
...owner = models.ForeignKey(User, on_delete=models.CASCADE)
...
现在已经有一些Topic和User了,如何关联呢?
首先我们通过Django shell查询到用户ID:
(ll_env) $ python manage.py shell
Python 3.6.8 (default, Aug 7 2019, 08:02:28)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39.0.1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: ll_admin>, <User: user01>]>
>>> for user in User.objects.all():
... print(user.username, user.id)
...
ll_admin 1
user01 2
>>> <Ctrl+D>
now exiting InteractiveConsole...
然后应用变化到数据库,也就是将已有的topic全部关联到用户ID为1的用户ll_admin
:
(ll_env) $ python manage.py makemigrations learning_logs
You are trying to add a non-nullable field 'owner' to topic without a default; we can't do that (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 let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 1
Migrations for 'learning_logs':learning_logs/migrations/0003_topic_owner.py- Add field owner to topic
(ll_env) $ 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
验证topic已经关联到用户:
(ll_env) $ python manage.py shell
Python 3.6.8 (default, Aug 7 2019, 08:02:28)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39.0.1)] on linux
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 ll_admin
Rock Climbing ll_admin
既然Topic已经关联用户了,接下来我们限制已登录用户对Topic的访问。
在views.py
中添加语句以实现过滤并排序(之前的语句是没有过滤的):
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
现在使用user01登录就看不到Topic了。
不过user01还是可以直接访问页面http://localhost:8000/topics/1/
看到topic。为此我们修改views.py
:
from django.http import Http404
...if topic.owner != request.user:raise Http404
...
这回直接访问就报错了:
接下来我们需要保护edit entry页面。方法是类似的,不赘述了。
最后,我们需要将新建的topic与用户关联。
修改views.py
的new_topic部分:
new_topic = form.save(commit=False)new_topic.owner = request.usernew_topic.save()
本章到此结束。
Python Crash Course读书笔记 - 第19章:USER ACCOUNTS相关推荐
- Python Crash Course读书笔记 - 第18章:GETTING STARTED WITH DJANGO
Django是一个web框架.可用来构建交互式网站. 设置项目 首先需要写项目说明书(spec). 然后需要创建虚拟环境(virtual environment). 虚拟环境是一个隔离的环境,可以单独 ...
- Python Crash Course读书笔记 - 第2章:Variables and Simple Data Types
变量 文件hello_world.py中, .py是python文件的后缀,因此会用Python interpreter解析. $ cat hello_world.py print("Hel ...
- Python Crash Course读书笔记 - 第16章:DOWNLOADING DATA
本章首先探索在线公开数据源.然后介绍CSV和JSON格式数据的处理,并分别用Matplotlib和Plotly做可视化. CSV文件格式 CSV(comma-separated values)格式,正 ...
- Python Crash Course读书笔记 - 第15章:GENERATING DATA
数据可视化是指通过可视化的手段探索数据,和数据分析紧密关联.通过代码来探索数据集的模式. 和显示酷炫的图片无关,而是让用户之前并不知道的数据含义和模式. Python被广泛应用于遗传学,气候研究,政治 ...
- python第三章上机实践_《机器学习Python实践》读书笔记-第三章
<机器学习Python实践>,第三章,第一个机器学习项目 以往目录:橘猫吃不胖:<机器学习Python实践>读书笔记-第一章zhuanlan.zhihu.com 书中介绍了一 ...
- 开始读Python Crash Course读书笔记
2020年1月13日晚开始读Python Crash Cours第二版.Crash Course是速成班的意思. 简要信息如下: Python Crash Course, 2nd Edition A ...
- AUTOMATE THE BORING STUFF WITH PYTHON读书笔记 - 第19章:MANIPULATING IMAGES
本章介绍Pillow模块,可处理图形文件.安装如下: # pillow安装依赖于JPEG源代码 $ sudo yum install libjpeg-turbo-devel $ pip3 instal ...
- python基础教程读书笔记——第三章 字符串
第三章 字符串 摘要: %s , $x , find() , join() , split() , lower() , title() , strip() 1.字符串格式化 format = &qu ...
- 《Python从入门到实践》读书笔记——第五章 if语句
<Python从入门到实践>读书笔记--第五章 if语句 1. 一个简单示例 cars = ['audi', 'bwm', 'subaru', 'toyota']for car in ca ...
最新文章
- Codevs 3002 石子归并 3(DP四边形不等式优化)
- 大数据标签获取处理步骤_大数据处理分为哪些步骤
- 13 CO配置-控制-内部订单-定义定单类型
- 人生重开模拟器微信小程序源码
- STM32的启动文见分析
- 谈谈金融行业的开源风险管理
- 从客户端中(...)检测到有潜在危险的 Request.Form值
- mount远程驱动器
- 计算机原理及应用教学大纲,《单片机原理及应用技术》课程教学大纲
- 服务器cmd升级系统命令,02-软件升级操作指导(命令行版)
- 计算机无法进入bios模式,电脑系统无法进入bios界面解决方法
- Maya: Time Editor Maya教程:时间编辑器 Lynda课程中文字幕
- 1Mbps高速光耦合器TLP112A工作原理及应用实例说明
- python小程序短信发送助手
- MySQL的启动与简单命令_002
- Educational Codeforces Round 95 (Rated for Div. 2)D. Trash Problem(权值线段树+离散化)
- 解决npm一直停在“checking installable status“的问题
- 三个数比大小 输出最大值
- VCS和Verdi联合仿真
- gitbook 插件 赞赏