本文利用Python的Django框架从0开始最终实现一个简单的Web 停车场管理系统。
实现过程以快速为遵旨,重点实现核心功能。
源码请参考优快云站内资源站:
目前一个典型的停车场管理包含如下几个功能:
1、扫描车牌识别自动出入场
2、会员管理(充值,提醒充值。。等)
3、收费(如果是有效期内的会员就免费)
为了实现上面几个功能,我们先简单的做下数据库和页面的设计。
数据库设计
数据库的话可以设计以下几个:
会员表:
车牌,姓名,手机号,会员起始日期,会员截止日期
用于会员的到期提醒,充值等管理
出入库记录表:
车牌,入场日期时间,出场日期时间,付款日期时间,付款金额
用于出入场记录及收费管理
页面功能设计
简单对系统做一个页面的结构划分
- 停车场管理系统
- 会员管理
- 会员充值
- 会员查看
- 车辆入场管理
- 车牌识别
- 入场记录
- 车辆出场管理
- 车牌识别
- 出场记录
- 计算费用
- 收费管理
- 会员管理
系统实现
初步整理了需求后,我们直接开始撸代码。
创建项目
django-admin startproject DjangoPark2023
进入项目文件夹
cd DjangoPark2023
创建虚拟环境
python -m venv venv
进入虚拟环境
venv\Scripts\activate.bat
在虚拟环境中安装Django
pip install django
创建app
Django框架下的核心的模型和视图都需要在app中实现,我们简单点就创建一个app名为app01,实际项目中可以根据模块创建多个app。
python manage.py startapp app01
安装app
在文件 DjangoPark2023/settings.py 中更新代码如下
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01' #增加此项
]
截止到目前的文件结构
定义模型
根据我们前面的数据库
app01/models.py
from django.db import models
# Create your models here.
class vip_uer(models.Model):
name = models.CharField(max_length=32, verbose_name='姓名')
carnum = models.CharField(max_length=32, unique=True, verbose_name='车牌号')
phone = models.CharField(max_length=32, verbose_name='手机号')
begintime = models.DateTimeField(auto_now=False, auto_now_add=False)
endtime = models.DateTimeField(auto_now=False, auto_now_add=False)
class car_record(models.Model):
carnum = models.CharField(max_length=32, verbose_name='车牌号')
intime = models.DateTimeField(auto_now=False, auto_now_add=False, verbose_name='入场时间')
outtime = models.DateTimeField(auto_now=False, auto_now_add=False, verbose_name='出场时间', null=True, blank=True)
paytime = models.DateTimeField(auto_now=False, auto_now_add=False ,verbose_name='收费时间', null=True, blank=True)
amount = models.IntegerField(verbose_name='收费金额', null=True, blank=True)
安装连接MySQL
的包
pip install pymysql
引入pymysql
DjangoPark2023/__init__.py
import pymysql
pymysql.install_as_MySQLdb()
MySQL的安装就不详细讲了,大家自行安装。
手工创建一个数据库,名字为:djangopark2023
连接MySQL数据库配置
DjangoPark2023/settings.py
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'djangopark2023', # 数据库名称
'HOST': '127.0.0.1', # 数据库地址,本机 ip 地址 127.0.0.1
'PORT': 3306, # 端口
'USER': 'root', # 数据库用户名
'PASSWORD': '123456', # 数据库密码
}
}
迁移模型
python manage.py makemigrations
更新迁移表
python manage.py migrate
数据表创建成功
创建超级用户
python manage.py createsuperuser
用户名:admin
密码:123456
运行服务器
python manage.py runserver
输入后台网址看下:http://127.0.0.1:8000/admin/
登录后没有显示自定义的模型,还需要注册模型到后台。
模型注册到admin后台
app01/admin.py
from django.contrib import admin
# Register your models here.
# 别忘了导入models
from app01.models import vip_uer
# 注册模型到admin中
admin.site.register(vip_uer)
添加一条VIP记录
功能和页面实现
会员管理
会员充值:
充值时选择充值天数,30天,90天,180天,360天,或自定义
如果车牌不存在,则添加一条会员记录,起始时间为现在,截止时间为现在+套餐天数。
如果车牌已存在,没过期,则截止时间 加上 套餐天数,如果过期了,则更新起始时间为现在,截止时间为现在+套餐天数。
前端网页模板
新建文件夹templates用于管理模板文件。
然后配置下模板位置
import os
*...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 添加此项
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
接着在templates创建模板文件:这里我们未来快速实现功能,暂时先直接在线引入bootstrap静态文件。
templates/recharge.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<title>会员充值</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-6">
<br>
<!-- 充值的表单 -->
<form method="post" action=".">
<!-- Django中需要POST数据的地方都必须有csrf_token -->
{% csrf_token %}
<div class="form-group">
<!-- 标签 -->
<label for="title">车牌号</label>
<!-- 文本框 -->
<input type="text" class="form-control" id="carnum" name="carnum">
</div>
<!-- 充值套餐 -->
<div class="form-group">
<label for="body">充值天数</label>
<!-- 文本区域 -->
<input type="text" class="form-control" id="chargedays" name="chargedays" rows="6">
</div>
<!-- 提交按钮 -->
<button type="submit" class="btn btn-primary">提交充值</button>
</form>
</div>
</div>
</div>
</body>
</html>
视图函数功能实现:
app01/views.py
from django.http import HttpResponse
from django.shortcuts import render
import datetime
from app01 import models
from .models import vip_uer,car_record
# Create your views here.
def Recharge(request):
if request.method == 'POST':
chargedays = int( request.POST.get('chargedays') )
carnum = request.POST.get('carnum')
print(chargedays)
days = datetime.timedelta(days=chargedays)
if models.vip_uer.objects.filter(carnum=carnum):
vip = vip_uer.objects.get(carnum=carnum)
endtime = vip.endtime + days
print(vip.endtime)
models.vip_uer.objects.update(carnum=carnum, endtime = endtime)
else:
begtime = datetime.datetime.now()
endtime = begtime + days
models.vip_uer.objects.create(carnum=carnum, begintime = begtime ,endtime=endtime)
return HttpResponse("充值成功!")
else:
return render(request, 'recharge.html')
路由配置
DjangoPark2023/urls.py 中更新如下代码,配置对应的路由规则。
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('recharge/', views.Recharge), #添加如下项
]
重新运行服务器
输入地址:http://127.0.0.1:8000/recharge/
输入车牌号和充值天数后提交充值后。
基础的架构打好了,接下来就是各种功能视图和模板的实现了,有时间再继续更!
继续:
车辆入场管理
车辆入场核心涉及两个功能点:车牌识别,入场记录。
车牌识别,我们直接通过选择图片模拟车牌扫描的动作,识别到车牌后,再通过车辆入场按钮完成车辆的入场登记。
车牌识别
实现车牌识别我们就没必要直接自己实现了,直接调Open CV来实现的话太费劲。
我们直接调百度现有的接口来实现就快很多了,详细的实现过程请参考如下链接。
实现的大概思路是我们网页端先导入一张图片,点击识别图片的时候,Django后端获取图片并存储起来,然后调用百度的图像识别接口识别我们的存储图片,返回对应的车牌信息给到网页前端。
简单一点来说调用百度接口需要做如下动作:
1、安装百度图像识别包:pip install baidu_aip
2、安装文件编码包 chardet :pip install chardet
3、安装图片处理包 Pillow :pip install Pillow
接着我们新建模型用于处理识别的图片:
app01/models.py
class License_plate(models.Model):
car_img = models.ImageField(upload_to='car_imgs',unique=True, blank=True, null=True)
car_num = models.CharField(max_length=32, unique=True, verbose_name='车牌号', null=True)
color = models.CharField(max_length=32, verbose_name='车牌颜色')
更新并迁移表
python manage.py makemigrations
python manage.py migrate
然后定义一个函数调用百度接口
先引入AipOC
from aip import AipOcr
然后定义一个函数Park_discern调用函数识别图像,返回识别的内容信息。
from aip import AipOcr
def Park_discern(image):
APP_ID = '自己的ID'
API_KEY = '自己的KEY'
SECRET_KEY = '自己的KEY'
# 创建客户端对象
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
# 建立连接的超时时间,单位为毫秒
client.setConnectionTimeoutInMillis(5000)
# 通过打开的连接传输数据的超时时间,单位为毫秒
client.setSocketTimeoutInMillis(5000)
res = client.licensePlate(image)
return res
再定义一个函数 car_in,前端在点击图像识别按钮时调用函数根据前端选择的图片实现图像识别,将识别的内容返回给网页。
def car_in(request):
if request.method == 'POST':
# 读取图片
img = request.FILES.get('car_img')
if img == None:
# 没有选择图片,而直接点击检测
error = '请选择一张图片!'
return render(request, 'car_in.html', {'error': error})
else:
try:
# 将图片数据存起来
new_car = models.License_plate.objects.create(car_img=img)
# 定义读取图片函数
def get_file_content(filePath):
with open(filePath, 'rb') as fp:
return fp.read()
#生成图片地址
url = './media/' + str(new_car.car_img)
# 读取图片信息
image = get_file_content(url)
# 调用接口识别车牌
res = Park_discern(image)
#车牌号
carnum = res['words_result']['number']
#车牌颜色
color = res['words_result']['color']
try:
# 车牌是否识别过
is_carnum = models.License_plate.objects.get(car_num=carnum)
if is_carnum:
#识别过了的直接从数据库读取历史数据并删除当前存储的图片数据和文件
new_car.car_img = is_carnum.car_img
print(new_car.id )
models.License_plate.objects.filter(id=new_car.id ).delete()
except models.License_plate.DoesNotExist:
# 没识别过,则保存车牌和颜色信息
new_car.color = color
new_car.car_num = carnum
new_car.save()
# return redirect('carnum_add')
print(new_car.car_img)
return render(request,'car_in.html',{'carport_url':new_car.car_img,'carnum':carnum,'color':color})
except Exception as e:
return HttpResponse('识别发生错误!')
return render(request, 'car_in.html',{'carport_url':'car_imgs/intro.jpg'})
入场登记
再定义一个函数carin_update,点击车辆入场按钮时登记车辆入场记录。
def carin_update(request):
if request.method == 'POST':
carnum = request.POST.get('carnum')
# tz = pytz.timezone('Asia/Shanghai')
new_intime = timezone.now()
print(new_intime)
models.car_record.objects.create(carnum=carnum, intime=new_intime)
if models.vip_uer.objects.filter(carnum=carnum):
vip = vip_uer.objects.get(carnum=carnum)
# now = datetime.now(timezone.utc)
endtime = vip.endtime
remain_days = endtime - new_intime
remain_days = str(remain_days.days)
context = '会员车辆' + carnum + '欢迎您!' + '剩余会员天数:' + remain_days
print(context)
return HttpResponse(context)
else:
return HttpResponse("临时车辆,欢迎入场!")
车牌识别和车辆入场的功能我们都放到一个模板里面:templates/car_in.html
<!-- 网站主语言 -->
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<body>
<div class="container">
<div class="row">
<div class="col-xl-10">
<div class="content-box-large">
<div class="panel-body">
<div class="card-body">
<label>检测图片</label>
<form enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
{# <label for="car_img"><img id="imgforshow" src="/static/imgs/intro.jpg"#}
<label for="car_img"><img id="imgforshow" src="/media/{{ carport_url }}"
style="width: 350px; height: 200px; border:
1px solid black; cursor: pointer"></label>
<input type="file" name="car_img" id="car_img" style="display: none;"><br>
<div class="form-group">
<label>检测结果:车牌号</label>
<input class="form-control" type="text" name="carnum" value="{{ carnum }}">
<label>检测结果:车牌颜色</label>
<input class="form-control" type="text" name="color" value="{{ color }}">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit">识别车牌</button>
<button class="btn btn-primary" formaction="/carin_update/" formmethod="post">车辆入场
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- jQuery UI -->
<script src="https://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
$("#car_img").change(function () {
var rd_img = new FileReader();
rd_img.readAsDataURL(this.files[0]);
rd_img.onload = function () {
$("#imgforshow").attr("src", rd_img.result);
};
});
</script>
</body>
</html>
选择图片,点击识别车牌,可以识别到车牌号和车牌颜色。
点击车辆入场,如果是会员则会提示剩余会员天数否则提示临时车辆。
车辆出场管理
车辆出场基本和入场差不多,识别车辆功能,然后更新出场记录。
不过我们在更新出场记录的时候,需要先找到扫描到的车牌最近的一次入场记录。
然后计算出停车时长,出场之前需要判断是否为会员,会员直接出场,否则需要先付款。然后才能出场。
分别编写视图函数如下:
def car_out(request):
if request.method == 'POST':
# 读取图片
img = request.FILES.get('car_img')
if img == None:
# 没有选择图片,而直接点击检测
error = '请选择一张图片!'
return render(request, 'car_in.html', {'error': error})
else:
try:
# 将图片数据存起来
new_car = models.License_plate.objects.create(car_img=img)
# 定义读取图片函数
def get_file_content(filePath):
with open(filePath, 'rb') as fp:
return fp.read()
#生成图片地址
url = './media/' + str(new_car.car_img)
# 读取图片信息
image = get_file_content(url)
# 调用接口识别车牌
res = Park_discern(image)
#车牌号
carnum = res['words_result']['number']
#车牌颜色
color = res['words_result']['color']
try:
# 车牌是否识别过
is_carnum = models.License_plate.objects.get(car_num=carnum)
if is_carnum:
#识别过了的直接从数据库读取历史数据并删除当前存储的图片数据和文件
new_car.car_img = is_carnum.car_img
print(new_car.id )
models.License_plate.objects.filter(id=new_car.id ).delete()
except models.License_plate.DoesNotExist:
# 没识别过,则保存车牌和颜色信息
new_car.color = color
new_car.car_num = carnum
new_car.save()
# return redirect('carnum_add')
print(new_car.car_img)
return render(request,'car_out.html',{'carport_url':new_car.car_img,'carnum':carnum,'color':color})
except Exception as e:
return HttpResponse('识别发生错误!')
return render(request, 'car_out.html',{'carport_url':'car_imgs/intro.jpg'})
def carout_update(request):
if request.method == 'POST':
carnum = request.POST.get('carnum')
new_outtime = timezone.now()
print(new_outtime)
new_car_record = car_record.objects.filter(carnum=carnum).order_by('outtime')
out_car = new_car_record[0]
out_car.carnum = carnum
out_car.outtime = new_outtime
out_car.save()
if models.vip_uer.objects.filter(carnum=carnum):
vip = vip_uer.objects.get(carnum=carnum)
print(vip)
# now = datetime.now(timezone.utc)
endtime = vip.endtime
remain_days = endtime - new_outtime
remain_days = str(remain_days.days)
print(remain_days)
context = '会员车辆' + carnum + '一路顺风!' + '剩余会员天数:' + remain_days
print(context)
return HttpResponse(context)
else:
return HttpResponse("临时车辆,一路顺风!")
模板templates/car_out.html:
<!-- 网站主语言 -->
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- jQuery UI -->
<link href="https://code.jquery.com/ui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" media="screen">
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-10">
<div class="content-box-large">
<div class="panel-body">
<div class="card-body">
<label>检测图片</label>
<form enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
{# <label for="car_img"><img id="imgforshow" src="/static/imgs/intro.jpg"#}
<label for="car_img"><img id="imgforshow" src="/media/{{ carport_url }}"
style="width: 350px; height: 200px; border:
1px solid black; cursor: pointer"></label>
<input type="file" name="car_img" id="car_img" style="display: none;"><br>
<div class="form-group">
<label>检测结果:车牌号</label>
<input class="form-control" type="text" name="carnum" value="{{ carnum }}">
<label>检测结果:车牌颜色</label>
<input class="form-control" type="text" name="color" value="{{ color }}">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit">识别车牌</button>
<button class="btn btn-primary" formaction="/carout_update/" formmethod="post">车辆出场</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- jQuery UI -->
<script src="https://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
$("#car_img").change(function () {
var rd_img = new FileReader();
rd_img.readAsDataURL(this.files[0]);
rd_img.onload = function (){
$("#imgforshow").attr("src", rd_img.result);
};
});
</script>
</body>
</html>