本案例主要示範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開發與運維
需要代碼,請站内私信我。