菜谱系统小成阶段,Python Web 领域终于攻占一个小山头

橡皮擦,一个逗趣的互联网高级网虫。新的系列,让我们一起进入 Django 世界。

已经完成的文章

十、菜谱的添加与展示

本篇博客将进行菜谱系统的核心模块开发,菜谱的添加与展示。

10.1 添加菜谱

在 Django 中对于一个功能的实现,添加一定是必备的,没有数据就没有办法进行后续操作了。

实现该功能的第一步依旧是在模板文件夹中添加 HTML 页面。本页面对于口味,工艺需要调用 API 数据进行渲染,本阶段不做调整。

{% extends "menuapp/frame.html" %} {% block title %} 菜谱系统 ---- 添加菜谱 {%
endblock %} {% block content %}
<div class="container">
  <h2 class="form-signup-heading">添加菜谱</h2>
  <div class="well"></div>
  <form class="form-horizontal" role="form" method="post">
    {% csrf_token %}
    <div class="form-group">
      <label for="name" class="col-sm-2 control-label">名称:</label>
      <div class="col-sm-6">
        <input
          type="text"
          class="form-control"
          id="name"
          name="name"
          placeholder="请输入菜谱名称"
        />
      </div>
    </div>
    <div class="form-group">
      <label for="technology_sel" class="col-sm-2 control-label">工艺:</label>
      <div class="col-sm-6">
        <select name="technology" class="form-control" id="technology_sel">
          <option>炒</option>
          <option>煮</option>
          <option>烤</option>
          <option>蒸</option>
        </select>
      </div>
    </div>
    <div class="form-group">
      <label for="flavor_sel" class="col-sm-2 control-label">口味:</label>
      <div class="col-sm-6">
        <select name="flavor" class="form-control" id="flavor_sel">
          <option>家常</option>
          <option>香辣</option>
          <option>怪味</option>
          <option>黑椒</option>
        </select>
      </div>
    </div>

    <div class="form-group">
      <label for="difficulty" class="col-sm-2 control-label">难度:</label>
      <div class="col-sm-6">
        <select name="difficulty" class="form-control" id="difficulty">
          <option value="1">初级</option>
          <option value="2">中级</option>
          <option value="3">高级</option>
        </select>
      </div>
    </div>
    <div class="form-group">
      <label for="production_time_sel" class="col-sm-2 control-label"
        >时间:</label
      >
      <div class="col-sm-6">
        <select
          name="production_time"
          class="form-control"
          id="production_time_sel"
        >
          <option value="5">5分钟</option>
          <option value="10">10分钟</option>
          <option value="15">15分钟</option>
          <option value="30">30分钟</option>
        </select>
      </div>
    </div>
    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-6">
        <button type="submit" class="btn btn-lg btn-primary btn-block">
          确定添加
        </button>
      </div>
    </div>
  </form>
</div>

{% endblock %}

add.html 添加完毕,接下来在对 views.py 文件进行补充,完善该页面的调用。

# 菜谱添加
@user_passes_test(lambda u: u.is_staff)
def add_menu(request):
    user = request.user
    state = None
    # 当用户点击确认添加按钮时候的操作
    if request.method == "POST":
        n_menu = Menu(
            name=request.POST.get("name", ""),
            technology=request.POST.get("technology", ""),
            flavor=request.POST.get("flavor", ""),
            difficulty=request.POST.get("difficulty", ""),
            production_time=request.POST.get("production_time", "")
        )
        n_menu.save()
        state = "success"

    context = {
        "active_menu": 'add_menu',
        "user": user,
        "state": state

    }
    return render(request, "menuapp/add_menu.html", context)

注意该函数上部存在一个装饰器,主要用于判断当前登录的用户是否是超级管理员,否则没有权限操作。使用该装饰器,需要在头部导入相关函数。

from .models import Menu
from django.contrib.auth.decorators import user_passes_test

此时,访问 http://127.0.0.1:8000/add 页面,URL 会自动跳转到 http://127.0.0.1:8000/login/?next=/add/ 页面,注意这里遇到了之前埋下的雷,也就是旧 BUG,我们在编制路由的之后,没有考虑带参数的场景,所以接下来修改 urls.py 代码如下,修改的内容差异你可以自行比对。

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="default"),
    path("register/", views.register, name="register"),
    path("login/", views.login, name="login"),
    path("logout/", views.logout, name="logout"),
    path("add/", views.add_menu, name="add_menu")
]

此时编译代码,得到如下界面,表示本步骤已经操作完成。

20210406212940431[1].png

写到这里需要对 login 函数进行一下完善,因为该视图可能处理两种情况,第一种登录之后跳转首页,第二种是登录之后跳转到登录前的页面。修改代码部分如下:

if user is not None:
    auth.login(request, user)
    # 获取 next 指向的地址,如果存在就跳转到 next 指向的地址
    target_url = request.GET.get("next", reverse("default"))

    return HttpResponseRedirect(target_url)

接下来实现注册用户的同时,添加管理员权限,该权限字段由 is_staff 来控制。修改 register.html 页面,修改的代码部分如下所示:

<div class="form-group">
  <label for="master" class="col-sm-2 control-label">权限:</label>
  <div class="col-sm-6">
    <div class="checkbox">
      <label>
        <input type="checkbox" name="is_staff" id="master" />管理员
      </label>
    </div>
  </div>
</div>

继续修改 views.py 文件,重点在函数头部编写了一个复选框数据转换字典,然后通过前台传递到视图的数据进行转换:

