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):
總結:到這裏全部練習題更新完畢。