Django实战教程: 开发餐厅在线点评网站(1)

版权声明:本文系大江狗原创,请勿直接copy应用于你的出版物或任何公众平台。 https://blog.csdn.net/weixin_42134789/article/details/81626987

小编我最喜欢写Django的基础知识,尤其是一个一个细小的知识点。因为我相信无论你是新手还是高手,熟练地掌握基础知识才能在实际Web开发项目中游刃有余。然而我们学习Django的最终目的还是应用,今天小编我就带你用Django开发一个餐厅在线点评的APP,也算应读者的要求。如果你对本文中的代码阅读起来还感觉有点吃力,建议关注我的微信公众号,点击经典原创阅读Django基础(1)到(12)。如果对代码有任何问题或不理解,可以在评论区留言。

总体思路

我们要开发一个餐厅点评网站(APP),具体包括以下几个功能性页面。

  • 查看餐厅(restaurants)列表 - 所有用户

  • 查看餐厅详情(包括名称,地址,电话,菜品和点评) - 所有用户

  • 创建餐厅 - 仅限登录用户

  • 修改餐厅 - 仅限登录用户,且每个用户只能修改自己创建的餐厅

  • 给餐厅添加菜品(dishes) - 仅限登录用户

  • 修改菜品信息 - 每个登录用户只能修改自己创建的菜品

  • 查看菜品详情(品名,描述, 图片和价格)

  • 给餐厅添加评论(review)和评分(rating)

如果匿名用户查看餐厅列表和详情时,它们会被要求先登录后再创建餐厅或给餐厅添加评论。我们预期的效果如下图所示。本教程分2部分,本文仅介绍前4个功能性页面。

项目开发环境

Django 2.0 + Python 3.5 + SQLite。因为我们需要上传显示图片,所以请确保你已通过pip安装python的pillow图片库。

项目配置settings.py

我们通过python manage.py startapp myrestaurants创建一个叫myrestaurants的APP,把它加到settings.py里INSATLLED_APP里去,如下所示。users是对Django自带AUTH User的扩展应用。如果不清楚,请阅读这里

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    'myrestaurants',
    'users',
]

因为我们要用到静态文件如css和图片,我们需要在settings.py里设置STATIC_URL和MEDIA。用户上传的图片会放在/media/文件夹里。

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"), ]

# specify media root for user uploaded files,
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

整个项目的urls.py如下所示。我们把myrestaurants的urls.py也加进去了。别忘了在结尾部分加static配置。

from django.conf.urls import url, include
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
    url(r'^myrestaurants/', include('myrestaurants.urls')),
    url(r'^admin/', admin.site.urls),
    url(r'^accounts/', include('users.urls')),

] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

模型models.py

对于Django而言,能设计一个良好的models,我们已经成功了一半。在本例中我们创建了Restuarant, Dish和Review的模型。因为我们在视图中会应用Django的通用视图,所以我们的模型里还需要定义get_abosolute_url。Django的CreateView和UpdateView在完成对象的创建或编辑后会自动跳转到这个绝对url。

from django.db import models
from django.contrib.auth.models import User
from datetime import date
from django.urls import reverse

