☰
基于Django的荣誉证书管理系统设计与实现
2026/10/22 11:29:40 网站建设 项目流程

这个题目我熟。前前后后帮人看过好几个类似的项目,自己也动手做过一版,从需求分析到部署上线踩了不少坑。荣誉证书管理系统听着不起眼,但真做起来,它比普通CRUD项目有意思多了——涉及文件上传、模板渲染、权限控制、Excel导入导出,甚至还能扯上PDF生成,一个小系统能把Django的大部分常用模块都摸一遍。如果你是拿它做毕业设计或者课程设计,这篇文章应该能帮你省掉不少弯路。

先说清楚这个系统是干什么的。说白了,就是把单位或者学校里发的各种荣誉证书管起来:谁在什么时候拿了什么奖、证书编号是多少、有没有发到人手上。以前都是用Excel表记,时间长了一堆文件堆在文件夹里,查起来费劲,统计更费劲——领导问一句“今年校级以上的奖项发了多少个”,光翻表就能翻半天。证书管理系统就是把这一套流程搬到Web上:录入、修改、搜索、筛选、统计、导出,全程可视化,还能按模板批量打印证书。

技术选型是Python加Django,这个组合在中小型管理系统里非常能打。Django自带Admin后台、ORM、用户认证和CSRF防护,一个人三天就能把骨架搭起来,比用Flask自己拼积木省心太多。加上Python生态里的openpyxl、pandas,做数据导入导出几乎是无痛操作。下面我把整个项目的设计思路、数据库表结构、核心功能实现和部署调试的坑,一条条拆开讲。

1. 项目整体设计与需求拆解

1.1 核心需求解析

做任何一个管理系统,第一步都不是写代码,而是把角色和流程盘清楚。荣誉证书管理系统里,角色大致分两类:

  • 管理员:负责证书录入、审核、发放记录、系统配置,通常对应老师或部门负责人
  • 普通用户:查询自己的证书、在线预览和下载电子版,对应学生或员工

这两个角色的权限天然不同,所以在设计阶段就要把权限模型定下来,不然后面每写一个视图都得考虑“谁能访问”,非常痛苦。

从业务流程上看,证书的流转路径通常是:奖项申报 → 审批通过 → 证书生成 → 发放登记 → 归档统计。系统不需要管前两步(那是另外的系统),但要把“证书生成”和“发放登记”做好:管理员录入一条证书数据,系统按模板渲染出证书图片或PDF,发放后标记状态,最后按部门、时间、奖项类别做多维统计。

还有两个容易被忽略但实际很关键的需求:

第一是批量操作。学校发奖经常是几十上百个学生一起发,一条条手工录入会累死人,所以系统必须在列表页支持批量导入Excel和批量导出,最好还能勾选多条记录一起改状态或者删除。

第二是证书模板的灵活性。不同奖项的证书长得不一样,有的横版有的一竖版,有的学校要求中间带校徽。如果模板写死在代码里,每次换格式都要改代码重新部署,太费劲。所以模板要用独立的表存起来,内容用HTML加占位符(比如{name}、{award}、{date}),管理后台里可以维护多套模板,渲染时自动替换变量。

1.2 技术架构与方案选型

技术栈选型上,我最终定的是:

  • 后端:Python 3.10 + Django 4.2
  • 前端:Bootstrap 5 + jQuery(没上Vue,因为我要考虑后续交给基础薄弱的同学维护,Bootstrap全家桶好改)
  • 数据库:MySQL 8.0(开发环境也可以SQLite,省心)
  • 文件存储:本地media目录
  • Excel处理:openpyxl
  • PDF生成:WeasyPrint

为什么用Django 4.2而不是直接上5.0?4.2是长期支持版本,官方维护周期长,小毛病少。做这类管理系统,稳定性比追新版本重要,别在答辩前夜发现某些第三方库和新版Django不兼容,那就尴尬了。

为什么不用Vue/React前后端分离?纯属性价比问题。这个场景下页面量不大、交互不复杂,Django的模板引擎加上少量Ajax就够用了。前后端分离意味着要处理跨域、Token认证、多套部署,对一个人的小项目来说纯纯增加工作量,收益却很小。这不是技术鄙视链的问题,是“多少人力干多少活”的问题。

