20-1 其他表单 :我们对登录页面和 add_topic 页面应用了 Bootstrap 样式。请对其他基于表单的页面做类似的修改: new_entry 页面、 edit_entry 页面和注册页面。
20-2 设置博客的样式 :对于你在第 19 章创建的项目 Blog ,使用 Bootstrap 来设置其样式。
20-3 在线博客 :将你一直在开发的项目 Blog 部署到 Heroku 。确保将 DEBUG 设置为 False ,并修改设置 ALLOWED_HOSTS ,让部署相当安全。
20-4 在更多的情况下显示 404 错误页面 :在视图函数 new_entry() 和 edit_entry() 中,也使用函数 get_object_or_404() 。完成这些修改后进行测试:输入类似于 http://localhost:8000/new_entry/99999/ 的 URL ,确认你能够看到 404 错误页面。
20-5 扩展 “ 学习笔记 ” :在 “ 学习笔记 ” 中添加一项功能,将修改推送到在线部署。尝试做一项简单的修改,如在主页中对项目作更详细的描述;再尝试添加一项更高级的功能,如让用户能够将主题设置为公开的。为此,需要在模型 Topic 中添加一个名为 public 的属性(其默认值为 False ),并在 new_topic 页面中添加一个表单元素,让用户能够将私有主题改为公开的。然后,你需要迁移项目,并修改 views.py ,让未登录的用户也可以看到所有公开的主题。将修改推送到 Heroku 后,别忘了迁移在线数据库。
优化第十七章项目“pizzeria ”,实现过程(部署Heroku未实现):
七.设置项目样式
- 执行命令“pip install django-bootstrap3”安装 django-bootstrap3程序,把该程序加入settings.py(图1);为了能使用Bootstrap提供的交互式元素,需要在settings.py加入代码(图2):
- 修改模板base.html(代码1)、index.html(代码2)、login.html(代码3)、register.html(代码4):
{% load bootstrap3 %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Pizzas</title> <!-- 引入css和js --> {% bootstrap_css %} {% bootstrap_javascript %} </head> <body> <!-- 导航栏 --> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> </button> <a class="navbar-brand" href="{% url 'pizzas:index' %}"> Pizzas</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a href="{% url 'pizzas:pizzas' %}">Pizzas</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if user.is_authenticated %} <li><a>Hello, {{ user.username }}.</a></li> <li><a href="{% url 'users:logout' %}">log out</a></li> {% else %} <li><a href="{% url 'users:register' %}">register</a></li> <li><a href="{% url 'users:login' %}">log in</a></li> {% endif %} </ul> </div> <!--/.nav-collapse --> </div> </nav> <div class="container"> <div class="page-header"> {% block header %}{% endblock header %} </div> <div> {% block content %}{% endblock content %} </div> </div> <!-- /container --> </body> </html>
{% extends "pizzas/base.html" %} {% block header %} <div class='jumbotron'> <h1>Track your message.</h1> </div> {% endblock header %} {% block content %} <h2> <a href="{% url 'users:register' %}">Register an account</a> to make your own pizzeria, and list the pizzas you're learning about. </h2> <h2> Whenever you learn something new about a pizza, make an entry summarizing what you've learned. </h2> {% endblock content %}
{% extends "pizzas/base.html" %} {% load bootstrap3 %} {% block header %} <h2>Log in to your account.</h2> {% endblock header %} {% block content %} <form method="post" action="{% url 'users:login' %}" class="form"> {% csrf_token %} {% bootstrap_form form %} {% buttons %} <button name="submit" class="btn btn-primary">log in</button> {% endbuttons %} <!-- 实参value告诉Django在用户成功登录后将其重定向到什么地方 —— 在这里是主页 --> <input type="hidden" name="next" value="{% url 'pizzas:index' %}" /> </form> {% endblock content %}
{% extends "pizzas/base.html" %} {% load bootstrap3 %} {% block header %} <h2>Register an account.</h2> {% endblock header %} {% block content %} <form method="post" action="{% url 'users:register' %}"> {% csrf_token %} {% bootstrap_form form %} {% buttons %} <button name="submit" class="btn btn-primary">register</button> {% endbuttons %} <!-- 实参value告诉Django在用户成功登录后将其重定向到什么地方 —— 在这里是主页 --> <input type="hidden" name="next" value="{% url 'pizzas:index' %}" /> </form> {% endblock content %}
- 修改模板pizzas.html(代码1)、pizza.html(代码2)、new_pizza.html(代码3)、new_topping.html(代码4)、edit_topping.html(代码5):
{% extends "pizzas/base.html" %} {% block header %} <h1>Pizzas</h1> <h4 class="text-right"><a href="{% url 'pizzas:new_pizza' %}">Add new pizza</a></h4> {% endblock header %} {% block content %} <ul> {% for pizza in pizzas %} <li> <h4> <a href="{% url 'pizzas:pizza' pizza.id %}">{{ pizza }}</a> <span class="pull-right">{{ pizza.date_added|date:'M d, Y H:i' }}</span> </h4> </li> {% empty %} <li>No pizzas have been added yet.</li> {% endfor %} </ul> {% endblock content %}
{% extends 'pizzas/base.html' %} {% block header %} <h2>{{ pizza }}</h2> <h4 class="text-right"><a href="{% url 'pizzas:new_topping' pizza.id %}">Add new topping</a></h4> {% endblock header %} {% block content %} {% for topping in toppings %} <div class="panel panel-default"> <div class="panel-heading"> <h3> {{ topping.date_added|date:'M d, Y H:i' }} <small> <a class="pull-right" href="{% url 'pizzas:edit_topping' topping.id %}"> edit topping</a> </small> </h3> </div> <div class="panel-body"> {{ topping.name|linebreaks }} </div> </div> <!-- panel --> {% empty %} There are no toppings for this pizza yet. {% endfor %} {% endblock content %}
{% extends "pizzas/base.html" %} {% load bootstrap3 %} {% block header %} <h2>Add a new pizza:</h2> {% endblock header %} {% block content %} <form action="{% url 'pizzas:new_pizza' %}" method='post' class="form"> {% csrf_token %} {% bootstrap_form form %} {% buttons %} <button name="submit" class="btn btn-primary">add pizza</button> {% endbuttons %} </form> {% endblock content %}
{% extends "pizzas/base.html" %} {% load bootstrap3 %} {% block header %} <h2>Add a new topping:</h2> {% endblock header %} {% block content %} <form action="{% url 'pizzas:new_topping' pizza.id %}" method='post'> {% csrf_token %} {% bootstrap_form form %} {% buttons %} <button name="submit" class="btn btn-primary">add topping</button> {% endbuttons %} </form> {% endblock content %}
{% extends "pizzas/base.html" %} {% load bootstrap3 %} {% block header %} <h2>Edit topping:</h2> {% endblock header %} {% block content %} <form action="{% url 'pizzas:edit_topping' topping.id %}" method='post'> {% csrf_token %} {% bootstrap_form form %} {% buttons %} <button name="submit" class="btn btn-primary">save changes</button> {% endbuttons %} </form> {% endblock content %}
- 全部页面效果如下(分别index、register、login、pizzas、pizza、new_pizza、new_topping、edit_topping):
- 创建自定义错误页面,路径pizzeria/pizzeria下新建文件夹templates,文件夹新建模板404.html(代码1)、500.html(代码2):
{% extends "pizzas/base.html" %} {% block header %} <h2>The item you requested is not available. (404)</h2> {% endblock header %}
{% extends "pizzas/base.html" %} {% block header %} <h2>There has been an internal error. (500)</h2> {% endblock header %}
- 修改settings.py,使用自定义错误页面模板(图1),禁止默认的Django调试页面(图2,DEBUG设置False时,必须设置ALLOWED_HOSTS):
- 手工请求不存在的pizza或topping时,显示500错误(服务端错误),不符合实际情况;现把这两种情况改成显示404错误,修改pizzas--views.py(使用get_object_or_404):
from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect, Http404 # django2.0后把原来的django.core.urlresolvers包更改为了django.urls包 from django.urls import reverse from django.contrib.auth.decorators import login_required from .models import Pizza, Topping from .forms import PizzaForm, ToppingForm # Create your views here. def index(request): """ 披萨的主页 """ return render(request, 'pizzas/index.html') # login_required() 的代码检查用户是否已登录,仅当用户已登录时, Django 才运行 topics() 的代码。如果用户未登录,就重定向到登录页面。 # @login_required def pizzas(request): """ 显示所有的配料 """ if request.user.is_authenticated: # 登录状态下,只显示自己的数据 pizzas = Pizza.objects.filter(owner=request.user).order_by('date_added') else: # 未登录状态下,显示公开的数据 pizzas = list(Pizza.objects.all()) for pizza in pizzas[:]: if pizza.public == False: pizzas.remove(pizza) context = {'pizzas': pizzas} return render(request, 'pizzas/pizzas.html', context) @login_required def pizza(request, pizza_id): """ 显示单个披萨及其所有的配料 """ # 获取不到pizza条目改成显示404 pizza = get_object_or_404(Pizza, id=pizza_id) # 请求的披萨不归当前用户所有,我们就引发 Http404 异常,让 Django 返回一个 404 错误页面 check_topic_owner(pizza, request) # date_added 前面的减号指定按降序排列 toppings = pizza.topping_set.order_by('-date_added') context = {'pizza': pizza, 'toppings': toppings} return render(request, 'pizzas/pizza.html', context) @login_required def new_pizza(request): """ 添加新披萨 """ if request.method != 'POST': # 未提交数据:创建一个新表单 form = PizzaForm() else: # POST 提交的数据 , 对数据进行处理 form = PizzaForm(request.POST) # is_valid校验填写了必填信息和符合数据类型及长度 if form.is_valid(): new_pizza = form.save(commit=False) new_pizza.owner = request.user new_pizza.save() # 将用户重定向到网页 pizzas return HttpResponseRedirect(reverse('pizzas:pizzas')) context = {'form': form} return render(request, 'pizzas/new_pizza.html', context) @login_required def new_topping(request, pizza_id): """ 在特定的披萨中添加新配料 """ # 获取不到配料条目显示404 pizza = get_object_or_404(Pizza, id=pizza_id) # 解决一个用户可在另一个用户的学习笔记中添加条目问题 check_topic_owner(pizza, request) if request.method != 'POST': # 未提交数据 , 创建一个空表单 form = ToppingForm() else: # POST 提交的数据 , 对数据进行处理 form = ToppingForm(data=request.POST) if form.is_valid(): # 传递了实参commit=False,让Django创建一个新的配料对象,并将其存储到new_topping中,但不将它保存到数据库中 new_topping = form.save(commit=False) new_topping.pizza = pizza # 把配料保存到数据库,并将其与正确的披萨相关联 new_topping.save() return HttpResponseRedirect(reverse('pizzas:pizza', args=[pizza_id])) context = {'pizza': pizza, 'form': form} return render(request, 'pizzas/new_topping.html', context) @login_required def edit_topping(request, topping_id): """ 编辑既有配料 """ # 获取不到配料编辑条目显示404 topping = get_object_or_404(Topping, id=topping_id) pizza = topping.pizza check_topic_owner(pizza, request) if request.method != 'POST': # 初次请求,使用当前条目填充表单(instance=topping) form = ToppingForm(instance=topping) else: # POST 提交的数据,对数据进行处理 form = ToppingForm(instance=topping, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('pizzas:pizza', args=[pizza.id])) context = {'topping': topping, 'pizza': pizza, 'form': form} return render(request, 'pizzas/edit_topping.html', context) def check_topic_owner(pizza, request): """校验关联到的用户是否为当前登录的用户""" if pizza.owner != request.user: raise Http404
- 给pizza增加设置是否公开的功能,实现当登录状态下,只显示登录用户自己的数据;而未登录状态下,则显示公开的数据;先修改模型Pizza(pizzas--models.py)增加public属性(图1)、修改pizzas--forms.py(图2,增加字段“is public”和显示字段名)、修改pizzas--views.py(图3,判断登录和未登录情况,并注释“@login_required”),最后进行数据迁移(先执行命令“python manage.py makemigrations pizzas”,后执行命令“python manage.py migrate”);登录和未登录显示数据效果图(图4、图5):
总结:到这里全部练习题更新完毕。