news 2026/9/1 0:45:09

Django实战:开发停车场预约计费系统的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django实战:开发停车场预约计费系统的完整指南

简介:本资源是一套基于Python Django框架开发的停车场预约与计费系统完整源码案例,面向Web开发初学者及Django进阶学习者,聚焦真实业务场景中的用户管理、车位调度、动态计费与在线支付等核心功能实现。压缩包共2000个文件,含1632个JavaScript前端交互脚本(支撑预约表单、时间选择器、状态实时更新等)、249个HTML模板页面(覆盖登录、车位列表、预约确认、订单支付全流程)、54个CSS样式文件(含bootstrap、font-awesome、datetimepicker等主流UI组件),以及5个核心Python后端逻辑文件(models/views/urls等),整体大小为13.19MB。已有129人下载学习,资源结构清晰、模块解耦明确,提供从数据库建模、REST式视图设计到第三方支付对接的全链路参考实现,特别适合用于课程设计、毕业项目或Django工程化实践训练。

2. 停车不再是“碰运气”:为什么我决定做一个预约计费系统

今年年初,我所在的城市核心商圈停车难的问题越来越明显。每天早上八点半,写字楼停车场入口就开始排长队,进去之后能不能找到车位全凭运气。更让人头疼的是,很多车主进场绕一圈发现没位置,出场时却因为停留时间不足半小时被收了“入场费”。从停车场运营方的角度看,车位利用率其实并不高,高峰期拥堵,平峰期闲置,动态调度全靠管理员拿对讲机喊。这种低效的供需匹配,本质上是信息不透明造成的。

那段时间我正好在系统地学习Python和Django,手头一直在找合适的练手项目。看过博客系统、电商项目、内容管理后台,总觉得不太过瘾,直到有天在停车场等了二十分钟才等到一个车位,我忽然意识到:与其做那些“做完就删”的Demo,不如干脆自己开发一套“停车场预约停车计费系统”。这样既能解决真实场景里的痛点,又能把Django框架的核心能力——ORM建模、用户认证、Session会话管理、Admin后台定制、模板渲染、REST API设计——全部串起来实战一遍。这篇文章就是我把这个项目从零到一完整落地后的复盘,包含了业务拆解、数据库设计、计费引擎、并发控制、部署上线以及几处让我印象深刻的坑。适合已经有Python基础、想通过完整项目进阶Django的同学参考。

3. 从需求到功能:停车场预约计费系统的核心业务拆解

开发这类系统最容易犯的错,就是一上来就写代码。在没有理清业务边界的情况下,代码写得再快,后面返工的成本也会成倍上涨。所以第一步,我是把整个停车场景里会涉及的“角色”和“动作”一条一条列出来,再转成功能模块。

3.1 拆解真实场景:车主、管理员和运营方各需要什么

一个停车场预约计费系统,表面上看只是“用户预约、按时计费”,实际上至少分成三个角色视角:

  • 车主(C端用户):需要能够注册登录、查看停车场实时剩余车位、预约车位、取消预约、入场签到、出场结算、查看历史停车记录。预约时最关心的是“现在还有没有位置”和“大概要花多少钱”。
  • 停车场管理员:需要审核车位状态、处理异常订单、手动修正计费结果、查看当日营收统计。管理员不一定懂技术,所以要给一套足够直观的后台界面。
  • 系统运营方(超级管理员):需要管理多个停车场(如果后续扩展)、设置计费规则(按时长阶梯计价)、查看全局的经营报表、管理用户和黑名单。

从这三个角色出发,我把系统拆成了四个核心模块:用户认证与权限管理、停车位信息管理、预约订单管理、计费与结算管理。其中预约和计费是业务的核心链路,权限与车位管理是支撑。Django的Contrib模块自带用户认证体系和Admin后台,恰好能覆盖权限管理和后台可视化这两块,省去了大量重复造轮子的工作。

3.2 Django项目与App如何划分:模块边界比代码量更重要

Django提倡“项目-应用”的结构,一个项目可以包含多个App,每个App负责一个独立业务域。这是我一开始就确定的划分方式:

App名称负责的业务域核心模型
accounts用户注册、登录、个人信息User(扩展Django自带User)
parking_lot停车场、车位信息维护ParkingLot, ParkingSpace
reservation预约、入场、出场流程Reservation, ParkingRecord
billing计费规则与订单结算BillingRule, BillingOrder

