图书管理系统文档

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
可以发现

  1. 会自动在media目录下面创建book_img目录,然后将上传的图片保存这个文件夹下面,图片的名字是上传时候的名字。
  2. 如果上传了同样文件名的图片,此时在后台会自动对图片名字进行修改。

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

  1. 只设置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

注意

  1. book.bookpdf获取的是Django的一个FieldFile类型的数据,如果想要获取文件的名字,需要使用book.bookpdf.name.
  2. 通过拼接得到文件的绝对路径,然后进行下载。
  3. 通过上面的代码目前测试了两种文件内容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. 增加日期选择器

在前端界面输入日期的时候增加一个日期选择器,目前找到了如下两种方法:

  1. 设置html中的input类型date。here 这种方法可以通过在forms.py文件中对字段增加widget属性,在属性中设置改标签的属性。
  2. 使用js代码。 here