多说一句WeasyPrint。它能把HTML加CSS渲染成PDF,中文字体支持也还行。证书打印这种场景最合适,因为模板本身就是HTML,同一套模板既能网页预览又可以生成PDF,不用写两套视图。

2. 数据库设计与核心模型实现

2.1 数据表关系规划

管理系统的根基是数据表设计,表结构没想清楚,后面写视图全是补丁。荣誉证书系统的核心表我拆成了六张,下面表格里列的是重点字段:

表名核心字段作用
CertificateType奖项名称、等级、默认模板奖项类型维护
Recipient姓名、工号/学号、所属单位、联系方式获奖人员信息
Certificate证书编号、获奖人外键、奖项外键、颁发日期、状态、文件路径证书主表
Template模板名称、模板内容(HTML)、缩略图证书模板管理
OperationLog操作人、操作类型、操作时间、详情操作留痕
User用户名、密码、角色类型登录与权限

核心关系是:Certificate表通过外键同时关联Recipient表与CertificateType表,Template表则通过外键或类型字段关联到CertificateType。也就是说,一条证书记录必然能追溯“谁”“得了什么奖”“用了哪套模板”。

这里有一个设计细节值得说一说:荣誉证书是有编号的,但证书编号往往不是纯数字,而是类似“XX-2024-00123”的格式。这个编号必须做成唯一索引,因为同一期发奖的编号不能重复。但要注意,编号的格式在录入前是不确定的,所以最好让用户手工输入,而不是程序自动生成,系统只负责做唯一校验。

2.2 关键模型代码拆解

用Django的ORM来写,模型层就清爽很多。我在models.py里定义了下面这几个类,关键注释直接写在代码里:

from django.db import models from django.contrib.auth.models import User class CertificateType(models.Model): name = models.CharField('奖项名称', max_length=128, unique=True) level = models.CharField('奖项级别', max_length=16, choices=[('school', '校级'), ('province', '省级'), ('national', '国家级')]) default_template = models.ForeignKey('Template', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='默认模板') class Meta: verbose_name = '奖项类型' ordering = ['id'] def __str__(self): return f'{self.get_level_display()}-{self.name}' class Recipient(models.Model): name = models.CharField('姓名', max_length=64) student_id = models.CharField('学号/工号', max_length=32) department = models.CharField('所属单位/院系', max_length=128) phone = models.CharField('手机号', max_length=20, blank=True) class Meta: verbose_name = '获奖人' unique_together = ('name', 'student_id') def __str__(self): return f'{self.name}({self.student_id})' class Certificate(models.Model): STATUS_CHOICES = [ ('draft', '待打印'), ('printed', '已打印'), ('signed', '已签收'), ] cert_no = models.CharField('证书编号', max_length=64, unique=True) recipient = models.ForeignKey(Recipient, on_delete=models.CASCADE, verbose_name='获奖人') cert_type = models.ForeignKey(CertificateType, on_delete=models.CASCADE, verbose_name='奖项类型') issue_date = models.DateField('颁发日期') status = models.CharField('状态', max_length=16, choices=STATUS_CHOICES, default='draft') file_path = models.FileField('证书文件', upload_to='certs/%Y/%m/', blank=True, null=True) class Meta: verbose_name = '证书' ordering = ['-issue_date'] def __str__(self): return f'{self.cert_no}-{self.recipient.name}' class Template(models.Model): name = models.CharField('模板名称', max_length=128) content_html = models.TextField('HTML模板内容') is_active = models.BooleanField('是否启用', default=True) class Meta: verbose_name = '证书模板'

三个地方值得说明:

第一,Recipient表的unique_together。一个人可能拿多个奖,但是同一个人加学号去重是合理的设定,避免一个人重复录入两条几乎一样的信息。

第二,Certificate表里recipient和cert_type都用on_delete=models.CASCADE。这意味着删除获奖人或者奖项类型时,关联的证书记录也会被删掉。实际项目中这个策略要有意识地用——如果只是换了一个人输入法,不小心删错人,CASCADE就直接把历史证书也带走了,很危险。更稳妥的是PROTECT,被引用时不让删,要求先处理证书数据。不过考虑到毕设环境里有后台管理能力,CASCADE也能接受,要紧的是自己心里清楚删数据时会发生什么。