之所以把预约和计费拆成两个App,是因为预约关心的是“车位状态流转”,计费关心的是“金额如何计算”,两者未来都可能独立扩展。比如停车场要做优惠券抵扣,改动范围会限定在billing内部,不会动到reservation的核心逻辑。这个边界划分在后来的开发中帮了大忙——计费规则改了三次,预约模块一次都没动过。

4. 为什么选Django做底座:技术选型和架构设计复盘

接触过Python Web开发的朋友都知道,主流的框架无非是Django、Flask和FastAPI这三个。我的选择过程没有太多纠结,但还是想把自己的思考过程写出来,供正在做选型的同学参考。

4.1 对比Flask和FastAPI:Django在项目型系统里的原生优势

Flask以轻量灵活著称,适合做API服务或微服务模块,但整个用户认证、ORM、表单处理、Admin后台都需要自己拼接第三方库,项目规模一旦变大,很容易出现“瓶盖子拧不紧”的尴尬。FastAPI则是异步高性能路线,在接口文档自动生成方面非常舒服,但ORM和后台管理仍然需要自己去组合SQLAlchemy、Alembic这些组件。

而Django最大的优势是“全家桶”:

  • 自带ORM:不用手写SQL,模型定义好之后迁移、建表、增删改查一条龙。
  • 自带Admin后台:只需要在admin.py里注册模型,就能得到一个可用的管理界面,非常适合停车场管理员这种不写代码的角色。
  • 自带用户认证体系:登录、注册、Session管理、权限分组,开箱即用。
  • 成熟的模板引擎:服务端渲染页面非常方便,无需前后端分离就能完成一套完整可用的业务系统。
  • 表单处理与CSRF防护:内置的安全机制大大降低了Web应用常见漏洞的风险。

对于“预约停车计费”这种典型的业务管理系统,快速迭代、可靠运行比极致性能更重要。Django的设计哲学“自带一切”恰好就是最稳妥的选择。

4.2 项目目录结构与核心依赖清单

我的项目目录结构大致是这样的:

parking_system/ ├── manage.py ├── requirements.txt ├── config/ # 项目配置 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── accounts/ # 用户模块 │ ├── models.py │ ├── views.py │ └── forms.py ├── parking_lot/ # 车位模块 │ ├── models.py │ ├── admin.py │ └── views.py ├── reservation/ # 预约模块 │ ├── models.py │ ├── views.py │ └── services.py ├── billing/ # 计费模块 │ ├── models.py │ ├── views.py │ └── calculators.py └── templates/ # 模板文件

核心依赖我尽量精简,只选了四个:

Django>=4.2,<5.0 mysqlclient==2.2.0 python-dotenv==1.0.0 django-crispy-forms==2.0

Django版本我选了4.2 LTS,这个版本是官方长期维护版本,安全补丁支持周期长,适合正式项目。mysqlclient用来自定义数据库连接,python-dotenv用来管理环境变量,crispy-forms用来美化表单样式。我是从MySQL上线后才发现python-dotenv的重要性——不把数据库密码放进settings.py,而是放进.env文件里,这样就算代码传到公开仓库也不用担心密钥泄露。

5. 数据模型设计:车辆、车位、订单和计费记录的关系梳理

这个系统最核心的数据模型有五张表,它们之间的关系是整个系统的骨架。我在设计的时候遵循了几个原则:尽量用Django自带的User模型做扩展,不轻易新建用户表;能用状态字段表达的流程节点,不单独建表;所有时间字段都用DateTimeField,方便后续做统计报表。

5.1 五张核心表的字段设计与关系拆解

User(用户表)

Django自带的User模型已经包含用户名、密码、邮箱等字段,我通过OneToOneField扩展了手机号和车牌号:

from django.contrib.auth.models import User from django.db import models class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') phone = models.CharField(max_length=20, verbose_name='手机号') plate_number = models.CharField(max_length=10, verbose_name='车牌号') created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.user.username} ({self.plate_number})"

车牌号在预约时是关键信息,停车场入口的摄像头识别车牌后,需要和预约订单里的车牌号做匹配,所以这块字段必须冗余在一张独立的Profile表里,不能等用户每次预约再手动输入。

ParkingLot(停车场表)

考虑到未来系统可能接入多个停车场,我把停车场信息独立成一张表:

