django小练习-图书管理功能
-
功能需求(只实现了很基础的功能,前端界面还没搞):
- 能显示数据库中图书的基本信息:id、题目、出版社、价格、零售价;
- 支持对图书信息的修改和删除。
- 支持分页功能,在这个练习中,因为自己编的数据量少,所以每页只显示两本书。
- 支持导出功能。能将当前页的数据以csv文件的格式导出。
-
开发:
-
准备工作:
-
创建项目:django-admin startproject 项目名;
-
创建应用:cd到项目文件夹下:python manage.py startapp 应用名;
-
注册应用:在项目文件夹下的settings.py文件夹中,INSTALLED_APPS变量中添加应用名’bookstore’
-
创建数据库并进行配置:
- 创建:mysql -uroot -p进入mysql,使用命令:create database 数据库名 default charset utf8创建数据库。
- 在项目文件夹下的settings.py文件中配置变量DATABASES:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mysite3', 'USER': 'root', 'PASSWORD': '你的数据库密码', 'HOST': '127.0.0.1', 'PORT': '3306' } }
-
语言和时区的配置:也是在settings.py文件夹中,
LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai'
-
模板文件夹:在应用文件夹下手动创建templates文件夹,为防止路由出错,在templates文件夹下再创建一个应用同名的文件夹 bookstore,在这个文件夹中写模板文件,也就是html文件。
-
配置分布式路由:
-
先配置主路由:在项目文件夹下的urls.py文件中添加:
from django.urls import path,include path('bookstore/',include('bookstore.urls'))
-
配置分路由:在应用文件夹下手动创建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) ]
-
-
-
功能开发:
-
创建表:
-
创建模型类
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)
-
进行数据库迁移:
cd到项目文件夹,在终端中输入:python manage.py makemigrations
和python manage.py migrate
,进入数据库进行检查:use 数据库名;
show tables;
就能看见通过模型类创建的表。可以使用desc 表明;
命令查看表的结构。 -
创建数据:
可以在django shell中(使用python manage.py shell命令启动,最好是先pip install ipyhton,注意项目代码发生变化时,要重新进入django shell)使用模型类.objects.create()
语句创建数据。(记得要先引入模型类from 应用名.models import 模型类名
)
-
-
显示图书信息和分页机制;
-
视图函数:
引入用到的库文件:
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这个数据很重要,它包含的就是当前页面应该显示的数据信息。前端页面拿数据的话就用的它。 -
模板文件(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 %}等结束标签。 -
路由:在应用的urls.py文件中加入
path('all_book',views.all_book),
使用的是分布式路由,所以只用管bookstore后边的就行。
-
-
修改信息功能:
-
路由:在应用文件夹的urls.py文件中加入
path('update_book/<int:book_id>',views.update_book),
使用path转换器拿到要修改的书本的id号book_id,并将其传给视图函数。 -
视图函数:
通过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')
-
模板文件:
<!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拿到的数据,使用对象.属性的方式对数据库中的数据进行修改,也就是一查二改三保存,#} {#这样就能实现对数据库中的数据进行修改。#}
-
-
删除信息功能:在这个案例中,我们使用的是伪删除:在数据库中增设一个is_active字段,默认是True,也就是要显示的数据。“删除”了的数据就将该条记录的is_active字段设置为False。显示数据的时候,使用的是filter函数,把is_active字段为True的数据筛选出来进行显示。
- 路由:在应用文件夹的urls.py文件中加入
path('delete_book',views.delete_book),
- 视图函数:
“删除”按钮出的链接是<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')
- 路由:在应用文件夹的urls.py文件中加入
-
导出功能:
- 路由:
path('output_csv',views.output_csv)
- 视图函数:
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
- 路由:
-
-