第三,file_path字段的upload_to='certs/%Y/%m/'会自动按年月生成子目录,避免一个文件夹里塞几万张证书图片导致浏览卡顿。这个路径写在数据库里,读取时再拼接MEDIA_URL,会灵活很多。

2.3 为什么要单独建OperationLog表

Django有Admin后台,管理员操作其实自带变更日志,但那只是记录“改了哪条记录”,记录不了“从Excel导入了100条证书”或者“批量给50个人生成了PDF”这种动作。所以我自己加了一张OperationLog表,在Service层里用一个公共方法记日志:

def write_log(user, action, detail): OperationLog.objects.create( operator=user, action=action, detail=detail )

别小看这张表,答辩的时候老师大概率会问“系统怎么保证数据操作可追溯”。Django自带日志不够直观,自己写一张表把关键操作记录下来,演示给老师看什么时间谁导入了数据、谁修改了证书状态,这个细节非常加分。

3. 核心功能实现与关键代码实操

3.1 登录认证与权限控制

Django自带的django.contrib.auth已经把登录、登出和Session管理做好了,我不打算重复造轮子。但默认的User表没有“角色”的概念,做权限控制时我直接在User上扩展了一个Profile模型:

class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) role = models.CharField('角色', max_length=16, choices=[('admin', '管理员'), ('user', '普通用户')], default='user')

视图层面用@login_required确保只有登录用户能进系统,再用@user_passes_test限制管理员专属功能:

from django.contrib.auth.decorators import login_required, user_passes_test def is_admin(user): return hasattr(user, 'profile') and user.profile.role == 'admin' @login_required @user_passes_test(is_admin) def certificate_list(request): ...

写代码时有个坑:@user_passes_test判断的是当前登录用户,如果直接在URL上访问管理员页面但没登录,Django会跳到登录页;但如果登录了却不是管理员,会直接跳404。这个行为对普通用户来说也算合理,不暴露页面存在性。

3.2 证书的增删改查与条件筛选

证书列表页是用户打开系统的第一屏,体验好坏直接影响整个项目的观感。我做列表页的核心思路就一句话:接口不做成写死的,所有查询都用QuerySet链式过滤,屏幕上放几个筛选控件,每个控件对应一个Filter条件,前端提交后拼接QuerySet。

视图代码大概是这样的:

from django.db.models import Q def cert_list_view(request): queryset = Certificate.objects.select_related('recipient', 'cert_type').all() kw = request.GET.get('keyword', '').strip() cert_type_id = request.GET.get('type', '') status = request.GET.get('status', '') if kw: queryset = queryset.filter( Q(cert_no__icontains=kw) | Q(recipient__name__icontains=kw) | Q(recipient__student_id__icontains=kw) ) if cert_type_id: queryset = queryset.filter(cert_type_id=cert_type_id) if status: queryset = queryset.filter(status=status) page_obj = Paginator(queryset, 15).get_page(request.GET.get('page')) ...

重点是select_related。Django的ORM在查询外键关联数据时默认是惰性的,每次访问certificate.recipient.name都会发一条SQL,列表页一页15条数据就要额外执行15次查询,数据量上来后页面会肉眼可见地变慢。加上select_related('recipient', 'cert_type'),ORM在查询证书表时就通过JOIN一次性把关联数据全取回来,N+1问题当场解决。我见过不少初学者写的列表页卡得厉害,都是这个细节没注意。

筛选控件怎么实现?如果每一项都是手写原生HTML,页面里全是if判断,代码会非常丑。Django的Form组件在这里价值很大。我定义一个CertFilterForm,字段就设成keyword、type、status,渲染时用{{ form.keyword }},既能自动保留上次选择的状态,又不用手工拼接URL参数。整个筛选功能大概20行代码就写完了,强烈推荐。

3.3 证书模板渲染与PDF导出

这是整个系统技术含量最高的模块,值得多花点笔墨。

证书模板用HTML存到数据库里,模板内容里预留占位符。比如一份校级证书的模板HTML是这样:

<div class="certificate-box"> <h2>荣誉证书</h2> <p>兹证明 <span class="name">{{ name }}</span> 同学于 {{ year }} 年荣获 {{ award_name }}</p> <p>特发此证,以资鼓励。</p> <div class="date">颁发日期:{{ issue_date }}</div> </div>

渲染的时候从数据库里把这个HTML读出来,用Python的字符串替换把{{ name }}这些占位符替换成真正的数据:

