尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django构建汽车美容行业网站:从项目搭建到业务建模实战

Django构建汽车美容行业网站:从项目搭建到业务建模实战 简介这份源码包面向汽车美容行业线上转型需求提供基于Django框架的完整网站设计与实现方案适合Web开发学习者、行业从业者及需要快速搭建预约服务平台的开发者参考。项目涵盖车辆美容预约、服务套餐选择、技师团队展示、在线支付、会员管理、客户反馈与数据分析等核心业务模块前后端逻辑清晰。压缩包共66个文件其中55个Python文件负责视图、模型、中间件及工具函数等后端逻辑7个XML配置文件用于数据库、缓存与中间件等运行参数设定另有Git忽略文件、Idea工程文件与说明文档整体68KB目录结构按应用模块划分便于按需查阅。目前已有276人学习下载。通过该项目可掌握Django框架下的模型设计、路由配置、用户交互及前后端协作思路也能借鉴汽车美容业务的功能拆解方式是一份兼具实战价值与教学意义的源码案例。1. 汽车美容行业网站设计为什么选 Django 而不是展示型 CMS汽车美容门店的官网需求往往被误当成一个展示页几个服务项目、几张效果图、一个预约电话就完事。但真到上线交付就会发现数据是活的——车型档案要跟会员绑定、施工单要关联美容师提成、洗车/打蜡/镀晶的价格随时调、高峰期预约要排班。这类状态多、关系密、还要给店长做后台录入的业务展示型 CMS 撑不住纯前端静态站更撑不住。Django 的立项逻辑就在这里自带 ORM 和 migration模型改完直接同步数据库自带 Admin 后台不写一行前端就能让店员录入项目和订单自带认证和权限会员登录、员工分权是现成能力。这也解释了为什么检索“python django搭建web项目”“django创建app”的源码包里汽车美容、汽修、健身房这类垂直行业占了相当大的比例——它们是典型的 Model 密集业务天然适合 MTV 架构。源码交付的价值在于拿回去就能跑而不是重新搭框架。2. 搭建 Django 项目骨架与美容网站的基础 settings2.1 先定项目结构一个业务拆两个 app拿到“汽车美容行业网站设计源码”第一件要做的不是写 models而是把 Django 项目目录立起来。行业源码常见的败笔是把所有 model 塞进一个 app几百行代码堆在 models.py 里后续改需求时牵一发动全身。我的做法是按业务域拆开customer管客户、车辆和会员等级shop管服务项目、预约和工单Django 自带的auth管后台员工账号三者通过外键关联边界清楚。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install django mysqlclient pillow django-simpleui django-admin startproject beauty_salon . python manage.py startapp customer python manage.py startapp shop这段命令里值得说的是依赖选择mysqlclient是生产环境连接 MySQL 的常用驱动pillow用于服务项目图标、案例效果图的 ImageField 处理django-simpleui属于后加的 Admin 美化组件装不装都能跑装上之后admin界面更接近管理后台审美。先装好再创建项目省得回头补包。Windows 上如果mysqlclient编译报错常见的替代方案是换用pymysql并在项目的__init__.py里调用pymysql.install_as_MySQLdb()但对新手来说本地开发直接用默认 SQLite 起步生产再切 MySQL路径更平滑。2.2 settings 里与汽车美容业务直接相关的 6 处配置项目目录生成后beauty_salon/settings.py是第一个要动刀的文件。默认配置跑通没问题但离“可交付的源码”还差几步。# beauty_salon/settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, simpleui, # admin 界面美化放在 admin 前 customer, shop, ] DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: beauty_salon, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, OPTIONS: {charset: utf8mb4}, } } TIME_ZONE Asia/Shanghai USE_TZ True MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media LOGIN_URL /login/ LOGIN_REDIRECT_URL /参数说明按重要程度排数据库DATABASES中OPTIONS指定utf8mb4是为了让车牌号、英文大小写、中文备注都能正常存取汽车美容订单里经常混着“VIP”“京A·12345”这类混合字符默认utf8在索引和排序上会有兼容问题。CONN_MAX_AGE 60表示 MySQL 连接在请求结束后保留 60 秒省去重复握手开销业务量不大的美容门店站这个值已经够用。TIME_ZONE用Asia/Shanghai是因为预约时间是以门店本地时钟为准如果保留默认 UTC订单里的施工时间会整体偏移 8 小时。MEDIA_ROOT指向上传文件落地目录汽车美容的效果图、施工前后对比照都从这里读写。LOGIN_URL和LOGIN_REDIRECT_URL是 Django 认证机制的默认跳转会员与后台登录都需要它们。这些配置项在交付时都需要提供样例和注释否则接手的人不知道哪几个值必须改。2.3 路由与静态资源的最小可用配置配好 settings 后根路由beauty_salon/urls.py需要把两个 app 的 URL 挂进去同时补上开发环境的媒体文件服务。这一步漏掉Admin 后台会显示正常但上传的图片全都 404。# beauty_salon/urls.py from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), path(, include(customer.urls)), path(, include(shop.urls)), ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)这段代码的逻辑是include把各 app 的 URLconf 按前缀挂到根路由不用app_name的话项目小也能跑但我习惯在每个 app 的 urls.py 里定义app_name方便模板里写shop:service_list这类命名空间static(settings.MEDIA_URL, document_root...)只在DEBUGTrue时生效生产环境必须交给 nginx源码交付说明里要强调这一点。到这里项目骨架已经能被python manage.py runserver启动下一步才是把汽车美容的业务实体建模出来。3. 汽车美容核心业务实体在 Django models 里的数据建模3.1 客户、车辆、会员等级一对多的关系怎么建汽车美容行业的特点是一人多车。客户可能家里两台车、公司三台车洗车卡绑定在车牌上还是绑定在客户上业务上经常争论。我的建模原则是会员等级、储值余额这类资产挂在Customer上车辆档案单独建Car通过外键指向客户并且用related_namecars让反向查询读起来像英语句子。# customer/models.py from django.db import models class Customer(models.Model): MEMBER_CHOICES [ (normal, 普通客户), (silver, 银卡会员), (gold, 金卡会员), ] name models.CharField(姓名, max_length32) phone models.CharField(手机号, max_length11, uniqueTrue) level models.CharField(会员等级, max_length16, choicesMEMBER_CHOICES, defaultnormal) balance models.DecimalField(储值余额, max_digits10, decimal_places2, default0) created_at models.DateTimeField(建档时间, auto_now_addTrue) def __str__(self): return f{self.name}({self.phone}) class Car(models.Model): customer models.ForeignKey(Customer, on_deletemodels.CASCADE, verbose_name车主, related_namecars) plate_no models.CharField(车牌号, max_length16, uniqueTrue) brand models.CharField(品牌, max_length32) model_name models.CharField(车型, max_length32, blankTrue) mileage models.IntegerField(当前里程(km), default0) def __str__(self): return f{self.plate_no} {self.brand}字段选择体现的是业务约束phone加uniqueTrue是因为门店系统靠手机号识别客户重复建档会导致储值对不上balance用DecimalField而不是FloatField因为浮点数在金额累加时会出现 0.10.20.30000000000000004 的精度问题流水和余额都不能接受这种误差on_deletemodels.CASCADE表示删客户时连带删车辆档案实际交付时客户有消费记录我不会用 CASCADE而是会改成PROTECT或软删除字段这个属于交付前根据需求调整的地方。related_namecars让代码里可以直接写customer.cars.all()比默认的car_set可读性好得多。3.2 服务项目与预约多对多用中间表而不是自动建表服务项目和预约的关系是典型的多对多一次预约可以做精洗加打蜡也可以做镀晶加内饰养护。Django 的ManyToManyField可以直接建关联表但汽车美容场景里预约明细还需要记录“分配给哪个美容师”“现场实收多少钱”这些字段挂在自动生成的关联表上很别扭所以我会显式建中间模型AppointmentItem。# shop/models.py from django.db import models from customer.models import Customer, Car class ServiceItem(models.Model): CATEGORY_CHOICES [ (wash, 洗车), (beauty, 美容), (maintain, 养护), ] name models.CharField(项目名称, max_length64) category models.CharField(分类, max_length16, choicesCATEGORY_CHOICES, defaultwash) price models.DecimalField(标准价, max_digits8, decimal_places2) duration models.IntegerField(预计工时(分钟), default30) icon models.ImageField(图标, upload_toservice/, blankTrue) def __str__(self): return self.name class Appointment(models.Model): STATUS_CHOICES [ (pending, 待确认), (confirmed, 已确认), (done, 已完成), (canceled, 已取消), ] customer models.ForeignKey(Customer, on_deletemodels.CASCADE, verbose_name预约客户) car models.ForeignKey(Car, on_deletemodels.CASCADE, verbose_name预约车辆) appoint_time models.DateTimeField(预约时间) status models.CharField(状态, max_length16, choicesSTATUS_CHOICES, defaultpending) remark models.TextField(备注, blankTrue) created_at models.DateTimeField(提交时间, auto_now_addTrue) class AppointmentItem(models.Model): appointment models.ForeignKey(Appointment, on_deletemodels.CASCADE, related_nameitems) service models.ForeignKey(ServiceItem, on_deletemodels.PROTECT) quantity models.PositiveIntegerField(数量, default1) sale_price models.DecimalField(实收单价, max_digits8, decimal_places2) employee models.ForeignKey(Employee, on_deletemodels.SET_NULL, nullTrue, blankTrue, verbose_name施工美容师)这段设计有几个关键决策。AppointmentItem.sale_price是给“实收单价”留的快照字段因为服务项目调价后历史预约不能跟着变查询时优先取sale_price而不是service.price。on_deletemodels.PROTECT放在ServiceItem上防止有人手滑删掉一个仍有预约记录关联的项目。employee models.ForeignKey(Employee, ...)用了字符串引用而不是直接写类名是为了避免和后面定义的Employee产生导入顺序问题。Appointment里冗余存了car虽然通过 customer 也能查到但预约时明确指定车辆是业务刚需门店要安排工位和美容师不能模棱两可。3.3 查询与删除对象N1 问题的基本解法models 建完之后业务查询和删除要特别注意性能。汽车美容的预约列表常常按客户姓名搜索ORM 直接遍历会引发 N1 次查询先查 10 条预约每条预约又查一次客户、车辆、明细页面响应能慢上一倍。Django 的解法是select_related和prefetch_related前者用于外键的单表 JOIN后者用于多对多和反向关联。# shop/views.py 中查询预约列表 from django.db import transaction appointments Appointment.objects.select_related( customer, car ).prefetch_related(items__service).filter( statuspending ) # 删除对象批量取消一周前的草稿预约 with transaction.atomic(): deleted, _ Appointment.objects.filter( statuscanceled, created_at__lt2025-01-01 ).delete()select_related(customer, car)会生成一条带 LEFT JOIN 的 SQL把外键对象一次取回.prefetch_related(items__service)则生成两条独立查询再在内存里组装适合跨关系表的集合。删除操作里transaction.atomic()是关键预约删除可能连带明细删除如果中途出错事务回滚能保证主表和子表数据一致。delete()返回的元组(total_deleted, detail_dict)里第一个元素是所有对象的总删除数适合写进操作日志。这里提醒一点生产环境很少真正delete()用户数据常用做法是加is_active或is_archived字段做软删除避免误删后无法恢复。4. 视图与模板联动让服务列表和预约在页面里流转4.1 用类视图展示服务项目带搜索和分页汽车美容门店的服务项目通常 20 到 50 个列表页不能一次全铺开客户需要能搜“镀晶”“打蜡”这种关键词。Django 的ListView配合get_queryset重写是最省力的方案比手写HttpResponse 分页逻辑干净得多。# shop/views.py from django.views.generic import ListView from .models import ServiceItem class ServiceListView(ListView): model ServiceItem template_name shop/service_list.html context_object_name services paginate_by 9 def get_queryset(self): qs super().get_queryset() keyword self.request.GET.get(q, ).strip() if keyword: qs qs.filter(name__icontainskeyword) return qs这里的参数逻辑是paginate_by 9对应门店常见的三列布局一页 9 个项目视觉上最整齐context_object_name指定模板里循环变量的名字模板写{% for s in services %}而不是默认的object_list语义更直观get_queryset里name__icontainskeyword是大小写不敏感的模糊匹配搜索“镀晶”能匹配“全车镀晶”“玻璃镀晶”。模板侧需要手动渲染分页控件ListView只把page_obj传给模板样式要自己写。!-- shop/templates/shop/service_list.html -- div classservice-grid {% for s in services %} div classservice-card h3a href{% url shop:service_detail s.pk %}{{ s.name }}/a/h3 p分类{{ s.get_category_display }} | {{ s.duration }} 分钟/p p classprice¥{{ s.price }}/p /div {% empty %} p暂无可预约的服务项目/p {% endfor %} /div div classpagination {% if page_obj.has_previous %} a href?q{{ request.GET.q }}page{{ page_obj.previous_page_number }}上一页/a {% endif %} span{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}/span {% if page_obj.has_next %} a href?q{{ request.GET.q }}page{{ page_obj.next_page_number }}下一页/a {% endif %} /div模板里s.get_category_display会把 models 里定义的(wash, 洗车)转换成中文“洗车”这是 Django choices 字段自带的展示方法。分页链接里带上?q{{ request.GET.q }}是为了翻页时不丢失搜索关键词这是个高频踩坑点不带上 q 参数第二页就会回到全量列表。这个模板片段可以直接搬进源码交付包里只需要补上对应的 CSS。4.2 预约提交POST 表单、redirect 和 messages 传数据预约表单是汽车美容站的核心交互。用户选择服务项目、填车牌和预约时间提交后跳转到一个“预约成功”的提示页同时给店长后台生成一条待确认记录。这里我使用 Django 的CreateView配合messages框架实现重定向传数据代码量最少且符合 Django 的请求-重定向-响应模式。# shop/views.py from django.contrib import messages from django.urls import reverse_lazy from django.views.generic import CreateView from .models import Appointment from .forms import AppointmentForm class AppointmentCreateView(CreateView): model Appointment form_class AppointmentForm template_name shop/appointment_form.html success_url reverse_lazy(shop:service_list) def form_valid(self, form): form.instance.customer self.request.user.customer messages.success(self.request, 预约已提交等待门店电话确认) return super().form_valid(form)这段代码中form.instance.customer在表单里不展示、由视图自动填充避免用户伪造别人的预约这里的写法是把它直接挂在登录用户的 Customer 外键上。messages.success写入一条一次性消息Django 在下一个请求的模板里可以通过{% if messages %}渲染出来。与重定向搭配时消息会先存入 session页面刷新后自动清除不会出现刷新还在提示的尴尬。成功地址reverse_lazy而不是reverse是因为类属性在模块加载时就求值reverse_lazy延迟到请求时解析 URL这两个 API 的差异也是源码评审时经常被问到的点。这里顺带说明“django 前后端分离”的边界汽车美容行业网站设计源码走的是服务端渲染路线模板直接输出 HTML好处是 SEO 友好、交付简单、不需要 CORS 和 token 管理只有当网站需要承接小程序或 App 的 API 请求时才有必要拆出 Django REST Framework 做前后端分离。一个会话型业务站点强行分离反而会让源码包复杂度翻倍。4.3 URL 命名与模板反向解析对照URLconf 是源码里最长被忽略但又最容易乱的部分。我按功能模块把路由分组统一用app_name加path name做命名空间。下面是 shop app 的 URL 配置也是模板里{% url shop:xxx %}的对照表。# shop/urls.py from django.urls import path from . import views app_name shop urlpatterns [ path(, views.ServiceListView.as_view(), nameservice_list), path(service/int:pk/, views.ServiceDetailView.as_view(), nameservice_detail), path(appointment/new/, views.AppointmentCreateView.as_view(), nameappointment_create), path(appointments/, views.AppointmentListView.as_view(), nameappointment_list), ]URL 表达式name作用service_list首页服务项目列表 搜索service/int:pk/service_detail项目详情含施工说明appointment/new/appointment_create提交预约表单appointments/appointment_list当前用户的历史预约列表这里的int:pk是 Django 路径转换器只匹配整数访问/service/abc/直接 404省得在视图里自己try/except校验参数。命名空间shop:加上去之后即使后续再加一个orderapp 有同名 URL模板里也互不冲突。源码交付时这份 URL 表通常会写进 README它决定了接手者能否快速定位功能对应的代码文件。5. Django admin 定制、源码整理与上线排错5.1 admin 界面美化与列表页高频配置项汽车美容网站交付后真正每天登录后台的是门店店长不是程序员。店长电脑分辨率普遍不高鼠标操作也慢默认 Django admin 是能用但list_display不配置的话订单列表只会显示对象字符串完全没法检索。实际操作里我会给每个核心 model 都写 admin 注册并利用django-simpleui优化列表和表单的视觉密度。# shop/admin.py from django.contrib import admin from .models import ServiceItem, Appointment, AppointmentItem admin.register(ServiceItem) class ServiceItemAdmin(admin.ModelAdmin): list_display (name, category, price, duration) list_filter (category,) search_fields (name,) list_per_page 20 admin.register(Appointment) class AppointmentAdmin(admin.ModelAdmin): list_display (appoint_time, customer, car, status) list_filter (status, appoint_time) search_fields (customer__name, car__plate_no) inlines [AppointmentItemInline]list_display里的status和price直接显示字段值店长不用点进详情就能确认预约、核对金额search_fields (customer__name, car__plate_no)是跨表搜索写的是 Django 的双下划线语法ORM 会 JOIN 到客户表和车辆表查询注意这里的customer__name中customer和name必须和 model 字段完全一致写成customer_name会报字段错误。list_filter左侧筛选栏会按日期和状态下钻这是店长每天早上看“今天预约”的操作入口。AppointmentItemInline是预约明细的行内编辑在预约编辑页直接增删项目比跳转子页面快得多。这些配置配合 simpleui 后后台整体质感接近市面上的商业管理软件。运行前执行迁移命令缺了这步 admin 页面会直接 500python manage.py makemigrations customer shop python manage.py migrate python manage.py createsuperuser python manage.py collectstaticcollectstatic是部署前必做的一步Django admin 的 CSS、JS 会复制到STATIC_ROOT指定的目录如果跳过线上后台会变成没有样式的纯文字页面。DEBUGFalse之后只有执行过collectstatic静态资源才能被 nginx 正确 serve。5.2 源码交付的常见坑与排错表交付源码包时踩坑记录通常比功能说明更值钱。下面这几项是“django 项目”落地里几乎必遇到的我把现象和处置方案做成对照可以直接写进交付文档。现象根因处置mysqlclient安装报错缺少 MySQL 客户端编译依赖Linux 装libmysqlclient-devWindows 用 pymysql 替代admin 后台无样式未执行collectstatic或STATIC_ROOT未配置配置STATIC_ROOT并运行 collectstatic上传图片 404MEDIA_URL路由没挂在 urls.py 里static()追加或交给 nginx alias保存时间差 8 小时TIME_ZONE默认 UTC改为Asia/Shanghai并确保 MySQL 连接串 timezone 一致预约列表查询慢N1 查询列表视图加select_related/prefetch_related排错表的作用是让接手者不必从头读源码“django install mysqlclient”这类问题在真机部署时几乎人人都会遇到。在看到报错黑窗时直接对表查根因比读一屏 traceback 快得多。5.3 一个值得写进源码包的验证技巧自定义 management command 造演示数据最后给一个源码交付时非常讨巧的做法写一个 Django 自定义命令一键生成演示数据。门店老板验收系统时最想知道的是“你的系统有数据是啥样”而不会接受一个空荡荡的后台。# shop/management/commands/seed_demo.py import random from django.core.management.base import BaseCommand from django.utils import timezone from customer.models import Customer, Car from shop.models import ServiceItem, Appointment class Command(BaseCommand): help 生成演示客户、车辆和预约数据 def add_arguments(self, parser): parser.add_argument(--customers, typeint, default10) def handle(self, *args, **options): count options[customers] for i in range(count): customer, _ Customer.objects.get_or_create( phonef1380000{i:04d}, defaults{name: f演示客户{i}} ) Car.objects.get_or_create( plate_nof京A{i:04d}, defaults{customer: customer, brand: 大众, mileage: 20000} ) services list(ServiceItem.objects.all()) if services: Appointment.objects.create( customerCustomer.objects.first(), carCar.objects.first(), appoint_timetimezone.now() timezone.timedelta(hours2), statuspending, ) self.stdout.write(self.style.SUCCESS(f已生成 {count} 位演示客户))这个命令的实用点在于BaseCommand的add_arguments使用 argparse 语法调用方可以执行python manage.py seed_demo --customers 30控制生成量get_or_create保证命令可重复执行而不产生重复脏数据self.style.SUCCESS是 Django 内置的控制台绿色输出。配合一个空的测试库交付前跑manage.py migrate manage.py seed_demo manage.py runserver一个新环境从裸机到完整后台 demo 数据只需要三分钟。验收人员第一次打开 admin 界面看到的不是空白列表而是带客户姓名、车牌号和待确认订单的页面这个第一印象往往直接决定源码包能不能过验收验完自行再刷一遍即可清空演示数据。本文还有配套的精品资源点击获取
返回列表