def register(request):
    CHECKBOX_MAPPING = {'on': True, 'off': False, }
    if request.user.is_authenticated:
        return HttpResponseRedirect(reverse("default"))

    # 用户注册状态信息
    state = None
    # 当用户提交注册信息
    if request.method == "POST":
        username = request.POST.get("username", "")
        password = request.POST.get("password", "")
        email = request.POST.get("email", "")
        is_staff = CHECKBOX_MAPPING[request.POST.get("is_staff", "off")]

        # 判断用户名是否存在
        if User.objects.filter(username=username):
            state = "user_exist"

        else:
            n_user = User.objects.create_user(username=username, password=password, email=email, is_staff=is_staff)
            # 保存注册信息到数据库
            n_user.save()
            state = "success"  # 表示注册成功

    context = {
        "active_menu": 'default',
        "user": None,
        "state": state
    }
    return render(request, "menuapp/register.html", context)

当上述代码运行成功之后,再通过 register.html 页面注册的同时,可以勾选管理员身份,注册到数据库中的数据如下。

20210406215355360[1].png

截止到现在,如果你整体步骤都梳理清楚之后,就已经实现登录之后才可以访问添加菜谱页面的 Web 应用了。
备注,如果非管理员访问 add/ 会自动跳转回首页。

10.2 菜谱列表

优先实现一个最简单的列表页面,在读取菜谱数据的时候,为防止出现一次性读取大量数据,所以需要使用投影方法,读取部分数据字段。

views.py 新增 menu_list 函数,该函数要求用户登录状态下才可以访问,在文件开头注意导入对应的函数。

from django.contrib.auth.decorators import user_passes_test, login_required
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage  # 分页组件

@login_required
def menu_list(request):
    user = request.user

    menus = Menu.objects.all()

    # 数据分页
    paginator = Paginator(menus, 10)
    page = request.GET.get("page")
    # 分页异常处理
    try:
        menus = paginator.page(page)
    except PageNotAnInteger:
        menus = paginator.page(1)
    except EmptyPage:
        menus = paginator.page(paginator.num_pages)

    context = {
        "user": user,
        "active_menu": "view_menu",
        "menu_list": menus
    }

    return render(request, "menuapp/list.html", context)

本函数实现了基本的分页功能,但是对菜谱的分类功能,还未实现,后文继续对其进行补充说明。实现了 menu_list 函数之后,立刻对 urls.py 文件进行修改。

urlpatterns = [
    path("", views.index, name="default"),
    path("register/", views.register, name="register"),
    path("login/", views.login, name="login"),
    path("logout/", views.logout, name="logout"),
    path("add/", views.add_menu, name="add_menu"),
    path("list/", views.menu_list, name="menu_list")
]

准备工作已经完成,在 templates/menuapp 中新增文件 list.html,添加如下代码,该代码中使用了部分模板语言。

{% extends "menuapp/frame.html" %} {% block title %} 菜谱系统 ---- 列表页面 {%
endblock %} {% block content %}
<div class="container">
  <div class="row">
    <div class="col-md-10 col-md-offset-1">
      <div class="col-md-2">
        <div class="list-group">
          <a href="{% url 'menu_list'%}">全部菜谱</a>
          <!--后期处理成菜谱分类-->
        </div>
      </div>
      <div class="col-md-9 col-md-offset-1">
        <table class="table table-hover">
          <thead>
            <tr>
              <th>#</th>
              <th>菜谱名称</th>
              <th>工艺</th>
              <th>口味</th>
              <th>难度</th>
              <th>时间</th>
            </tr>
          </thead>
          <tbody>
            {% for menu in menu_list %}
            <tr>
              <td>{{forloop.counter}}</td>
              <td>{{ menu.name }}</td>
              <td>{{ menu.technology }}</td>
              <td>{{ menu.flavor }}</td>
              <td>{{ menu.difficulty }}</td>
              <td>{{ menu.production_time }}</td>
            </tr>
            {% endfor %}
          </tbody>
        </table>
        <nav>
          <ul class="pager">
            {% if menu_list.has_previous %}
            <li class="previous">
              <a
                href="{% url 'menu_list' %}?page={{ menu_list.previous_page_number }}"
                >上一页</a
              >
            </li>
            {% else %}
            <li class="previous disabled">
              <a href="#">上一页</a>
            </li>
            {% endif %} 第 {{ menu_list.number }} / {{
            menu_list.paginator.num_pages }} 页 {% if menu_list.has_next %}
            <li class="next">
              <a
                href="{% url 'menu_list' %}?page={{ menu_list.next_page_number }}"
                >下一页</a
              >
            </li>
            {% else %}
            <li class="next disabled">
              <a href="#">下一页</a>
            </li>
            {% endif %}
          </ul>
        </nav>
      </div>
    </div>
  </div>
</div>
{% endblock %}

最终实现初稿效果如下,一个包含分页的菜谱系统列表页面已经完成。

20210406225210187[1].png

10.3 本篇博客小节

本篇博客对菜谱系统的菜谱添加、列表以及分页功能,一个微型的菜谱管理系统已经初具模型,后续都是对其进行完善与修改,希望你能在本篇博客学到知识,感谢。

相关阅读

  1. Python 爬虫 100 例教程,超棒的爬虫教程,立即订阅吧
  2. Python 游戏世界(更新中,目标文章数 50+,现在订阅,都是老粉)
  3. Python 爬虫小课,精彩 9 讲

今天是持续写作的第 <font color="red">128</font> / 200 天。
如果你想跟博主建立亲密关系,可以关注同名公众号 <font color="red">梦想橡皮擦</font>,近距离接触一个逗趣的互联网高级网虫。
博主 ID:梦想橡皮擦,希望大家<font color="red">点赞</font>、<font color="red">评论</font>、<font color="red">收藏</font>。

<font color=white size=1>
django教程 django中文文档 Django 文档
django需要什么基础 第一个django项目 django建站教程
django框架怎么使用 django组件 在django中创建项目
</font>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容