def render_certificate(cert, template): content = template.content_html content = content.replace('{{ name }}', cert.recipient.name) content = content.replace('{{ year }}', str(cert.issue_date.year)) content = content.replace('{{ award_name }}', cert.cert_type.name) content = content.replace('{{ issue_date }}', cert.issue_date.strftime('%Y年%m月%d日')) return content

有同学问过,为什么不用Django模板引擎的render_to_string?其实也可以,但模板引擎有转义机制,而且变量名一多很难调试。直接用replace更直观,毕竟模板是给管理员维护的,不搞太复杂反而好。前端需要预览时,返回这段HTML并配上一套打印用的CSS,浏览器自带打印功能;后端需要批量生成PDF时,就用WeasyPrint把HTML转成PDF文件保存到media目录。

这里必须提醒一个中文PDF的坑。WeasyPrint默认字体在高版本系统上不一定带中文字体,直接转出来的PDF全是方块。解决办法是在CSS里显式指定系统自带的中文字体:

body { font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "SimSun"; }

同时确保系统装好对应字库。在服务器上跑PDF生成之前,先在命令行执行一项检查,确认字体存在。我就在这个坑上踩过,本地Windows上开发好好的,放到Linux服务器全乱码,查了半天才知道是字体没装。

3.4 Excel批量导入的理想与实现

批量导入功能需要先说清楚边界:它是“辅助录入”,不是“自动导入”。意思是要让管理员把数据填到一个固定模板里,上传之后系统校验数据合法性,合法的写入数据库,不合法的返回错误逐行提示,不能再往复杂里做。

实现思路用openpyxl读取文件,逐行校验再写入:

import openpyxl from django.shortcuts import render, redirect def import_certificates_view(request): if request.method == 'POST': excel_file = request.FILES.get('file') wb = openpyxl.load_workbook(excel_file) ws = wb.active errors = [] success_count = 0 for row in ws.iter_rows(min_row=2, values_only=True): name, student_id, award_name, issue_date = row try: recipient, _ = Recipient.objects.get_or_create( name=name, student_id=student_id, defaults={'department': request.POST.get('default_department', '')} ) cert_type, _ = CertificateType.objects.get_or_create(name=award_name) cert_no = generate_cert_no(request.POST.get('prefix'), issue_date, award_name) cert = Certificate.objects.create( cert_no=cert_no, recipient=recipient, cert_type=cert_type, issue_date=issue_date, ) success_count += 1 except Exception as e: errors.append(f'第{ws.iter_rows().index + 1}行: {e}') write_log(request.user, '批量导入', f'成功{success_count}条,失败{len(errors)}条') return render(request, 'core/import_result.html', { 'success_count': success_count, 'errors': errors, })

逐行校验是一个效率和体验的平衡点。要求一次性全部成功也不现实,Excel里偶然后面几行出错是常见情况。成功的写库,失败的在页面上反馈具体行数和原因,让管理员去Excel里改完重新上传,这个交互模式是我在多个项目里跑下来最省事的。

按照固定格式的模板做好之后,用户拿到Excel文件最常犯的错误是日期格式不对。有人填"2024-05-01",有人填"2024/5/1",还有填"二零二四年五月一日"的。openpyxl读到的日期对象和字符串混在一起,必须做一层容错转化,我在项目里写了一个parse_date的辅助函数,专门处理这些情况:

from datetime import datetime def parse_date(value): if isinstance(value, datetime): return value.date() if isinstance(value, str): for fmt in ('%Y-%m-%d', '%Y/%m/%d', '%Y.%m.%d'): try: return datetime.strptime(value, fmt).date() except ValueError: continue return None

3.5 证书发放状态的流转

证书从模板打印出来之后,不能永远停留在“待打印”的初始状态。管理员操作的关键路径是:先勾选一批证书,点击“打印”,系统生成PDF或调用前端打印模板,打印完成之后把状态改为“已打印”,最后在实际发到人手里后登记为“已签收”。

状态的流转我在列表页上做了一个批量操作按钮,视图层用一个字典维护合法流转:

def batch_update_status(request): ids = request.POST.getlist('cert_ids') target_status = request.POST.get('target_status') allow_map = { 'printed': ['draft'], 'signed': ['draft', 'printed'], } certs = Certificate.objects.filter(id__in=ids) updated = 0 for cert in certs: if cert.status in allow_map.get(target_status, []): cert.status = target_status cert.save() updated += 1 ...

