orning! 最近关注本公众号的人很多,小编我真有点小激动。上篇文章中没发小姐姐的福利图,立马就有人来问了。我不禁内心一凉。亲们,你们关注本公众号不会是为了福利图而来的吧?话不多说,小编我今天接着分享使用Django开发一个餐厅在线点评网站的第2部分教程。今天的任务是开发其余4个功能性页面(见标黄部分)。
-
查看餐厅(restaurants)列表 - 所有用户
-
查看餐厅详情(包括名称,地址,电话,菜品和点评) - 所有用户
-
创建餐厅 - 仅限登录用户
-
修改餐厅 - 仅限登录用户,且每个用户只能修改自己创建的餐厅
-
给餐厅添加菜品(dishes) - 仅限登录用户
-
修改菜品信息 - 每个登录用户只能修改自己创建的菜品
-
查看菜品详情(品名,描述, 图片和价格)
-
给餐厅添加评论(review)和评分(rating)
在阅读本文前,请务必先完成阅读Django实战教程: 开发餐厅在线点评网站(1)。福利图照送,喜欢的给个赞! :)
URLconf之urls.py
小编我使用Django开发网站或APP有个习惯,一般先写urls.py再编写视图views.py。我是这么思考的:
-
本文中有4个功能性页面,那么我需要设计4个urls,并编写4个视图处理方法与之对应。
-
根据功能性页面的需求,我可以更好地思考需要通过url给视图传递哪些参数。比如创建菜品时,我们还需要通过URL传递餐厅的id值(<pk>)。因为一个餐厅对应多个菜品。
from django.urls import path, re_path from . import views # namespace app_name = 'myrestaurants' urlpatterns = [ # 前4个功能性页面的URL见教程第一部分 # 创建菜品 ex.: /myrestaurants/restaurant/1/dishes/create/ re_path(r'^restaurant/(?P<pk>\d+)/dishes/create/$', views.DishCreate.as_view(), name='dish_create'), # 编辑菜品, ex.: /myrestaurants/restaurant/1/dishes/1/edit/ re_path(r'^restaurant/(?P<pkr>\d+)/dishes/(?P<pk>\d+)/edit/$', views.DishEdit.as_view(), name='dish_edit'), # 查看菜品信息 ex: /myrestaurants/restaurants/1/dishes/1/ re_path(r'^restaurant/(?P<pkr>\d+)/dishes/(?P<pk>\d+)/$', views.DishDetail.as_view(), name='dish_detail'), # 创建餐厅评论, /myrestaurants/restaurant/1/reviews/create/ re_path(r'^restaurant/(?P<pk>\d+)/reviews/create/$', views.review_create, name='review_create'), ]
你可能会问为什么下例中不通过pk而是通过pkr传递餐厅的id值了? 这是因为对应菜品的EditView或DetailView需要从url传递过来的菜品的pk参数,同一个url中有两个pk参数是不被允许的。这个url中菜品已经用掉了pk,餐厅的id值就就不能用pk传递了,所以用其它名字。这个名字可以pkr也可以是pka,rid,随便你怎么取。
re_path(r'^restaurant/(?P<pkr>\d+)/dishes/(?P<pk>\d+)/edit/$',
视图views.py
与上述4个urls对应的处理视图如下所示。请注意观察:
-
创建菜品时我们是如何使用form_valid方法将菜品与通过url传递过来的餐厅id建立联系的。之所以需要用form_valid方法是因为我们在表单里把餐厅和user去掉了,因为我们不希望用户在前端更改这两部分数据。
-
review_create方法是如何将用户通过表单提交来的评论和评分存入数据库,并跳转到餐厅详情页面的。
# Create your views here.
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views.generic import DetailView, ListView, UpdateView
from django.views.generic.edit import CreateView
from .models import RestaurantReview, Restaurant, Dish
from .forms import RestaurantForm, DishForm
# 其余视图见第一部分教程
class DishDetail(DetailView):
model = Dish
template_name = 'myrestaurants/dish_detail.html'
class DishCreate(CreateView):
model = Dish
template_name = 'myrestaurants/form.html'
form_class = DishForm
# Associate form.instance.user with self.request.user and get pk value.
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.restaurant = Restaurant.objects.get(id=self.kwargs['pk'])
return super(DishCreate, self).form_valid(form)
class DishEdit(UpdateView):
model = Dish
template_name = 'myrestaurants/form.html'
form_class = DishForm
def review_create(request, pk):
restaurant = get_object_or_404(Restaurant, pk=pk)
review = RestaurantReview(
rating=request.POST['rating'],
comment=request.POST['comment'],
user=request.user,
restaurant=restaurant)
review.save()
return HttpResponseRedirect(reverse('myrestaurants:restaurant_detail', args=[pk]))
表单forms.py
创建和编辑对象时用到了DishForm,其表单如下所示。
from django.forms import ModelForm, TextInput, URLInput, ClearableFileInput
from .models import Restaurant, Dish
class DishForm(ModelForm):
class Meta:
model = Dish
exclude = ('user', 'date', 'restaurant',)
widgets = {
'name': TextInput(attrs={'class': 'form-control'}),
'description': TextInput(attrs={'class': 'form-control'}),
'price': TextInput(attrs={'class': 'form-control'}),
'image': ClearableFileInput(attrs={'class': 'form-control'}),
}
labels = {
'name': '菜名',
'description': '描述',
'price': '价格(元)',
'image': '图片',
}
模板templates
# dish_detail 展示餐厅详情(如果用户登录,显示修改按钮)
只有当request.user == dish.user时才显示菜品修改按钮。
{% extends "myrestaurants/base.html" %}
{% block content %}
<h3>
{{ dish.name }}
{% if request.user == dish.user %}
(<a href="{% url 'myrestaurants:dish_edit' dish.restaurant.id dish.id %}">修改</a>)
{% endif %}
</h3>
<p> 餐厅: <a href="{% url 'myrestaurants:restaurant_detail' dish.restaurant.id %}">
{{ dish.restaurant.name}}
</a></p>
<p>{{ dish.description }}</p>
{% if dish.image %}
<p><img src="{{ dish.image.url }}"/></p>
{% endif %}
<p>价格: {{ dish.price }}元</p>
{% endblock %}
上面模板最重要的代码就是仔细观察如何向命名的url dish_edit传递餐厅的id和菜品的id了,如下所示。
{% url 'myrestaurants:dish_edit' dish.restaurant.id dish.id %}
展示效果如下:
# form.html (创建和编辑菜品)
因为我们需要用表单上传图片文件,enctype="multipart/form-data"必需加上。
{% extends "myrestaurants/base.html" %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data" >
{% csrf_token %}
{% for hidden_field in form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% for field in form.visible_fields %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}
<input type="submit" value="提交"/>
</form>
{% endblock %}
展示效果如下:
注意: 我们没有专门展示菜品的列表,也没有专门提交餐厅评论的页面。它们都包含在餐厅详情模板里了。
# restaurant_detail.html
{% extends "myrestaurants/base.html" %}
{% block content %}
<h3>
{{ restaurant.name }}
{% if request.user == restaurant.user %}
(<a href="{% url 'myrestaurants:restaurant_edit' restaurant.id %}">修改</a>)
{% endif %}
</h3>
<h4>地址</h4>
<p>
{{ restaurant.address }}, <br/>
{{ restaurant.telephone }}
</p>
<h4>菜单
{% if request.user.is_authenticated %}
(<a href="{% url 'myrestaurants:dish_create' restaurant.id %}">添加</a>)
{% endif %}
</h4>
<ul>
{% for dish in restaurant.dishes.all %}
<li><a href="{% url 'myrestaurants:dish_detail' restaurant.id dish.id %}">
{{ dish.name }}</a> - {{ dish.price }}元</li>
{% empty %}<li>对不起,该餐厅还没有菜肴。</li>
{% endfor %}
</ul>
<h4>用户点评</h4>
{% if restaurant.reviews.all %}
{% for review in restaurant.reviews.all %}
<p>{{ review.rating}}星, {{ review.user }}点评, {{ review.date | date:"Y-m-d" }}</p>
<p>{{ review.comment }}</p>
{% endfor %}
{% else %}
<p>目前还没有用户点评。</p>
{% endif %}
<h4>添加点评</h4>
{% if request.user.is_authenticated %}
<form action="{% url 'myrestaurants:review_create' restaurant.id %}" method="post">
{% csrf_token %}
<p>评论</p>
<textarea name="comment" id="comment"></textarea>
<p>评分
{% for rating in RATING_CHOICES %}
<span><input type="radio" name="rating" id="rating{{ forloop.counter }}" value="{{ rating.0 }}" />
<label for="choice{{ forloop.counter }}">{{ rating.0 }}星</label>
</span>
{% endfor %}
</p>
<input type="submit" value="提交" />
</form>
{% else %}
<p>请先<a href="{% url 'users:login' %}?next={% firstof request.path '/' %}">登录</a>再评论。</p>
{% endif %}
{% endblock %}
最后展示效果如下所示:
小结
本文利用Django的通用视图和数百行代码开发了一个简单的餐厅在线点评网站。如果你是一个熟练的Django开发者,完成整个项目(不包括测试)半天肯定够了,可见Django开发速度之快。除此以外,Django的数据,业务逻辑与模板的分离的设计思想也便于前端和后端人员的分开维护。希望通过本教程,你了解到了Django Web开发的思路和大致流程,并熟悉通用视图的使用和URL参数的传递技巧。
从下周起,小编我会开始Django与Python爬虫基础知识交替写,欢迎关注。
大江狗
2018.8.13