class Restaurant(models.Model):
    name = models.TextField()
    address = models.TextField(blank=True, default='')
    telephone = models.TextField(blank=True,  default='')
    url = models.URLField(blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('myrestaurants:restaurant_detail', args=[str(self.id)])

class Dish(models.Model):
    name = models.TextField()
    description = models.TextField(blank=True,  default='')
    price = models.DecimalField('USD amount', max_digits=8, decimal_places=2, blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)
    image = models.ImageField(upload_to="myrestaurants", blank=True, null=True)
    restaurant = models.ForeignKey(Restaurant, null=True, related_name='dishes', on_delete=models.CASCADE)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('myrestaurants:dish_detail', args=[str(self.restaurant.id), str(self.id)])

# This Abstract Review can be used to create RestaurantReview and DishReview
class Review(models.Model):
    RATING_CHOICES = ((1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'), (5, 'five'))
    rating = models.PositiveSmallIntegerField('Rating (stars)', blank=False, default=3, choices=RATING_CHOICES)
    comment = models.TextField(blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)

    class Meta:
        abstract = True

class RestaurantReview(Review):
    restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, related_name="reviews")
    
    def __str__(self):
        return "{} review".format(self.restaurant.name)

URLConf配置urls.py

每个path都对应一个视图,一个命名的url和我们本文刚开始介绍的一个功能性页面。本文中包含了4个urls。我们在查看餐厅详情和编辑餐厅信息的url中传递了pk(餐厅id)作为参数。

from django.urls import path, re_path
from . import views

# namespace
app_name = 'myrestaurants'
urlpatterns = [

# 查看餐厅列表
    path('', views.RestaurantList.as_view(), name='restaurant_list'),

# 查看餐厅详情, 如/myrestaurants/restaurant/1/
    re_path(r'^restaurant/(?P<pk>\d+)/$',
        views.RestaurantDetail.as_view(), name='restaurant_detail'),

# 创建餐厅, 如:/myrestaurants/restaurant/create/
    re_path(r'^restaurant/create/$', views.RestaurantCreate.as_view(), name='restaurant_create'),

# 编辑餐厅详情, 如: /myrestaurants/restaurant/1/edit/
    re_path(r'^restaurant/(?P<pk>\d+)/edit/$',
        views.RestaurantEdit.as_view(), name='restaurant_edit'),

]

视图views.py

为了简化开发,本例中使用了Django自带的通用视图。我们使用ListView来显示餐厅列表,使用DetailView来显示餐厅详情,使用CreateView来创建餐厅,使用UpdateView来编辑餐厅信息。如果你对通用视图不了解,请阅读下文:

# 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 RestaurantList(ListView):

    queryset = Restaurant.objects.all().order_by('-date')
    context_object_name = 'latest_restaurant_list'
    template_name = 'myrestaurants/restaurant_list.html'


class RestaurantDetail(DetailView):
    model = Restaurant
    template_name = 'myrestaurants/restaurant_detail.html'

    def get_context_data(self, **kwargs):
        context = super(RestaurantDetail, self).get_context_data(**kwargs)
        context['RATING_CHOICES'] = RestaurantReview.RATING_CHOICES
        return context


class RestaurantCreate(CreateView):
    model = Restaurant
    template_name = 'myrestaurants/form.html'
    form_class = RestaurantForm

    # Associate form.instance.user with self.request.user
    def form_valid(self, form):
        form.instance.user = self.request.user
        return super(RestaurantCreate, self).form_valid(form)


class RestaurantEdit(UpdateView):
    model = Restaurant
    template_name = 'myrestaurants/form.html'
    form_class = RestaurantForm

重要知识点:

  • 在RestaurantDetail视图里,我们通过get_context_data方法向模板传递了额外的变量RATING_CHOICES。DetailView视图会接受url传递来的pk值,并显示该模型的所有信息。

  • 在RestaurantCreate视图里,我们使用了form_valid方法。form_valid方法作用是添加前端表单字段以外的信息。在用户在创建餐厅时,我们不希望用户能更改创建用户,于是在前端表单里把user故意除外了(见forms.py),而选择在后台添加user信息。

表单forms.py

创建和编辑对象时需要用到表单,我们的表单如下所示。我们在前端表单里移除了user,而采用后台添加的方式。我们添加了widget和labels。添加widget的目的时为了定制用户输入控件(比如URLInput),并给其添加css样式(因为boostrap表单需要form-control这个样式)。

from django.forms import ModelForm,  TextInput, URLInput, ClearableFileInput
from .models import Restaurant, Dish


class RestaurantForm(ModelForm):
    class Meta:
        model = Restaurant
        exclude = ('user', 'date',)

        widgets = {
            'name': TextInput(attrs={'class': 'form-control'}),
            'address': TextInput(attrs={'class': 'form-control'}),
            'telephone': TextInput(attrs={'class': 'form-control'}),
            'url': URLInput(attrs={'class': 'form-control'}),
        }

        labels = {
            'name': '名称',
            'address': '地址',
            'telephone': '电话',
            'url': '网站',
        }

模板文件

我们在目录中创建templates/myrestaurants/目录,添加如下html模板。

# restaurant_list.html

{% extends "myrestaurants/base.html" %}

{% block content %}
<h3>餐厅列表</h3>

<ul>
  {% for restaurant in latest_restaurant_list %}
    <li><a href="{% url 'myrestaurants:restaurant_detail' restaurant.id %}">
    {{ restaurant.name }}</a></li>
  {% empty %}<li>对不起,没有餐厅点评。</li>
  {% endfor %}
</ul>

 {% if request.user.is_authenticated %}
<p><span class="glyphicon glyphicon-plus"></span> <a href="{% url 'myrestaurants:restaurant_create' %}">添加餐厅</a></p>
 {% else %}

<p>请<a href="{% url 'users:login' %}?next={% url 'myrestaurants:restaurant_create' %}">登录</a>后添加餐厅。</p>
  {% endif %}
{% endblock %}

重要知识点:

  • 请观察我们是如何把参数(如餐厅id)传递给命名url的。

  • 请观察我们是如何设置next实现匿名用户登录后立即跳转到餐厅创建页面的。

# 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>评分</p>
  <p>
    {% for rate in RATING_CHOICES %}
      <input type="radio" name="rating" id="rating{{ forloop.counter }}" value="{{ rate.0 }}" />
      <label for="choice{{ forloop.counter }}">{{ rate.0 }}星</label>
      <br/>
    {% endfor %}
  </p>
  <input type="submit" value="提交" />
</form>
{% else %}
<p>请先<a href="{% url 'users:login' %}?next={% firstof request.path '/' %}">登录</a>再评论。</p>
{% endif %}

{% endblock %}

重要知识点:

  • 请观察我们是如何通过 if request.user == restaurant.user 来控制每个用户只能编辑自己创建的餐厅的。

# form.html

注意: 创建餐厅和编辑餐厅,我们使用了同样一个模板。

{% 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 %}

# base.html

这个文件基本上只包括样式,不包括逻辑,可以不看。

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %} Django餐厅点评系统{% endblock %} </title>
<meta charset="utf-8">
<meta name="keywords" content="{% block keywords %} some keywords{% endblock %} ">
<meta name="description" content="{% block description %} Django web application example.{% endblock %}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="{% static 'myrestaurants/custom.css' %}">


</head>

<body class="bs-docs-home">
<nav class="navbar navbar-inverse navbar-static-top bs-docs-nav">
  <div class="container">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>                        
      </button>
        <a class="navbar-brand" href="#"><strong>Django餐厅点评APP</strong></a>
     </div>

      <div class="collapse navbar-collapse" id="myNavbar">

       <ul class="nav navbar-nav navbar-right">
       {% if user.is_authenticated %}

          <li class="dropdown">
              <a class="dropdown-toggle btn-green" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-user"></span>  {{ user.username }} <span class="caret"></span></a>
            <ul class="dropdown-menu">
              <li><a  href="{% url 'users:profile' user.id %}">我的账户</a></li>
              <li><a  href="{% url 'users:logout' %}">退出登录</a></li>
            </ul>
          </li>  
         {% else %}         
            <li class="dropdown"><a class="dropdown-toggle btn-green" href="{% url 'users:register' %}"><span class="glyphicon glyphicon-user"></span> 注册</a></li>
         <li class="dropdown"><a class="dropdown-toggle" href="{% url 'users:login' %}" ><span class="glyphicon glyphicon-log-in"></span> 登录</a></li>
       {% endif %}
       </ul>

    </div>

  </div>
</nav>    

 <!-- Page content of course! -->
<main id="section1" class="container-fluid">
<div class="container">
  <div class="row">
 <div class="col-sm-12">
   <div class="wrapper">
        <div class="newsbox">
             <div class="container1">       

 {% block content %}
  {% if error_message %}<p><strong>{{ error_message}}</strong></p>{% endif %}
  {% endblock %}

        </div>
      </div>
   </div>
</div>
</div>
    
</div> 
</main>
<footer class="footer">
  {% block footer %}{% endblock %}
</footer>
<!--End of Footer-->

<!-- Bootstrap core JavaScript
================================================== -->

<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

</body>
</html>

查看效果

连续运行python manage.py makemigrations, python manage.py migrate和python manage.py runserver,  打开http://127.0.0.1:8000/myrestaurants/就可以看到以下效果了。

餐厅列表(用户登录后跳转到添加餐厅页面)

创建餐厅(用户创建餐厅后跳转到餐厅详情)

餐厅详情页面(用户登录后才能点评)

用户登录后可以修改餐厅信息或提交点评

修改餐厅详情(每个登录用户只能修改自己创建的餐厅)

小结

我们利用Django开发了一个简单的餐厅点评网站,实现了以下4个标黄的功能性页面。下篇教程中,我们将开发剩余4个功能性页面,欢迎关注我的微信公众号获取更多更新。

  • 查看餐厅(restaurants)列表

  • 查看餐厅详情(包括名称,地址,电话,菜品和点评)

  • 创建餐厅 - 仅限登录用户

  • 修改餐厅 - 每个登录用户只能修改自己创建的餐厅

  • 给餐厅添加菜品(dishes) - 仅限登录用户

  • 修改菜品信息 - 每个登录用户只能修改自己创建的菜品

  • 查看菜品详情(品名,描述, 图片和价格)

  • 给餐厅添加评论(review)和评分(rating)

猜你喜欢

转载自blog.csdn.net/weixin_42134789/article/details/81626987