class ParkingLot(models.Model): name = models.CharField(max_length=100, verbose_name='停车场名称') address = models.CharField(max_length=255, verbose_name='地址') total_spaces = models.IntegerField(default=0, verbose_name='总车位数') hourly_rate = models.DecimalField(max_digits=6, decimal_places=2, default=5.00, verbose_name='默认时价') is_active = models.BooleanField(default=True, verbose_name='是否启用') def __str__(self): return self.name

total_spaces是总车位数,而实时剩余车位数不直接存这表里。剩余车位数是通过“总车位数 - 当前被占用的车位数”动态计算出来的。这样设计的好处是不会出现总车位、剩余车位数据不一致的情况。

ParkingSpace(车位表)

每个车位都属于一个停车场,通过外键关联:

class ParkingSpace(models.Model): SPACE_STATUS = ( ('available', '空闲'), ('occupied', '已占用'), ('reserved', '已预约'), ('maintenance', '维护中'), ) lot = models.ForeignKey(ParkingLot, on_delete=models.CASCADE, related_name='spaces') space_number = models.CharField(max_length=20, verbose_name='车位编号') status = models.CharField(max_length=20, choices=SPACE_STATUS, default='available', verbose_name='车位状态') class Meta: unique_together = ('lot', 'space_number') def __str__(self): return f"{self.lot.name} - {self.space_number}"

车位状态有四种:空闲、已占用、已预约、维护中。这里要注意一个关键点:预约时选中的车位会从“空闲”变成“已预约”,一旦用户入场签到,又变成“已占用”,离场后恢复为“空闲”。这个状态流转是整个系统的核心逻辑,后面在订单状态部分会详细展开。

Reservation(预约订单表)

class Reservation(models.Model): STATUS_CHOICES = ( ('pending', '待入场'), ('active', '停车中'), ('completed', '已完成'), ('cancelled', '已取消'), ('expired', '已过期'), ) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reservations') space = models.ForeignKey(ParkingSpace, on_delete=models.CASCADE, related_name='reservations') start_time = models.DateTimeField(verbose_name='预约开始时间') end_time = models.DateTimeField(verbose_name='预约结束时间') status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name='订单状态') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)

这张表是整个业务链路的中枢。用户预约时生成一条pending订单,同时把对应车位标记为reserved;用户入场后订单由pending变为active,车位变为occupied;用户离场结算完成后订单变为completed,车位再次变为available。

BillingOrder(计费订单表)

class BillingOrder(models.Model): reservation = models.OneToOneField(Reservation, on_delete=models.CASCADE, related_name='billing') total_amount = models.DecimalField(max_digits=8, decimal_places=2, default=0.00, verbose_name='总费用') paid_at = models.DateTimeField(null=True, blank=True, verbose_name='支付时间') is_paid = models.BooleanField(default=False, verbose_name='是否已支付') def __str__(self): return f"订单{self.id} - {self.total_amount}元"

计费订单和预约订单是一对一关系。每个预约最终只会产生一条计费记录,金额在出场结算时计算并写入。

5.2 关键设计决策:为什么不直接存“剩余车位数”

这个点我特别想展开说一下。最早一版设计里,我在ParkingLot表上直接加了一个available_spaces字段,每次预约成功就减一,离场就加一。听起来很直观,但上线测试时立刻遇到了两个问题:

  • 如果某个订单被取消,但取消逻辑没有正确归还车位,available_spaces就永久少了一个,而且很难排查是哪个订单导致的。
  • 多个用户同时预约时,available_spaces的并发更新容易产生竞态条件,可能出现“显示还有5个车位,但实际只卖出4个订单”等情况。

后来我改成“总车位数 - 当前非空闲状态的车位数 = 剩余车位数”,这个值完全通过实时查询计算得出,不再需要额外维护一个加减计数的字段。虽然每次查询多了一次数据库聚合计算,但保证了数据的绝对一致性。对于停车场这种查询频率远低于商品秒杀的场景,这点性能消耗完全可以接受。

6. 预约订单状态流转:从锁定车位到入场签到再到离场结算的完整链路

如果说数据模型是系统的骨架,那预约订单的状态流转就是整个系统的血脉。这一节我会按用户操作的完整路径,把整个链路串起来讲清楚。

6.1 预约创建:车位锁定与时间校验的实现细节

