本案例主要演示Django的路由、视图函数、视图类、Orm、表单以及用户和权限。采用Django3+BootStrap技术栈。为了简化难度,仅仅使用了很少的JavaScript。全网不可多得得Django学习资料
简单的需求分析
效果演示
项目开发详细过程
使用Win10+PyCharm+Python3.8+Django3.1.5+SQLite+Navicat下开发。
项目及其应用的建立
E:\edu\Django从入门到项目实战>django-admin startproject mybook
E:\edu\Django从入门到项目实战\mybook>python manage.py startapp app1
准备工作
修改settings.py文件
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app1',
]
配置静态文件、上传资源和登录url跳转
STATIC_URL = '/static/'
STATICFILES_DIRS=[os.path.join(BASE_DIR,'static')]
MEDIA_URL = '/media/'
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
LOGIN_URL="/book/login/"
修改mybook/urls.py文件
urlpatterns = [
path('admin/', admin.site.urls),
path('book/',include("app1.urls")),
re_path('media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
re_path('static/(?P<path>.*)', serve, {"document_root": settings.STATIC_ROOT}),
模型建立
app1\models.py文件
from django.db import models
class Author(models.Model):
a_id = models.AutoField(verbose_name='ID', primary_key=True)
name = models.CharField(verbose_name='作者姓名', max_length=20)
age = models.IntegerField(verbose_name='作者年龄', default=1)
mobile = models.CharField(verbose_name='电话', max_length=11)
address = models.CharField(verbose_name='地址', max_length=20)
intro = models.TextField(verbose_name='简介', null=True, blank=True)
class Publish(models.Model):
p_id = models.AutoField(verbose_name='ID', primary_key=True) # 主键
name = models.CharField(verbose_name='出版社名称', max_length=32)
address = models.CharField(verbose_name='出版社地址', max_length=64)
intro = models.CharField(verbose_name='出版社简介', null=True, blank=True, max_length=500)
class Book(models.Model):
b_id = models.AutoField(verbose_name='ID', primary_key=True)
name = models.CharField(verbose_name='书籍名称', max_length=50)
isbn = models.CharField(verbose_name='ISBN', max_length=50)
photo = models.ImageField(verbose_name='书籍封面', upload_to='imgs')
price = models.DecimalField(verbose_name='价格', max_digits=5, decimal_places=2)
publish = models.ForeignKey(to='Publish', to_field='p_id', on_delete=models.DO_NOTHING)
author = models.ManyToManyField(to="Author", db_table='book_author')
使用SQLite数据库
SQLite是一款基于内存或者文件的、开源的、关系型的轻量级数据库。SQLite的移植性非常好,很容易使用,小巧、高效、可靠。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。
SQLite数据库的目标就是尽量简单,抛弃了传统企业数据库的复杂特性,只实现了数据库的必备功能。虽然简单,但是功能和性能都非常出色。SQLite拥有完成的、自包含的数据库引擎。
SQLite数据库的一大特色就是在程序内部不需要网络配置和管理,没有管理员账户的概念,权限仅仅依赖于文件系统。
SQLite本身没有提供管理数据库的图形化界面,在不熟悉命令行的情况下,可以考虑通过一款图形化界面来完成管理工作,这里,推荐使用Navicat。
模型迁移
python manage.py makemigrations
python manage.py migrate
前端bootstrap框架介绍
Bootstrap
Bootstrap 是美国 Twitter 公司的设计师 Mark Otto 和 Jacob Thornton 合作基于 HTML、CSS、JavaScript 开发的简洁、直观、强悍的前端开发框架,使得 Web 开发更加快捷。Bootstrap 提供了优雅的 HTML 和 CSS 规范,它即是由动态 CSS 语言 Less 写成。(重点是响应式(随屏幕大小变化),能适应各种各种设备)
https://www.bootcss.com/
Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架。Bootstrap 是基于 HTML、CSS、JAVASCRIPT 的。
AdminLTE
AdminLTE 是受欢迎的开源的管理仪表盘和控制面板的 WebApp 模板。它是基于 Bootstrap 的 CSS 框架,反应灵敏的 HTML 模板。利用所有 Bootstrap 的组件对大部分使用插件进行设计和调整风格,创建出可以用作后端应用程序的用户界面一致性设计。AdminLTE 是基于模块化设计,很容易在其之上定制和重制。
项目的页面是基于 AdminLTE 的模板来改造的,而 AdminLTE 又是基于 Bootstrap 框架,所以后续如果有新功能新样式要加,可以直接在 AdminLTE 官网找案例,或者 Bootstrap 官网找案例,都可以直接使用。
官网下载 : https://adminlte.io/
功能开发
出版社的列表
模板文件:
{% extends "base.html" %}
{% load static %}
{% block title %}
<title>出版社列表</title>
{% endblock %}
{% block content %}
<div class="content-wrapper">
<div class="content-header">
<div class="container-fluid">
<div>
<div class="row">
<div class="col-sm-12">
<h1 class="m-0">
出版社模块
<small>列表</small>
</h1>
</div>
</div>
</div>
</div>
</div>
<!--内容开始-->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-12 search-collapse">
<form id="search_form" method="post">
{% csrf_token %}
<div class="form-group row">
<label for="inputtext" class="col-form-label">出版社名称:</label>
<div class="col-md-4">
<input type="text" id="search_name" name="name" class="form-control"/>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-info"><i class="fa fa-plus"></i>查询</button>
<a class="btn btn-primary single" href="{% url 'pub_add' %}">
<i class="fa fa-plus"></i> 新增
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</section>
<div class="col-sm-12 main">
<br>
<div class="panel panel-primary">
<div class="panel-body">
<table class="table table-bordered table-condensed table-striped table-hover">
<thead>
<tr>
<th>序号</th>
<th>出版社名称</th>
<th>出版社地址</th>
<th>功能操作</th>
</tr>
</thead>
<tbody>
{% for per in pubs %}
<tr>
<td>{{ per.p_id }}</td>
<td>{{ per.name }}</td>
<td>{{ per.address }}</td>
<td width="20%">
<a class="btn btn-primary single" href="{% url 'pub_edit' per.p_id %}">
<i class="fa fa-edit"></i> 修改
</a>
<a class="btn btn-danger" href="javascript:void(0)" onclick="showDeleteModal(this)">删除</a>
<input type="hidden" id="id_hidden" value={{ per.p_id }}>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7">无相关记录!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<nav aria-label="Contacts Page Navigation">
<ul class="pagination justify-content-center m-2">
{% if pubs.has_previous %}
<li class="page-item">
<a class="page-link"
href="{% url 'pub_list' %}?page={{ pubs.previous_page_number }}&name={{ name }}">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% for pg in pubs.paginator.page_range %}
{% if pubs.number == pg %}
<li class="page-item active">
<a class="page-link" href="">{{ pg }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link"
href="{% url 'pub_list' %}?page={{ pg }}&name={{ name }}</a>
</li>
{% endif %}
{% endfor %}
{% if pubs.has_next %}
<li class="page-item">
<a class="page-link"
href="{% url 'pub_list' %}?page={{ pubs.next_page_number }}&name={{ name }}">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
<!-- 信息删除确认 -->
<div class="modal fade" id="delModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" style="float:left">提示信息</h4>
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p id="info">您确认要删除当前数据吗?</p>
<input type="hidden" id="del_id">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<a id="delButton" class="btn btn-success" data-dismiss="modal">确定</a>
</div>
</div>
</div>
</div>
<script>
// 打开模态框并设置需要删除的ID
function showDeleteModal(obj) {
var $tds = $(obj).parent().children();// 获取到删除元素的所在列
var delete_id = $($tds[2]).val();// 获取隐藏控件的ID
console.log(delete_id)
$("#del_id").val(delete_id);// 给模态框中需要删除的ID赋值
$("#delModal").modal({
backdrop: 'static',
keyboard: false
});
};
$(function () {
// 模态框的确定按钮的点击事件
$("#delButton").click(function () {
var id = $("#del_id").val();
console.log("del" + id)
// ajax异步删除
$.ajax({
url: "/users/del/" + id + "/",
type: "GET",
success: function (result) {
if (result.code == "200") {
$("#delModal").modal("hide");
//window.location.href = "/users/index/";
}
}
})
});
});
</script>
{% endblock %}
视图函数:
@permission_required("publish_view")
@login_required
def publish_list(request):
if request.method == "GET":
pubs = Publish.objects.all()
return render(request, "publish/index.html", {'pubs': pubs})
if request.method == "POST":
name = request.POST.get("name")
if name:
pubs = Publish.objects.filter(name__contains=name)
else:
pubs = Publish.objects.all()
return render(request, "publish/index.html", {'pubs': pubs})
出版社的新增
模板页面:
{% extends "base.html" %}
{% load static %}
{% block ext_title %}
<title>出版社信息新增</title>
{% endblock %}
{% block content %}
<div class="content-wrapper">
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-12">
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="#">出版社管理</a></li>
<li class="breadcrumb-item active">出版社增加</li>
</ol>
</div>
</div>
</div>
</section>
<section class="content">
<div class="container-fluid">
<div class="row">
<!-- left column -->
<div class="col-md-12">
<!-- Horizontal Form -->
<div class="card card-info">
<div class="card-header">
<h3 class="card-title">出版社信息增加</h3>
</div>
<!-- /.card-header -->
<!-- form start -->
<form class="form-horizontal" method="post" novalidate>
{% csrf_token %}
<div class="card-body">
<div class="form-group row">
<!--<label for="inputtext class="col-sm-2 col-form-label">出版社名称</label>-->
<label for="{{ form.name.id_for_label }}"
class="col-sm-2 col-form-label">{{ form.name.label }}</label>
<div class="col-sm-10">
<!--<input type="text" class="form-control" id="name" name="name" placeholder="出版社名称">-->
{{ form.name }}
<span style="color:red">{{ errors.name.0 }}</span>
</div>
</div>
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">出版社地址</label>
<div class="col-sm-10">
<!--<input type="text" class="form-control" id="address" name="address" placeholder="出版社地址">-->
{{ form.address }}
<span style="color:red">{{ errors.address.0 }}</span>
</div>
</div>
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">出版社简介</label>
<div class="col-sm-10">
{{ form.intro }}
{# <textarea class="form-control" rows="3" id="intro" name="intro" placeholder="出版社简介"></textarea>#}
</div>
</div>
</div>
<!-- /.card-body -->
<div class="card-footer">
<button type="submit" class="btn btn-info">保存</button>
<button type="button" class="btn btn-default"
onclick="javascript:window.location='{% url "pub_list" %}'">返回
</button>
<span style="color:red">{{ info }}</span>
</div>
<!-- /.card-footer -->
</form>
</div>
<!-- /.card -->
</div>
<!--/.col (right) -->
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</section>
</div>
{% endblock %}
视图函数:
@login_required
def publish_add(request):
if request.method == "GET":
f = PublishForm()
return render(request, "publish/add.html", {"form": f})
elif request.method == "POST":
f = PublishForm(request.POST)
if f.is_valid():
name = f.cleaned_data.get("name")
vname = Publish.objects.filter(name=name)
if vname:
info = "出版社名称已经存在,请查询"
return render(request, "publish/add.html", {'form': f, 'info': info})
else:
Publish.objects.create(**f.cleaned_data)
return redirect(reverse("pub_list"))
else:
errors = f.errors
return render(request, "publish/add.html", {'form': f, 'errors': errors})
出版社的修改
视图函数
@login_required
def publish_edit(request, pid):
if request.method == "GET":
pub_obj = Publish.objects.filter(p_id=pid).first()
f = PublishForm({
"name": pub_obj.name,
"address": pub_obj.address,
"intro": pub_obj.intro,
})
return render(request, "publish/edit.html", {'form': f})
elif request.method == "POST":
f = PublishForm(request.POST)
pub_item = Publish.objects.filter(p_id=pid).first()
if f.is_valid():
pub = Publish.objects.get(p_id=pid)
pub.name = f.cleaned_data.get("name")
pub.address = f.cleaned_data.get("address")
pub.intro = f.cleaned_data.get("intro")
pub.save()
return redirect(reverse("pub_list"))
else:
errors = f.errors
return render(request, "publish/edit.html", {'form': f, 'errors': errors})
出版社的删除
作者的列表
视图函数:
@login_required
def author_list(request):
if request.method == "GET":
datas = Author.objects.all().order_by("-a_id")
page_size = 5
page = request.GET.get("page", 1)
pages = Paginator(datas, page_size)
authors = pages.page(page)
return render(request, "author/index.html", {'authors': authors})
if request.method == "POST":
name = request.POST.get("name")
mobile = request.POST.get("mobile")
search = dict()
if name:
search["name__contains"] = name
if mobile:
search["mobile"] = mobile
datas = Author.objects.filter(**search).order_by("-a_id")
page_size = 5
page = request.POST.get("page", 1)
pages = Paginator(datas, page_size)
authors = pages.page(page)
print(authors)
return render(request, "author/index.html", {'authors': authors})
图书的列表
视图函数:
@login_required
def book_list(request):
if request.method == "GET":
f = BookForm()
datas = Book.objects.all().order_by("-b_id")
page_size = 5
page = request.GET.get("page", 1)
pages = Paginator(datas, page_size)
books = pages.page(page)
return render(request, "book/index.html", {'form': f, 'books': books})
if request.method == "POST":
p_id = request.POST.get("publish", '0')
name = request.POST.get("name")
search = dict()
if name:
search["name__contains"] = name
if p_id:
search["publish_id"] = p_id
datas = Book.objects.filter(**search).order_by("-b_id")
f = BookForm({
"name": name,
"publish": p_id,
})
page_size = 5
page = request.POST.get("page", 1)
pages = Paginator(datas, page_size)
books = pages.page(page)
context = {
"name": name,
"publish": p_id,
"books": books,
"form": f,
}
print(context)
return render(request, "book/index.html", context=context)
图书和作者的多对多,出版社和图书的一对多
图书的新增
视图函数:
@login_required
def book_add(request):
if request.method == "GET":
f = BookForm()
return render(request, "book/add.html", {'form': f})
elif request.method == "POST":
f = BookForm(request.POST)
if f.is_valid():
book = Book()
book.name = f.cleaned_data.get("name")
book.isbn = f.cleaned_data.get("isbn")
book.photo = f.cleaned_data.get("photo")
book.price = f.cleaned_data.get("price")
book.publish_id = f.cleaned_data.get("publish")
# 或者使用publish实例
# publish_id=f.cleaned_data.get("publish")
# pub_obj=Publish.objects.filter(p_id=publish_id).first()
# book.publish=pub_obj
book.save()
#返回自增id
b_id=book.b_id
book_obj=Book.objects.filter(b_id=b_id).first()
#获取前台多选框内的作者信息
select_authors=request.POST.getlist("author")
alist = [i for i in select_authors]
#调用set指令,实际上是先删后插
book_obj.author.set(alist)
return redirect(reverse("book_list"))
else:
errors = f.errors
return render(request, "book/add.html", {'form': f, 'errors': errors})
图书的修改
模板文件:
{% extends "base.html" %}
{% load static %}
{% block ext_title %}
<title>图书信息修改</title>
{% endblock %}
{% block content %}
<div class="content-wrapper">
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-12">
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="#">图书管理</a></li>
<li class="breadcrumb-item active">图书修改</li>
</ol>
</div>
</div>
</div>
</section>
<section class="content">
<div class="container-fluid">
<div class="row">
<!-- left column -->
<div class="col-md-12">
<!-- Horizontal Form -->
<div class="card card-info">
<div class="card-header">
<h3 class="card-title">图书信息修改</h3>
</div>
<!-- /.card-header -->
<!-- form start -->
<form class="form-horizontal" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<div class="card-body">
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">图书名称</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="name" name="name"
value="{{ book.name }}" placeholder="图书名称">
<span style="color:red">{{ errors.name.0 }}</span>
</div>
</div>
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">ISBN</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="isbn" name="isbn"
value="{{ book.isbn }}" placeholder="请输入ISBN">
<span style="color:red">{{ errors.isbn.0 }}</span>
</div>
</div>
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">图书封面</label>
<div class="col-sm-8">
<div class="input-group">
<div class="custom-file">
{{ form.photo }}
<label class="custom-file-label">选择图书封面</label>
</div>
</div>
<!--<input type="file" name="photo" value="{{ book.photo }}" id="photo">-->
{% if book.photo %}
<img id="preview-image" src="media/{{ book.photo }}"
style="width: 150px; height: 150px;"/>
{% else %}
<img id="preview-image" src="{% static 'img/default-150x150.png' %}"
style="width: 150px; height: 150px;"/>
{% endif %}
<span style="color:red">{{ errors.photo.0 }}</span>
</div>
</div>
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">价格</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="price" name="price"
value="{{ book.price }}" placeholder="请输入价格">
<span style="color:red">{{ errors.price.0 }}</span>
</div>
</div>
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">出版社</label>
<div class="col-sm-10">
{{ form.publish }}
<span style="color:red">{{ errors.publish_id.0 }}</span>
</div>
</div>
<div class="form-group row">
<label for="inputtext" class="col-sm-2 col-form-label">作者</label>
<div class="col-sm-10">
{{ form.author }}
<span style="color:red">{{ errors.author_id.0 }}</span>
</div>
</div>
</div>
<!-- /.card-body -->
<div class="card-footer">
<button type="submit" class="btn btn-info">保存</button>
<button type="button" class="btn btn-default"
onclick="javascript:window.location='{% url "book_list" %}'">返回
</button>
<span style="color:red">{{ errors }}</span>
</div>
<!-- /.card-footer -->
</form>
</div>
<!-- /.card -->
</div>
<!--/.col (right) -->
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</section>
</div>
<script language="JavaScript">
$('[name="photo"]').bind('change', function () {
var file = this.files[0];
var rdr = new FileReader();
rdr.onload = function () {
$('#preview-image').attr('src', this.result);
};
rdr.readAsDataURL(file);
});
</script>
{% endblock %}
视图函数:
@login_required
def book_edit(request, bid):
if request.method == "GET":
# 根据bid找到具体的图书
book_obj = Book.objects.filter(b_id=bid).first()
# 找到出版社
publish_obj = book_obj.publish
author_ids = (book_obj.author.values_list('a_id','name'))
#zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
#*author_ids相当于zip反向操作
author_ids = list(zip(*author_ids))[0] if list(zip(*author_ids)) else []
print(author_ids)
f = BookForm(initial={
"name": book_obj.name,
"isbn": book_obj.isbn,
"photo": book_obj.photo,
#将p_id赋值给publish
"publish": publish_obj.p_id,
#将(1,2,3)元组赋值给多对多author
"author":author_ids,
})
return render(request, "book/edit.html", {'form': f, 'book': book_obj})
elif request.method == "POST":
f = BookForm(request.POST, request.FILES)
book_item = Book.objects.filter(b_id=bid).first()
if f.is_valid():
book = Book.objects.get(b_id=bid)
book.name = f.cleaned_data.get("name")
book.isbn = f.cleaned_data.get("isbn")
book.photo = f.cleaned_data.get("photo")
book.price = f.cleaned_data.get("price")
book.publish_id = f.cleaned_data.get("publish")
book.save()
# 获取前台多选框内的作者信息
select_authors = request.POST.getlist("author")
alist = [i for i in select_authors]
# 调用set指令,实际上是先删后插
book.author.set(alist)
return redirect(reverse("book_list"))
else:
errors = f.errors
print(errors)
return render(request, "book/edit.html", {'form': f, 'book': book_item, 'errors': erro
用户管理
视图函数:
@login_required
def user_list(request):
users = User.objects.all()
return render(request, "users/index.html", {'users': users})
组管理
视图函数:
@login_required
def group_list(request):
groups = Group.objects.all()
return render(request, "group/index.html", {'groups': groups})
首页的展示
视图函数:
@login_required
def index(request):
# 获取统计信息
pub_count = Publish.objects.all().count()
author_count = Author.objects.all().count()
book_count = Book.objects.all().count()
# 图表的label和data组装
infos = Book.objects.values('publish__name').annotate(Count('name'))
for info in infos:
print(info["publish__name"])
labels = [info["publish__name"] for info in infos]
print(labels)
datas = [info["name__count"] for info in infos]
print(datas)
context = {
"pub_count": pub_count,
"author_count": author_count,
"book_count": book_count,
"labels": labels,
"datas": datas,
}
return render(request, 'index.html', context=context)
Admin后台管理
admin.py文件
from django.contrib import admin
from django.utils.safestring import mark_safe
from django.utils.html import format_html
from .models import *
# Register your models here.
admin.site.site_title = "Django开发的图书管理系统"
admin.site.site_header = "欢迎来到我的图书管理系统"
admin.site.index_title = "图书管理系统后台管理"
@admin.register(Publish)
class Publish(admin.ModelAdmin):
pass
@admin.register(Author)
class Author(admin.ModelAdmin):
pass
@admin.register(Book)
class Book(admin.ModelAdmin):
pass
需要代码,请站内私信我。
更多Django内容,请关注 Django + Vue.js实战派――Python Web开发与运维
需要代码,请站内私信我。