Django-图书管理系统-图书增删改
1. 显示图书信息列表
1.1. 图书Model
包含的字段有:图书编号、书名、作者、出版社、出版日期、单价、当前数量、总数量、购买日期、内容摘要、类别。
models.py里面的内容如下:
class Book(models.Model): num = models.CharField(max_length=15, primary_key=True) name = models.CharField(max_length=20) author = models.CharField(max_length=20) publication = models.CharField(max_length=20) pub_date = models.DateField(auto_now_add=True) price = models.SmallIntegerField() current_count = models.SmallIntegerField() total_count = models.SmallIntegerField() buy_date = models.DateField(auto_now_add=True) abstract = models.CharField(max_length=100) type = models.CharField(max_length=20) def __str__(self): return self.name
然后在admin.py中增加下面的语句,就可以使用admin后台管理数据了。
from .models import Book admin.site.register(Book)
1.2. 显示图书列表
前端界面
book_list.html
{% for book in booklists %} <div> <p>书名: {{ book.name }}</p> <p>作者: {{ book.author }}</p> <p>出版社:{{ book.publication }}</p> <p>出版日期: {{ book.pub_date }}</p> </div> <hr> {% endfor %}
views.py代码
def bookList(request): books = Book.objects.all() context = {'booklists': books} return render(request, 'library/book_list.html', context)
2. 图片上传
ImageField方法的解释here
为了显示图书信息,需要显示书本的图片。
首先,需要安装pillow这个python图像库,Django图片方面的功能使用到了他。
pip install pillow
然后,在model中使用ImageField字段类型,如下所示:
img = models.ImageField(upload_to='book_img', default='default.png') # 需要default参数,否则会出错。default.png的用处是:当没有上传图片的时候,此时就会使用default.png作为当前记录的图片。
其中,upload_to参数表示图片上传的路径。
接着在settings.py中配置图片的路径。
# 公用URL,指向上传文件的基本路径 MEDIA_URL = '/media/' # 上传文件在服务器中的基本路径。 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
然后,可以通过admin后台管理系统上传一张图片,此时目录结构为:
-student_system
-library
-media
book_img
坟场之书.jpg
default.png
可以发现
- 会自动在media目录下面创建book_img目录,然后将上传的图片保存这个文件夹下面,图片的名字是上传时候的名字。
- 如果上传了同样文件名的图片,此时在后台会自动对图片名字进行修改。
2.1. 上传的图片重命名
在library目录下面创建一个storage.py文件,里面写入下面的逻辑。
from django.core.files.storage import FileSystemStorage class ImageStorage(FileSystemStorage): from django.conf import settings def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL): # 初始化 super(ImageStorage, self).__init__(location, base_url) # 重写 _save方法 def _save(self, name, content): # print(name, "***", content) # book_img\聊斋新义.jpg *** 聊斋新义.jpg # name为上传文件名称 import os, time, random # 文件扩展名 filename = name.split("\\")[-1] # 获取文件名,也就是客户端的文件命名 聊斋新义.jpg ext = os.path.splitext(filename)[1] # 获取文件扩展 .jpg raw_name = os.path.splitext(filename)[0] # 获取文件名 # print(raw_name) # 聊斋新义 # 文件目录 d = os.path.dirname(name) # 定义文件名,年月日时分秒随机数 fn = time.strftime('%Y%m%d%H%M%S') fn = fn + '_%d' % random.randint(10000, 99999) # 重写合成文件名 name = os.path.join(d, fn + "_"+raw_name + ext) # 将客户端的文件命名也作为服务器端文件名的一部分 # 调用父类方法 return super(ImageStorage, self)._save(name, content)
里面重命名文件名时主要是考虑当前的日期时间、随机数、和客户端的文件命名。注意_save的那么属性和content属性,通过上面的注释可以看到name为 book_img\聊斋新义.jpg ,也就是对应的设置的相对于media的相对路径。
接着,在models.py中
img = models.ImageField(upload_to='book_img', storage=ImageStorage(), default='default.png', verbose_name="图书照片")
之后,上传文件之后,在book_img目录下面的内容就是
20200325160203_11312聊斋新义.jpg
2.2. 根据年月日创建上传图片的文件夹
设置upload_to属性的值。
img = models.ImageField(upload_to='book_img/%Y/%m/%d', storage=ImageStorage(), default='default.png', verbose_name="图书照片")
上传之后的文件保存的路径
-media -book_img -2020 -3 -25 20200325160203_11312聊斋新义.jpg
2.3. MEDIA_ROOT 和 MEDIA_URL之间的区别
MEDIA_ROOT: 这个参数和ImageField里面的upload_to的值配合使用,上传的文件自动保存到os.join.path(MEDIA_ROOT, upload_to).
MEDIA_URL:用户可以通过URL访问上传的图片或者资源。比如在media文件夹下面的结构是:
-media -book_img -1.png -default.png
那么在浏览器中就可以通过http://127.0.0.1:8000/media/default.png; http://127.0.0.1:8000/media/book_img/1.png;来查看对应的资源。
2.4. STATIC_ROOT 和 STATIC_URL
参考here
STATIC_URL:和MEDIA_ROOT一样的,当设置了这个参数的值为
STATIC_URL = '/static/'
此时在浏览器中就可以通过 http://127.0.0.1:8000/static/login/css/login.css 访问应用library下面static/login/css/login.css这个文件了。
而STATIC_ROOT是在部署服务器的时候才会使用得到的参数,具体可以参考上面的连接。上面的连接也解释了static为什么放在应用下面,而media放在整个目录下面。
2.5. 设置上传文件的大小和类型
可以参考here
3. 增加图书信息
3.1. 图片上传的前端界面
首先,使用模型创建表单,语法比较简单。
class BookForm(forms.ModelForm): class Meta: model = Book fields = '__all__'
add_book.html
需要注意enctype的类型为multipart/form-data
<form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <table> {{ form.as_p }} </table> <button type="submit">上传</button> </form>
3.1.1. action参数为空时
当HTML页面处理form表单提交请求的时候,如果action中的值为空,那么这个请求将由当前页面的路径来处理。
3.1.2. enctype参数
参考here here
HTML表单中enctype中默认的是application/x-www-form-urlencoded。在使用包含文件上传控件的表单的时候,必须使用multipart/form-data.
3.2. 增加图书信息的逻辑
views.py中的代码
需要注意:这里直接对模型进行保存 form.save()实现的保存,不用一个字段的进行处理。
def addBook(request): if request.method == 'POST': form = BookForm(request.POST, request.FILES) # 注意 if form.is_valid(): form.save() return render(request, 'library/add_book.html', {'form':form}) else: form = BookForm() return render(request, 'library/add_book.html', {'form':form})
4. 修改图书信息
首先,在图书列表中显示图书的图片,然后在书名和图书的位置增加一个链接,点击链接之后跳转到详细页面,在详细页面中有“修改”的功能。
4.1. 图片显示
首先,在前端html中写入下面的语句:
book_list.html
<img src="/media/{{ book.img }}" alt="">
然后,在项目的urls.py文件中添加下面的数据
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ ... ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
此时就可以正常显示图片内容,但是需要注意:可以参考这里的分析here
- 只设置html中图片的相对路径或者绝对路径都是不可行的,因为不管是什么路径,都是一个url地址,而Django里面处理url都是要经过路由设置的,如果没有在urls.py中修改内容,那么就找不到图片。
4.1.1. 前端界面中使用MEDIA_URL
首先,使用
<img src="/media/{{ book.img }}" alt="">
这种方式是比较固定的,也就是MEDIA_URL的值为/media/的时候可行,如果修改了MEDIA_URL的值,此时就需要修改代码中的/media/,这比较麻烦,因此可以写成:
<img width="100" height="150" src="{{ MEDIA_URL }}{{ book.img }}" alt="">
但是,这样可能在前端显示不出来,需要在项目的settings.py中进行设置:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.template.context_processors.media', # 增加这一行,不同版本的Django不一样,具体的需要自己查询。 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
4.2. 增加链接(给图片和书名增加链接)
给图片和书名增加链接,点击图片之后,会新打开一个界面显示这个图片。点击书名之后,会跳转到详细页面。
book_list.html
{% for book in booklists %} <div> <a href="{{ MEDIA_URL }}{{ book.img }}" target="_blank"> <img width="100" height="150" src="{{ MEDIA_URL }}{{ book.img }}" alt=""> </a> <a href="{% url 'library:detail_book' book.num %}"> <p>书名: {{ book.name }}</p> </a> <p>作者: {{ book.author }}</p> <p>出版社:{{ book.publication }}</p> <p>出版日期: {{ book.pub_date }}</p> </div> <a href="{% url 'library:delete_book' book.num%} "> <button>删除</button> </a> <hr> {% endfor %}
4.3. 书本详细信息界面
首先,增加一个html界面detail_book.html.
<form action="" method="post"> {% csrf_token %} <label for="">编号</label><input type="text" value="{{ book.num }}" readonly> <label for="">书名</label><input type="text" value="{{ book.name }}" readonly> <label for="">作者</label><input type="text" value="{{ book.author }}" readonly> <label for="">现存数量</label><input type="text" value="{{ book.current_count }}"> <label for="">共有数量</label><input type="text" value="{{ book.total_count }}"> <button type="submit">完成修改</button> </form>
使用input呈现数据,设置readonly属性让部分属性只可读;而可以修改现存数量和共有数量两个属性。因为修改之后,需要上传到后台服务器,所以,需要使用表单。
4.4. 修改书本信息
然后,在views.py中增加逻辑处理
def detailBook(request, book_id): """这里的book_id需要和路由最后面的参数是一致的""" book = Book.objects.get(num=book_id) if request.method == 'POST': # 获取修改之后当前数量和总数量 current_count = request.POST['current_count'] total_count = request.POST.get('total_count') # 更新数据 book.current_count = current_count book.total_count = total_count book.save() print("数据已经更新完成") # 根据book的num获取该对象。 return render(request, 'library/detail_book.html', {'book': book})
需要注意,当前端界面是自己手写的HTML的时候,应该如何获取数据。
接着,在urls.py中增加路由
path('detail/<book_id>/', views.detailBook, name='detail_book')
5. 删除图书
首先,在views.py中增加删除图书的逻辑:
def deleteBook(request, book_id): Book.objects.filter(num=book_id).delete() books = Book.objects.all() context = {'booklists': books} return render(request, 'library/book_list.html', context)
根据num来删除。
然后,在book_list.html中增加一个删除按钮。
<a href="{% url 'library:delete_book' book.num%} "> <button>删除</button> </a>
需要注意,一般是使用JS代码实现点击按钮的功能,然后向服务器发送请求,最后在服务器删除数据。
最后,配置路由urls.py
path('delete/<book_id>/', views.deleteBook, name="delete_book")
6. 上传图书文件
上传文件参考here
6.1. 修改模型
models.py文件中增加上传PDF的文件字段
class Book(models.Model): num = models.CharField(max_length=15, primary_key=True, verbose_name="编号") name = models.CharField(max_length=20, verbose_name="书名") ... img = models.ImageField(upload_to='book_img/%Y/%m/%d', storage=ImageStorage(), default='default.png', verbose_name="图书照片") bookpdf = models.FileField(upload_to='book_pdf', default='default.pdf', verbose_name="图书pdf")
其余内容不用修改,此时上传一个pdf文件之后,在media文件夹下面会自动创建一个book_pdf的文件夹并把上传的文件保存到该位置。
如果要实现上传多个文件的功能可以参考here
这里没有实现文件上传的处理逻辑,具体的处理逻辑也可以参考上面的这个链接。
7. 下载图书文件
首先,在需要在图书列表中显示一个增加一个下载按钮,当点击该按钮之后,就会下载该图书文件。
7.1. 前端显示
{% for book in booklists %} ... <a href="{% url 'library:delete_book' book.num%} "> <button>删除</button> </a> <a href="{% url 'library:download_book' book.num%}">下载</a> <hr> {% endfor %}
在book_list.html中增加下载的按钮,同时在url路由中增加下载文献的处理路由。
urlpatterns = [ ... path('download/<book_id>/', views.downloadBook, name="download_book") ]
7.2. 下载图书的处理逻辑
图书下载处理逻辑:
def downloadBook(request, book_id): book = Book.objects.get(num=book_id) # 获取文件的路径 # print(type(book.bookpdf)) #<class 'django.db.models.fields.files.FieldFile'> import os from django.conf import settings book_path = os.path.join(settings.MEDIA_ROOT, book.bookpdf.name) # 下载文件 file = open(book_path, 'rb') from django.http import FileResponse response = FileResponse(file) response['Context-Type'] = 'application/cotet-stream' response['Context-Disposition'] = 'attachment;filename="aaa.pdf"' return response
注意
- book.bookpdf获取的是Django的一个FieldFile类型的数据,如果想要获取文件的名字,需要使用book.bookpdf.name.
- 通过拼接得到文件的绝对路径,然后进行下载。
- 通过上面的代码目前测试了两种文件内容pdf和zip文件;针对pdf文件,点击下载按钮的时候会在浏览器显示pdf的内容;如果是zip文件,点击下载按钮之后,则会直接下载内容。
7.2.1. 不预览直接下载
在html端进行修改。首先,href指向的是图书在服务器的URL地址,使用download参数指定文件下载之后的文件名。
<a href="{{ MEDIA_URL }}{{ book.bookpdf.name }}" download="{{ book.bookpdf.name }}">下载1</a>
8. 增加日期选择器
在前端界面输入日期的时候增加一个日期选择器,目前找到了如下两种方法: