允许用户输入数据

目前用户数据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相关推荐

  1. Python Crash Course读书笔记 - 第18章:GETTING STARTED WITH DJANGO

    Django是一个web框架.可用来构建交互式网站. 设置项目 首先需要写项目说明书(spec). 然后需要创建虚拟环境(virtual environment). 虚拟环境是一个隔离的环境,可以单独 ...

  2. Python Crash Course读书笔记 - 第2章:Variables and Simple Data Types

    变量 文件hello_world.py中, .py是python文件的后缀,因此会用Python interpreter解析. $ cat hello_world.py print("Hel ...

  3. Python Crash Course读书笔记 - 第16章:DOWNLOADING DATA

    本章首先探索在线公开数据源.然后介绍CSV和JSON格式数据的处理,并分别用Matplotlib和Plotly做可视化. CSV文件格式 CSV(comma-separated values)格式,正 ...

  4. Python Crash Course读书笔记 - 第15章:GENERATING DATA

    数据可视化是指通过可视化的手段探索数据,和数据分析紧密关联.通过代码来探索数据集的模式. 和显示酷炫的图片无关,而是让用户之前并不知道的数据含义和模式. Python被广泛应用于遗传学,气候研究,政治 ...

  5. python第三章上机实践_《机器学习Python实践》读书笔记-第三章

    <机器学习Python实践>,第三章,第一个机器学习项目 以往目录:橘猫吃不胖:<机器学习Python实践>读书笔记-第一章​zhuanlan.zhihu.com 书中介绍了一 ...

  6. 开始读Python Crash Course读书笔记

    2020年1月13日晚开始读Python Crash Cours第二版.Crash Course是速成班的意思. 简要信息如下: Python Crash Course, 2nd Edition A ...

  7. AUTOMATE THE BORING STUFF WITH PYTHON读书笔记 - 第19章:MANIPULATING IMAGES

    本章介绍Pillow模块,可处理图形文件.安装如下: # pillow安装依赖于JPEG源代码 $ sudo yum install libjpeg-turbo-devel $ pip3 instal ...

  8. python基础教程读书笔记——第三章 字符串

    第三章 字符串 摘要: %s , $x , find()  , join() , split() , lower() , title() , strip() 1.字符串格式化 format = &qu ...

  9. 《Python从入门到实践》读书笔记——第五章 if语句

    <Python从入门到实践>读书笔记--第五章 if语句 1. 一个简单示例 cars = ['audi', 'bwm', 'subaru', 'toyota']for car in ca ...

最新文章

  1. Codevs 3002 石子归并 3(DP四边形不等式优化)
  2. 大数据标签获取处理步骤_大数据处理分为哪些步骤
  3. 13 CO配置-控制-内部订单-定义定单类型
  4. 人生重开模拟器微信小程序源码
  5. STM32的启动文见分析
  6. 谈谈金融行业的开源风险管理
  7. 从客户端中(...)检测到有潜在危险的 Request.Form值
  8. mount远程驱动器
  9. 计算机原理及应用教学大纲,《单片机原理及应用技术》课程教学大纲
  10. 服务器cmd升级系统命令,02-软件升级操作指导(命令行版)
  11. 计算机无法进入bios模式,电脑系统无法进入bios界面解决方法
  12. Maya: Time Editor Maya教程:时间编辑器 Lynda课程中文字幕
  13. 1Mbps高速光耦合器TLP112A工作原理及应用实例说明
  14. python小程序短信发送助手
  15. MySQL的启动与简单命令_002
  16. Educational Codeforces Round 95 (Rated for Div. 2)D. Trash Problem(权值线段树+离散化)
  17. 解决npm一直停在“checking installable status“的问题
  18. 三个数比大小 输出最大值
  19. VCS和Verdi联合仿真
  20. gitbook 插件 赞赏

热门文章

  1. 微信订阅推送通知实现
  2. 遥感原始图像计算机格式,卫星遥感影像数据是什么样格式的?
  3. 支付宝在服务器网页打不开怎么办,支付宝的页面打不开是怎么回事 浏览器支付页面打不开怎么办...
  4. 【欢迎来怼】事后诸葛亮会议
  5. 教你几招解决电脑假死现象
  6. 小程序后台开发sdk
  7. 2017年总结和计划
  8. 3DTouch桌面快捷方式
  9. UID、PID、PPID是什么?
  10. Delphi7 将Excel导入数据库