为什么状态要卡得这么严?因为一张证书一旦标记了“已签发”,它背后的编号就占用了,撤销会很麻烦。权限上也要约束一下:录入员可以记“已打印”,只有管理员才能改“已签收”。我在写代码时给这两个操作分别加了权限校验函数,不能图省事全部放行。

4. 项目部署、调试文档与常见问题排查

4.1 静态文件与媒体文件配置

Django开发模式下静态文件是自动服务的,但一旦关了DEBUG跑部署,所有静态文件会全部404。这个坑几乎每个Django初学者都会踩一遍。我的做法是项目一开始就把settings.py里相关配置写对,后面部署少折腾:

# settings.py STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles' STATICFILES_DIRS = [ BASE_DIR / 'static', ] MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'

项目根目录建立一个static目录放CSS和JS,图片、PDF等上传文件由media目录托管,收集完静态文件后Web服务器直接指向STATIC_ROOT,访问路径自然就通了。模板里要写{% load static %}再引etag,避免浏览器缓存旧版本导致改了CSS页面不变,我习惯在样式表路径后面加个版本号参数,比如<link rel="stylesheet" href="{% static 'css/base.css' %}?v=20240101">,发版时手动改一下就好了。

4.2 数据库连接与时区配置

开发用SQLite是没问题的,但部署到线上建议还是换MySQL,性能差一个量级。settings.py关键配置如下:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'cert_db', 'USER': 'root', # 不要明文写在代码里,用环境变量读 'PASSWORD': os.getenv('DB_PASSWORD', ''), 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', }, } } # 这个必须设成False,否则时间字段和真实时间对不上 USE_TZ = False TIME_ZONE = 'Asia/Shanghai'

设置USE_TZ = False有两个原因:一是证书管理系统绝大部分用户都在国内,没有必要用UTC存储时间;二是节省脑力,Django开启时区支持后,所有datetime字段的读写都会经过时区转换,稍不留神存进去的时间就比实际早8小时。小项目不涉及全球化部署,直接关掉时区支持是最稳妥的选择。

MySQL还涉及一个中文排序问题。证书列表页按奖项名称排序的时候,中文默认不能按拼音排。可以在排序字段上加字符集collation设置,或者干脆查询时用ORDER BY CONVERT(name USING gbk)。不过这类需求在课程设计里提得不多,真遇到了再处理就行。

4.3 调试文档怎么组织才有价值

源码交付的时候,“调试文档”并不是随便复制几个命令拼出来的东西。我的习惯是维护一份Debug.md,按下面这个目录写:

  • 环境版本清单(Python、Django、MySQL主版本号)
  • 安装步骤(创建虚拟环境、安装依赖、配置数据库、迁移)
  • 常见报错速查表(按报错信息归类,附解决方式)
  • 测试账号清单(管理员、普通用户各一个,写清楚密码)
  • 部署要点(Nginx配置、uwsgi命令、环境变量生效方式)

写调试文档有一个心法:把“你可能遇到的问题”和“你为什么遇到这个问题”一起写进去。比如“celery配置内存队列,consol但Celery在DEBUG=True下只能用gevent的替代方案”。单写命令没意义,真正有价值的是一句“这个问题常见于Django 2.0以上的环境,原因是xxx”。答辩时老师可能会翻你的文档,看到这种内容会认可你真的调过,态度分很稳。

4.4 高频坑位清单

把我在开发过程中遇到的、以及帮别人看代码时发现的高频问题整理成一张速查表,新手依表排查基本能对付:

现象可能原因解决思路
表单提交报CSRF错误模板里忘了加{% csrf_token %}在form标签里加上该标记
图片上传后访问404MEDIA_URL没有配路由urlpatterns里加+ static(settings.MEDIA_URL, document_root=...)
时间显示比实际多8小时USE_TZ=True但没设置TIME_ZONE设置USE_TZ=False或配置TIME_ZONE='Asia/Shanghai'
Excel导入中文乱码文件编码不是UTF-8上传前让用户转换成UTF-8编码,或引入chardet自动检测
数据库写入中文报错MySQL库表是latin1建库时指定utf8mb4字符集
列表页面卡顿外键查询没有select_related按关联字段补上select_related,可观察ORM的queries日志验证
部署后静态文件找不到没执行collectstaticpython manage.py collectstatic --noinput,然后检查STATIC_ROOT权限
生产环境每次请求都要重新编译模板模板改动了但没重启进程Django不会自动重载,需要手动重启服务进程