用户选择停车场、选择预约时间段后,后端需要做三件事:

  1. 校验所选时间段内该车位是否已被占用。
  2. 校验用户是否有未完成的预约(防止一个用户同时占多个车位)。
  3. 创建预约订单,并把车位状态改为“已预约”。

时间冲突校验是这里最容易出错的地方。我最初直接用简单的“开始时间小于预约结束时间且结束时间大于预约开始时间”来判断,后来发现跨天场景会漏判,尤其是用户选择22:00到次日02:00这种跨天时间段。

最终我采用的查询方式是参考Django官方文档里的时间重叠判断公式:

from django.db.models import Q from datetime import timedelta def is_space_available(space, start, end): # 查找与该车位存在时间重叠的未完成订单 overlapping = Reservation.objects.filter( space=space, status__in=['pending', 'active'], ).filter( Q(start_time__lt=end) & Q(end_time__gt=start) ).exists() return not overlapping

这个查询条件的意思是:已有订单的预约开始时间早于新订单的结束时间,且已有订单的预约结束时间晚于新订单的开始时间。只要满足这两个条件,就说明时间段有重叠。这个判断方式完美覆盖了跨天、首尾相接各种边界情况,推荐直接抄作业。

6.2 入场签到与离场结算:状态一致性保证

用户到达停车场后,管理员在后台通过车牌号查询到预约订单,确认无误后点击“入场签到”。这时订单状态从pending变为active,车位从reserved变为occupied,同时记录实际入场时间。

这里我踩过一个坑:入场时间应该以操作时间为准,而不是预约开始时间。用户预约的是10:00,但他9:40就到了,那么计费应该从9:40开始算。所以实际入场时间要存到Reservation模型的入场字段里,而不是直接沿用start_time。虽然这个逻辑很直觉,但我在第一版确实写成了“计费从start_time开始”,导致早到的用户被少计费了二十分钟。后来加了一个actual_check_in字段才修正过来。

离场结算的流程如下:

  1. 管理员输入车牌号,找到active状态的订单。
  2. 系统调用计费引擎,根据规则计算应缴金额。
  3. 生成BillingOrder,标记为未支付。
  4. 用户完成支付后,订单变为completed,车位状态变回available。

整个流程的关键在于,入场和离场操作都必须在一个事务里完成,保证订单状态和车位状态要么同时成功,要么同时失败。我在Django里用transaction.atomic装饰器来包裹这些操作:

from django.db import transaction @transaction.atomic def check_in(reservation_id): reservation = Reservation.objects.select_for_update().get(id=reservation_id) if reservation.status != 'pending': raise ValueError('当前订单状态无法入场') # 更新订单状态 reservation.status = 'active' reservation.actual_check_in = timezone.now() reservation.save() # 更新车位状态 space = reservation.space space.status = 'occupied' space.save() return reservation

select_for_update()是这里的关键,它会对这行订单数据加锁,防止两个管理员同时对同一个订单操作,出现重复入场的情况。

6.3 状态机一览:每个状态从哪里来,能到哪里去

为了方便理解,我把订单状态和车位状态的变化整理成一张表:

操作订单状态变化车位状态变化
用户预约无 → pendingavailable → reserved
用户取消预约pending → cancelledreserved → available
管理员入场签到pending → activereserved → occupied
管理员离场结算active → completedoccupied → available
预约时间过期未入场pending → expiredreserved → available

这张状态表是整个系统最核心的业务规则。后续如果要做定时任务自动将过期订单置为expired,只需扫描end_time早于当前时间且状态仍为pending的订单即可。

7. 计费引擎的核心算法:阶梯价格、时长计算与订单结算

计费是停车系统里最能体现业务复杂度的模块,也是用户感知最强的部分。这一节我会把计费引擎的算法设计、边界处理和精度问题展开来讲。

7.1 计费规则设计:为什么用阶梯计费而不是单一费率

早期版本的计费规则非常简单:每小时5元,不足一小时按一小时算。但这种规则对长时间停车的用户非常不友好,停24小时要交120元,很多人会选择停在旁边的免费路段,导致停车场营收反而下降。

后来我参考了真实停车场常见的“阶梯计费”模型,规则如下:

  • 首小时收费10元。
  • 之后每小时收费5元。
  • 单日封顶收费50元。
  • 不足一小时按一小时计算。

这个规则的设计逻辑是:首小时的高价能筛选出短时停车用户的实际需求,时价的梯度能鼓励用户提高停车效率,单日封顶则给长时间停车用户一个心理安全线,避免“停一天交出一顿饭钱”的惊吓。

