django小练习-图书管理功能

django小练习-图书管理功能

  1. 功能需求(只实现了很基础的功能,前端界面还没搞):

    1. 能显示数据库中图书的基本信息:id、题目、出版社、价格、零售价;
    2. 支持对图书信息的修改和删除。
    3. 支持分页功能,在这个练习中,因为自己编的数据量少,所以每页只显示两本书。
    4. 支持导出功能。能将当前页的数据以csv文件的格式导出。
  2. 开发:

    1. 准备工作:

      1. 创建项目:django-admin startproject 项目名;

      2. 创建应用:cd到项目文件夹下:python manage.py startapp 应用名;

      3. 注册应用:在项目文件夹下的settings.py文件夹中,INSTALLED_APPS变量中添加应用名’bookstore’

      4. 创建数据库并进行配置:

        1. 创建:mysql -uroot -p进入mysql,使用命令:create database 数据库名 default charset utf8创建数据库。
        2. 在项目文件夹下的settings.py文件中配置变量DATABASES:
          DATABASES = {
              'default': {
                  'ENGINE': 'django.db.backends.mysql',
                  'NAME': 'mysite3',
                  'USER': 'root',
                  'PASSWORD': '你的数据库密码',
                  'HOST': '127.0.0.1',
                  'PORT': '3306'
              }
          }
          
      5. 语言和时区的配置:也是在settings.py文件夹中,

        LANGUAGE_CODE = 'zh-Hans'
        
        TIME_ZONE = 'Asia/Shanghai'
        
      6. 模板文件夹:在应用文件夹下手动创建templates文件夹,为防止路由出错,在templates文件夹下再创建一个应用同名的文件夹 bookstore,在这个文件夹中写模板文件,也就是html文件。

      7. 配置分布式路由:

        1. 先配置主路由:在项目文件夹下的urls.py文件中添加:

          from django.urls import path,include
          path('bookstore/',include('bookstore.urls'))
          
        2. 配置分路由:在应用文件夹下手动创建urls.py文件,与这个应用有关的路由写在这个文件中,结构和主路由文件一样。 最终张下面这样:

          from django.urls import path
          from . import views
          
          #主路由匹配前边,子路由匹配具体的后边
          urlpatterns = [
              path('all_book',views.all_book),
              # url采用path转换器的形式进行操作
              path('update_book/<int:book_id>',views.update_book),
              path('delete_book',views.delete_book),
              path('output_csv',views.output_csv)
          ]
          
    2. 功能开发:

      1. 创建表:

        1. 创建模型类
            django是MTV框架,M层就是和数据库的表打交道的地方,在models.py文件中编写的模型类,通过数据库迁移就能生成相应的表:

          from django.db import models
          
          # Create your models here.
          
          class Book(models.Model):
              #根据ORM的映射规则,一个类实际上就是一张表(表名=应用名_类名),类中的每个属性就对应着表中的一个字段
              title = models.CharField('书名',max_length=50,default='',unique=True)
              pub = models.CharField('出版社',max_length=100,default='')
              price = models.DecimalField('价格',max_digits=7,decimal_places=2)
              #任何关于表结构的修改,务必在对应模型类上修改
              market_price = models.DecimalField('零售价',max_digits=7,decimal_places=2,default=0.0)
              is_active = models.BooleanField('是否活跃',default=True)
              # 改变当前模型类对应的表的名字
              class Meta:
                  db_table = 'book'
              def __str__(self):
                  return '%s-%s-%s-%s'%(self.title,self.price,self.pub,self.market_price)
          
        2. 进行数据库迁移:
            cd到项目文件夹,在终端中输入:python manage.py makemigrationspython manage.py migrate,进入数据库进行检查:use 数据库名; show tables;就能看见通过模型类创建的表。可以使用desc 表明;命令查看表的结构。

        3. 创建数据:
            可以在django shell中(使用python manage.py shell命令启动,最好是先pip install ipyhton,注意项目代码发生变化时,要重新进入django shell)使用模型类.objects.create()语句创建数据。(记得要先引入模型类from 应用名.models import 模型类名

      2. 显示图书信息和分页机制;

        1. 视图函数:

          引入用到的库文件:

          from django.core.paginator import Paginator
          from django.http import HttpResponse, HttpResponseRedirect
          from django.shortcuts import render
          import csv
          #引入模型类
          from .models import Book
          

          all_book函数:
            在这个数据时代,一般情况下,数据不会真正的删除,因此我们使用伪删除。在数据库中,创建一个is_active的字段,默认是True,表明这条数据是未被删除的。当要删除这本书时,点击“删除”操作,实际上进行的操作是将该记录的is_active字段设置为False。
            因此在显示图书数据时,使用的是filter(is_active=True)而不是all(),有针对性的将is_active字段为True的数据筛选出来进行显示。

          def all_book(request):
              #在视图中要做的就是将书全查出来,然后发送给视图进行渲染
              #在这里用filter(is_active=True)进行筛选,就能巧妙实现删除了数据时候,该数据不再被显示的要求
              all_book = Book.objects.filter(is_active=True)
          
              # /bookstore/all_book?page=4
              # 拿到当前的页码,没有的话就默认是1
              page_num = request.GET.get('page', 1)
          
              # 初始化paginator
              # 第一个参数是总的数据,第二个参数是每页显示的条数
              paginator = Paginator(all_book, 2)
              # 初始化具体页码对应的page对象,管具体的每一页
              current_page = paginator.page(int(page_num))
          
              return render(request,'bookstore/all_book.html',locals()) #locals可以将函数内的局部变量以字典的形式传进render中。
          

            分页功能使用的是pagnitor类和pagnitor对象。使用paginator = Paginator(all_book, 2)初始化pagnitor对象,其中第一个参数是需要分页的全部数据,第二个参数是每页显示数据的条数。通过page函数拿到当前页的信息:current_page = paginator.page(int(page_num))参数就是当前的页数。在编写模板文件时,我们将url写成参数的形式<a href="/bookstore/all_book?page={{ page_num }}">{{ page_num }}</a>,这样每次点击“上一页”或者“下一页”的时候,都能把page的数据通过get请求传给视图函数,在视图函数中使用page_num = request.GET.get('page', 1)就能拿到page。使用get方法相对友好,默认没有page信息的时候返回的值是1。
            current_page这个数据很重要,它包含的就是当前页面应该显示的数据信息。前端页面拿数据的话就用的它。

        2. 模板文件(html文件):

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>所有书籍</title>
          </head>
          <body>
          <P>
              <a href="/bookstore/output_csv?page={{ current_page.number }}">导出</a>
          </P>
          <table border="'1">
              <tr>
                  <th>id</th>
                  <th>title</th>
                  <th>pub</th>
                  <th>price</th>
                  <th>market_price</th>
                  <th>op</th>
              </tr>
              {% for book in current_page %}
                  <tr>
                      <td>{{ book.id }}</td>
                      <td>{{ book.title }}</td>
                      <td>{{ book.pub }}</td>
                      <td>{{ book.price }}</td>
                      <td>{{ book.market_price }}</td>
                      <td>
                          <a href="/bookstore/update_book/{{ book.id }}">更新</a>
                          {#点击a标签,发个get,拿到页面;然后修改数据,点击更新,就要给后台提交一些数据,这里就是POST#}
                          <a href="/bookstore/delete_book?book_id={{ book.id }}">删除</a>
                      </td>
                  </tr>
              {% endfor %}
          </table>
          
          {% if current_page.has_previous %}
              <a href="/bookstore/all_book?page={{ current_page.previous_page_number }}">上一页</a>
          {% else %}
              上一页
          {% endif %}
          
          {#当前页显示页码,非当前页显示a标签#}
          {% for page_num in paginator.page_range %}
              {% if page_num == current_page.number %}
                  {{ page_num }}
              {% else %}
                  <a href="/bookstore/all_book?page={{ page_num }}">{{ page_num }}</a>
              {% endif %}
          {% endfor %}
          
          
          {% if current_page.has_next %}
              <a href="/bookstore/all_book?page={{ current_page.next_page_number }}">下一页</a>
          {% else %}
              下一页
          {% endif %}
          
          </body>
          </html>
          
          {#T层通过视图作为桥梁,拿到m层的数据进行渲染,使用if for等关键字的时候是{%  %},记得在最后end,使用视图传过来的参数时{{  }}#}
          

          分页功能由下面这段代码实现,稍微修改变量的名称就可以复用:

          {% if current_page.has_previous %}
              <a href="/bookstore/all_book?page={{ current_page.previous_page_number }}">上一页</a>
          {% else %}
              上一页
          {% endif %}
          
          {#当前页显示页码,非当前页显示a标签#}
          {% for page_num in paginator.page_range %}
              {% if page_num == current_page.number %}
                  {{ page_num }}
              {% else %}
                  <a href="/bookstore/all_book?page={{ page_num }}">{{ page_num }}</a>
              {% endif %}
          {% endfor %}
          
          
          {% if current_page.has_next %}
              <a href="/bookstore/all_book?page={{ current_page.next_page_number }}">下一页</a>
          {% else %}
              下一页
          {% endif %}
          

          使用{% for book in current_page %}就可以循环取到current_page中的数据,也就是要显示的数据的信息。
          django的MTV结构实际上就是:T层通过视图函数V层作为桥梁,拿到M层的数据进行渲染。
          使用视图函数传过来的参数时,用{{ }},使用if for等关键字的时候,用{% %},记得后边跟上{% endif %}等结束标签。

        3. 路由:在应用的urls.py文件中加入path('all_book',views.all_book),使用的是分布式路由,所以只用管bookstore后边的就行。

      3. 修改信息功能:

        1. 路由:在应用文件夹的urls.py文件中加入path('update_book/<int:book_id>',views.update_book),使用path转换器拿到要修改的书本的id号book_id,并将其传给视图函数。

        2. 视图函数:
            通过path转换器,视图函数就能接收到book_id这个参数,拿到要修改的书的id。
          使用模板类.objects.get()方法时,尽量在try里面,因为get方法比较“霸道”,如果查到有0个或者多个符合条件的结果的话就会报错,导致程序终止。
            获取指定书的信息(get的时候得查书的信息,因为得给render的页面传过去用于显示,post的时候也得查,因为修改单个数据是一查二改三保存)
            也得分get和post两种情况进行处理:点击“修改”按钮时,发送的是get请求,这个时候负责的就是render一个页面,并且把这本书的信息传过去,因为得现在这本书修改之前的数据信息。在修改页面点击“提交”时,发送的是post请求,这时候就得处理数据了。通过request.POST[]方法拿到form表单传过来的数据,对之前查到的书的信息进行修改,记得改完之后要使用save函数进行保存book.save()。彻底修改完之后,再302跳转回all_book页面,形成一个闭环return HttpResponseRedirect('/bookstore/all_book')

          def update_book(request,book_id):
              # 书是通过path转换器过来,path('update_book/<int:book_id>',views.update_book),会将参数book_id传给视图函数。也就是路由中会带着,比如:bookstore/update_book/1,结尾就是id。
              #先获取指定书的信息(get的时候得查书的信息,因为得给render的页面传过去用于显示,post的时候也得查,因为修改单个数据是一查二改三保存)
              try:
                  book = Book.objects.get(id = book_id,is_active=True)
              except Exception as e:
                  print('--update book error is %s'%(e))
                  return HttpResponse('--The book is not existed')
              # get拿页面,post处理数据
              if request.method == 'GET':
                  return render(request,'bookstore/update_book.html',locals()) #得把书的信息传进去,因为得显示。
              elif request.method == 'POST':
                  price = request.POST['price']
                  market_price = request.POST['market_price']
                  #改
                  book.price = price
                  book.market_price = market_price
                  #保存
                  book.save()
                  #跳转回all_book页面,形成一个闭环
                  return HttpResponseRedirect('/bookstore/all_book')
          
        3. 模板文件:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>更改书籍</title>
          </head>
          <body>
          {#action是负责表单的这个数据具体要提交到哪里去,还提交给这个地址#}
          <form action="/bookstore/update_book/{{ book_id }}" method="post">
              <p>
                  title <input type="text" value="{{ book.title }}" disabled="disabled">
              </p>
              <p>
                  pub <input type="text" value="{{ book.pub }}" disabled="disabled">
              </p>
              <p>
                  price <input type="text" name="price" value="{{ book.price }}">
              </p>
              <p>
                  market_price <input type="text" name="market_price" value="{{ book.market_price }}">
              </p>
              <p>
                  <input type="submit" value="更新">
              </p>
          </form>
          </body>
          </html>
          
          {#点击了a标签,根据路由找到views中的update_book方法,同时根据发送的get请求获取id号(在all_book中将book.id传给views中的参数book_id),#}
          {#然后在if .. get中render一个update_book的界面,并把id传给给页面,用于显示url。修改完数据之后点击提交,也就是一个post请求,之前在views中的#}
          {#update_book方法中已经根据book_id使用get方法拿到了书的信息,因此就可以根据POST拿到的数据,使用对象.属性的方式对数据库中的数据进行修改,也就是一查二改三保存,#}
          {#这样就能实现对数据库中的数据进行修改。#}
          
      4. 删除信息功能:在这个案例中,我们使用的是伪删除:在数据库中增设一个is_active字段,默认是True,也就是要显示的数据。“删除”了的数据就将该条记录的is_active字段设置为False。显示数据的时候,使用的是filter函数,把is_active字段为True的数据筛选出来进行显示。

        1. 路由:在应用文件夹的urls.py文件中加入path('delete_book',views.delete_book),
        2. 视图函数:
          “删除”按钮出的链接是<a href="/bookstore/delete_book?book_id={{ book.id }}">删除</a>,通过参数将要删除的书的id传给视图函数。
          def delete_book(request):
              #通过获取查询字符串book_id拿到要删除的book的id,将其is_active改为False,然后302跳转至all_book.
              # 先做视图,再做页面,再绑定路由
          
              #拿到要删除的书的id
              book_id = request.GET.get('book_id')
              if not book_id:
                  return HttpResponse('---请求异常')
              # 查询要删的书的信息
              try:
                  book = Book.objects.get(id=book_id,is_active=True)
              except Exception as e:
                  print('---delete book get error %s'%(e))
                  return HttpResponse('--The book id is error')
              #将其is_active改成False
              book.is_active = False
              book.save() #因为伪删除相当于修改,所以一查二改三保存,要记得保存
              #302跳转至all_book
              return HttpResponseRedirect('/bookstore/all_book')
          
      5. 导出功能:

        1. 路由:path('output_csv',views.output_csv)
        2. 视图函数:
          def output_csv(request):
          
              # 在这里用filter(is_active=True)进行筛选,就能巧妙实现删除了数据时候,该数据不再被显示的要求
              all_book = Book.objects.filter(is_active=True)
          
              # 拿到当前的页码,没有的话就默认是1
              page_num = request.GET.get('page', 1)
          
              # 初始化paginator
              # 第一个参数是总的数据,第二个参数是每页显示的条数
              paginator = Paginator(all_book, 2)
              # 初始化具体页码对应的page对象,管具体的每一页
              current_page = paginator.page(int(page_num))
          
              response = HttpResponse(content_type='text/csv')
              response['Content-Disposition'] = 'attachment;filename="myBookOnPage%s"'%(page_num)
          
              writer = csv.writer(response)
          
              writer.writerow(['id','title','pub','price','market_price'])
          
              for book in current_page:
                  writer.writerow([book.id,book.title,book.pub,book.price,book.market_price])
          
              return response
          
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值