排查问题最大的心得是,配好日志。settings.py里加一个简简单单的logging配置,把Django请求日志和错误日志写到文件,很多问题看一眼日志就明白了,比在页面里一行行print强太多。

5. 从课程设计到实际可用的细化空间

5.1 证书批量打印的一体化方案

说这个系统只能打印“单张”就太浪费了。我在项目里给批量打印做了个优化:勾选一批证书后,后端把它们渲染到同一个HTML页面里,每个证书独占一个A4区域,用print CSS把每个区域设置为固定尺寸,浏览器打印时直接套打出多张证书。打印出来的效果和人工一张张贴上去几乎没差。这个方案还给前端省去了反复调用的麻烦,直接一个页面全部搞定。

@media print { .certificate-item { page-break-after: always; width: 210mm; height: 297mm; } }

批量打印模式下,打印状态自动更新就顺理成章:渲染的同时把cert的状态改成“已打印”。这个改动量不大,省掉了手动更新状态的步骤,体验提升却不小。

5.2 导出报表与统计看板

数据只是录进去,没有统计能力,那跟Excel没有任何区别。我做了两个维度的统计:一是在Dashboard首页放一组KPI卡片——总证书数、本月新增、待打印数量、待签收数量;二是在统计页面做按部门和奖项类型的柱状图,用Chart.js画,前端引入一个JS文件就够了。

<canvas id="typeChart" width="400" height="200"></canvas> <script> fetch('/api/statistics/types/') .then(res => res.json()) .then(data => { new Chart(document.getElementById('typeChart'), { type: 'bar', data: { labels: data.labels, datasets: [{ label: '证书数量', data: data.values }] } }); }); </script>

为了这个图表,我专门写了一个只返回JSON的API视图,配合JsonResponse使用,开发难度几乎为零。统计口径是:状态里除“草稿/待打印”的都算作有效证书,再按奖项表和证书表按类型分组合计。这个需求老师问起来有得聊,也有实际价值。

5.3 通知提醒与邮件扩展

证书该到期了没有人提醒,系统价值会打折扣。我在模型里预留了expiry_date字段,没在正文提,但后台写了一个定时任务,用系统自带的django-admin命令扫描未来7天内到达有效期截止日的证书,给管理员发提醒邮件。定时调度用crontab配合管理命令运行,完全没引入新的中间件。

如果以后真有同学想把它做成商业系统,这个提醒功能还能升级成给获奖人发短信通知、提示在线查看电子版证书。从一个管理工具变成一个服务体系,延展空间其实不小。但那是后话,先把当前的系统跑顺比什么都重要。

5.4 从“能用”到“好用”的三个心得

第一,坚决用Django自带的东西,别反复造轮子。Admin后台在数据维护阶段能大幅降低建数据成本,登录认证能省下大把时间,这些东西在你能力范围之内可能写得比Django更快,但永远不会比Django更稳。毕设答辩就是个拼稳定性的场合,一旦在演示中途500了,前面全白做。

第二,花时间做一点“看起来无意义的小细节”。比如列表页的批量选择(全选框的半选状态),退出登录后跳回首页的提示,导入Excel失败后在页面上标记出错行数。这些细节回答不了流量问题,但能撑起项目的完整度。老师翻代码时大概率先看界面操作流不流畅,这些小交互的印象分比你注释里写一百行说明都管用。

第三,数据库迁移记录别乱删。开发过程里改models.py是常态,每次改了都要python manage.py makemigrations,并且不要因为想“重置干净数据库”就跑migrate certificates zero。这个操作会把整个迁移链打断,如果你已经写了一部分依赖这些表的视图,再改就得手改了。我吃过这个亏,最后是开了个新虚拟环境重新初始化数据才搞定。

这个项目整体难度对一个人来说不算大,但胜在覆盖面广、贴近真实业务、深度也可挖。只要你把每一层的逻辑都想明白、把状态切清楚,评审时能讲出你的设计决策,这个系统的完成度和说服力就会比同批次的普通课程设计高出一截。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询