7.2 核心计费代码:时长计算与金额取整的边界处理

计费引擎的核心代码如下:

from datetime import timedelta import math class ParkingFeeCalculator: def __init__(self, first_hour_fee=10, hourly_fee=5, daily_cap=50): self.first_hour_fee = first_hour_fee self.hourly_fee = hourly_fee self.daily_cap = daily_cap def calculate(self, entry_time, exit_time): if exit_time <= entry_time: raise ValueError('离场时间必须晚于入场时间') total_minutes = int((exit_time - entry_time).total_seconds() / 60) total_hours = math.ceil(total_minutes / 60) if total_hours <= 1: return self.first_hour_fee # 计算跨越的天数 days = (exit_time.date() - entry_time.date()).days + 1 if days > 1: # 按天计算,每天封顶 total_fee = 0 current_start = entry_time for _ in range(days): day_end = current_start.replace(hour=23, minute=59, second=59) if day_end > exit_time: day_end = exit_time day_minutes = int((day_end - current_start).total_seconds() / 60) day_hours = math.ceil(day_minutes / 60) if day_hours <= 1: day_fee = self.first_hour_fee else: day_fee = self.first_hour_fee + (day_hours - 1) * self.hourly_fee total_fee += min(day_fee, self.daily_cap) current_start = day_end + timedelta(seconds=1) return total_fee # 单日内计算 fee = self.first_hour_fee + (total_hours - 1) * self.hourly_fee return min(fee, self.daily_cap)

我重点解释几个设计细节。一是math.ceil向上取整,这个决定了“不足一小时按一小时算”的规则。二是跨天场景处理,停22:00到次日08:00这种单日封顶逻辑必须按天拆分计算,否则会错误地把10个小时全部算进一天的封顶里,系统就会漏收钱。三是daily_cap的兜底逻辑,不管怎么算,单日费用不会超过设定上限。

7.3 金额精度与四舍五入问题

DecimalField搭配decimal_places=2直接处理金额,Python的Decimal类型不会出现浮点数0.1+0.2=0.30000000000000004这种精度问题。但注意,计算小时数用的是int类型,计算金额时用Decimal转换,避免float混入运算。

8. 并发场景下的关键实现:防止车位超卖和重复结算

停车场系统虽然不是电商秒杀级别的高并发,但“超卖”和“重复结算”这类问题在预约场景中依然存在,而且一旦发生,用户的投诉成本非常高。这一节我单独拿出来讲。

8.1 超卖问题的本质与解决方案

超卖问题的本质是多个请求同时读到一致的数据,然后基于这个数据做判断,最后写入时产生冲突。在预约流程中,两个用户同时看到车位A是空闲的,同时发起预约,如果后端处理不当,两个人都会成功,但车位只有一个,就超卖了。

Django的解决方案是使用select_for_update()锁行。在事务中,先锁定该车位对应数据行,确保同一时间只有一个订单能处理这个车位:

from django.db import transaction @transaction.atomic def create_reservation(user_id, space_id, start_time, end_time): with transaction.atomic(): space = ParkingSpace.objects.select_for_update().get(id=space_id) if space.status != 'available': raise ValueError('车位已被占用') # 检查时间段冲突 if not is_space_available(space, start_time, end_time): raise ValueError('该时间段已被预约') # 创建预约 reservation = Reservation.objects.create( user_id=user_id, space=space, start_time=start_time, end_time=end_time, status='pending', ) space.status = 'reserved' space.save() return reservation

select_for_update()要求整个操作必须在事务中执行,Django的transaction.atomic提供了这个上下文。这个方案能有效防止重复预约,但要注意锁的粒度:锁的是车位行,而不是整个停车场。所以不同车位之间互不影响,不会造成全局锁导致的性能下降。

8.2 重复结算的兜底方案

离场结算时,同样需要用select_for_update()锁住订单,并校验订单状态必须是active。如果管理员不小心点击两次“结算”,第二次操作会因为订单状态已经不是active而被拒绝。

另外还需要设置一个幂等标识。我在BillingOrder表上加了一个unique约束的reservation外键,这样即使两个请求同时尝试创建计费订单,数据库层面也能保证同一个预约只能产生一条计费记录。如果使用了PostgreSQL或MySQL的unique约束,Django ORM的IntegrityError就能兜住并发冲突。

8.3 测试并发场景的工具选择

本地开发时,我用了Apache Bench(ab)和Python的并发请求库来测试。比如用ab模拟50个并发请求同时预约同一个车位,验证最终生成的订单数只有1个。具体命令:

ab -n 50 -c 50 -p payload.json -T application/json http://127.0.0.1:8000/api/reserve/

如果系统没有正确加锁,这50个请求里会出现多个success响应;加锁正确的话,49个会拿到“车位已被占用”的提示。

9. 部署实战:从SQLite迁移到MySQL以及生产环境的配置细节

开发阶段我直接用Django默认的SQLite数据库,零配置、文件存储、查起来也方便。但正式部署就必须切换到MySQL或PostgreSQL,否则生产环境并发一高,SQLite的写锁问题会被无限放大。

9.1 迁移数据库时最容易踩的坑

从SQLite迁移到MySQL,我遇到的最大的坑是字符集和排序规则。SQLite对Emoji和生僻字的支持不如MySQL严格,迁移过去后如果MySQL表用的是utf8mb4_general_ci,插入Emoji字符时会出现“Incorrect string value”的报错。解决方案是创建数据库时显式指定字符集:

CREATE DATABASE parking_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

另一个坑是时间字段的存储。SQLite存储的是字符串,MySQL存储的是DATETIME类型,迁移时如果之前用了很多字符串格式化操作,现在需要统一改成Django的timezone工具类来生成和管理时间,否则会导致时区错乱。

迁移步骤建议用Django自带的命令分两步走:

python manage.py makemigrations python manage.py migrate

第一次执行migrate之前,一定要先备份SQLite文件。因为有些数据迁移操作是不可逆的,一旦执行错误,没有备份连后悔药都没得吃。

9.2 Nginx + uWSGI部署的简化配置

部署我采用的是Nginx做反向代理,uWSGI作为应用服务器。uWSGI的配置文件大概长这样:

[uwsgi] chdir = /var/www/parking_system module = config.wsgi:application master = true processes = 4 threads = 2 socket = 127.0.0.1:8001 vacuum = true die-on-term = true env = DJANGO_SETTINGS_MODULE=config.settings.production

Nginx配置里把/api/和/static/的请求代理到uWSGI服务,其余静态文件直接交给Nginx处理:

server { listen 80; server_name example.com; location /static/ { alias /var/www/parking_system/static/; } location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8001; } }

有一点值得提醒:Django的DEBUG模式在生产环境必须关闭,否则一旦出现异常,完整的报错信息、文件路径、数据库配置都会直接暴露给用户,这是很严重的安全隐患。

10. 踩坑记录:开发调试过程中最容易翻车的几个隐蔽问题

最后这部分,我想把自己在开发这个项目过程中印象最深的几个坑整理出来,这些细节很难在官方文档里查到,但实际开发中几乎必然会碰到。

10.1 Django时区设置导致的时间偏移

开发过程中我发现预约时间总是比实际时间慢8小时。排查了很久,发现问题出在settings.py里的TIME_ZONE配置。Django默认的TIME_ZONE是'UTC',如果项目不设置USE_TZ=True且TIME_ZONE='Asia/Shanghai',所有的时间字段都会按UTC存储,模板显示时也不会自动转换。最终配置如下:

USE_TZ = True TIME_ZONE = 'Asia/Shanghai'

设置了TIME_ZONE之后,模板中需要显示本地时间时,用模板过滤器:

{{ order.start_time|localtime|date:"Y-m-d H:i" }}

10.2 Admin后台注册模型后复选框宽度异常

这是个比较冷门但很影响体验的问题。Admin后台的ManyToManyField默认用复选框展示,如果关联对象特别多,复选框会挤成一列,几乎没法操作。我之前设计的预约记录和车位关联场景就遇到了这个问题。解决方案是自定义ModelAdmin,用filter_horizontal属性将多选框改成左右两栏的选择器:

class ReservationAdmin(admin.ModelAdmin): filter_horizontal = ('spaces',)

这个配置让后台管理体验提升了不止一个档次。

10.3 静态文件404导致页面样式全丢

开发模式下Django能正常处理静态文件,但部署后静态文件404是很常见的现象。原因是在生产环境里,Django默认不会托管静态文件,需要先执行collectstatic把所有静态文件收集到一个目录,再由Nginx处理:

python manage.py collectstatic --noinput

我在首次部署时忘记配置STATIC_ROOT,结果页面HTML正常加载,但所有CSS和JS全部404,看起来就像网站“裸奔”了。

10.4 订单超时未支付的处理

预约成功但用户始终没有支付、也没有入场,车位上会一直挂着reserved状态。我设计了一个Django管理命令,定时扫描所有end_time早于当前时间且状态为pending的订单,将它们自动标记为expired并释放车位:

from django.core.management.base import BaseCommand class Command(BaseCommand): help = '处理过期预约' def handle(self, *args, **options): expired_reservations = Reservation.objects.filter( status='pending', end_time__lt=timezone.now() ) for reservation in expired_reservations: with transaction.atomic(): reservation.status = 'expired' reservation.save() reservation.space.status = 'available' reservation.space.save()

然后通过crontab配置每小时执行一次:

0 * * * * cd /var/www/parking_system && /usr/bin/python3 manage.py expire_reservations >> /var/log/parking_expire.log 2>&1

这个定时任务是整个系统能够长期稳定运行的重要保障。没有它,只要有几个用户预约后不来,运营方就不得不在后台频繁手动释放车位。

11. 项目未来的扩展方向

到这里,一个完整的“Python基于Django停车场预约停车计费系统”的核心内容已经全部落地了。最后聊聊这个项目的扩展空间。我目前正在做的是给系统增加LPR车牌识别摄像头的对接,通过OpenCV识别入场车辆的车牌号码后,自动匹配预约订单并放行,真正实现“预约即通行”。远期规划里还会考虑对接微信小程序预约入口、停车费预充值账户、以及基于时间区间价格的动态调价。如果你也在用Django做类似的项目管理系统,这套模块划分、状态机设计和并发控制方案可以直接平滑移植,希望这篇复盘能帮你少走一些弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 0:36:03

全国地铁线路SHP数据处理全攻略:解压、坐标系纠偏与GIS分析

简介&#xff1a;本资源为2024年全国地铁线路矢量数据&#xff08;SHP格式&#xff09;&#xff0c;面向城市规划师、GIS从业人员、交通研究者及高校地理信息相关专业师生&#xff0c;解决城市交通网络建模、站点服务范围分析、应急疏散路径规划等核心场景中的基础底图与空间分…

作者头像 李华
网站建设 2026/9/1 0:36:00

加权TOPSIS详解:熵权法确定权重与Python实现

简介&#xff1a;本资源是一份面向数据分析与多准则决策初学者的MATLAB实战代码包&#xff0c;聚焦加权TOPSIS&#xff08;优劣距离法&#xff09;在IT项目评估、系统选型及水质等实际场景中的应用。它解决了多指标评价中权重设定与距离计算的核心难点&#xff0c;帮助用户快速…

作者头像 李华
网站建设 2026/9/1 0:34:52

病理图像深度学习工程实践:基于PyTorch的WSI切片分类

简介&#xff1a;本资源是一套面向生物信息学与医学图像分析方向研究者的Python深度学习实践代码包&#xff0c;聚焦于组织病理学图像与空间转录组数据的跨模态建模任务&#xff0c;解决H&E染色切片到基因表达谱的预测难题。资源共40个文件&#xff0c;以33个Python脚本为核…

作者头像 李华
网站建设 2026/9/1 0:29:02

青海全省30米DEM数据下载与处理全攻略:从GLO-30到DSM转DEM

简介&#xff1a;本资源为青海省30米分辨率数字高程模型&#xff08;DEM&#xff09;数据集&#xff0c;面向GIS科研人员、遥感分析工程师及地理信息专业师生&#xff0c;支撑地形分析、流域建模、灾害风险评估等空间分析任务。压缩包共10个文件&#xff0c;包含核心GeoTIFF格式…

作者头像 李华
网站建设 2026/8/31 23:59:47

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

作者头像 李华
网站建设 2026/8/31 23:57:15

算法学习重试怎样避免放大故障

算法学习重试怎样避免放大故障重试是在“这次失败可能是暂时的&#xff0c;并且再做一次不会造成额外副作用”两个条件都成立时才有意义。没有约束的重试会在故障时放大流量&#xff0c;甚至让原本可恢复的服务被二次压垮。 先判断这次请求能不能重试 网络短暂中断、连接重置和…

作者头像 李华