news 2026/9/13 15:44:46

基于Django的旅游信息管理系统:从源码到部署的完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Django的旅游信息管理系统:从源码到部署的完整实战

简介:基于Django的旅游信息管理系统源码是一份面向毕业设计及Python Web初学者的完整项目。系统覆盖用户注册登录与权限管理、景点信息维护、线路规划、预订服务、评论评分及后台管理等典型模块,贯穿Django的MVT架构、ORM数据库操作、表单处理与安全防护机制,适合理解企业级Web应用的开发流程与工程组织方式。资源包共788个文件,大小18.41MB,以45个Python源码、53个Vue组件、164个JavaScript脚本及53个CSS样式为主,同时包含HTML页面、SQL数据库脚本、图片图标素材与一键启动批处理文件,目录结构清晰。已有159人学习下载。借助源码可掌握Django与Vue前后端协同开发的思路,学习通过Admin后台管理数据、处理用户会话与权限,并参考内置数据库脚本和启动命令快速搭建运行环境,为独立完成毕业设计或进阶Django开发提供扎实的实践参考。

1. 旅游信息管理系统源码,难点从来不在增删改查

打开任何一个软件下载站,输入"Django 旅游信息管理系统",能搜出几十个版本差不多的源码包。它们都叫这个名字,功能列表也都写着景点管理、线路推荐、订单预订、后台管理。真正拉开差距的,是这套源码能不能在你本机跑起来、能不能换数据库、能不能改造成自己想要的业务模型。多数人下载后卡在第一步:依赖装不上、编码报错、admin后台样式丢失、静态文件404。

这个标题里最有价值的不是"旅游信息管理"六个字,而是Python和Django的组合方式。旅游信息管理系统是一个典型的中量级Web业务系统,它同时涉及多表关联查询、文件上传、后台权限管理、前台模板渲染,恰好覆盖了Django框架最常用、面试最常问的能力面。对有3年以上Python经验的人来说,这套系统是理解Django ORM与Admin机制的最佳样本;对刚入行的开发者,它是能把"会Python语法"转化成"会做Web项目"的完整链路。

2. 从源码包到能跑:Django项目结构与本地最小启动

2.1 拿到压缩包后,先看目录结构再动手

源码zip解压后,第一件事不是急着配数据库,而是先花两分钟看目录长什么样。一个标准的Django项目,哪怕是别人打包的,也躲不开这几样东西:manage.py、项目配置目录(通常叫config或与项目同名)、业务应用目录(apps或直接平铺的scenichotelorder之类)、模板目录templates、静态文件目录static,以及依赖清单文件requirements.txt

unzip tourism_django.zip cd tourism_django ls -la ├── manage.py ├── requirements.txt ├── config/ │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── scenic/ │ ├── routes/ │ └── order/ ├── templates/ └── static/

这段命令先解压再列目录。manage.py是所有Django管理命令的入口,无论是启动开发服务器、执行数据库迁移,还是创建超级管理员,都要通过它。config/settings.py是整个项目的总开关,数据库、安装的应用、模板路径、语言时区都在这一个文件里集中配置。apps目录下按业务拆分的子应用,每条业务线有自己的models.pyviews.pyurls.py,这是Django推荐的分层方式——按业务域划分,而不是按技术层划分。

2.2 虚拟环境、Python版本与依赖安装的连带坑

这一步踩坑率极高。很多人直接pip install -r requirements.txt,结果报错一大片。原因通常是Python版本与Django版本不匹配,或者项目依赖了mysqlclient这类需要系统编译库的包。

我一般会先确认Python版本,建议用3.8到3.11之间的版本,太老或太新都会在某个包上出问题。然后创建虚拟环境:

python3.10 -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txt

venv是Python自带的虚拟环境工具,python3.10指定解释器版本,source venv/bin/activate把当前shell切换到虚拟环境中。后续所有pip安装的包都落在venv目录内部,不会污染系统Python。upgrade pip先升级安装器本身,能减少很多莫名其妙的is not a supported wheel错误。如果requirements.txt里锁了Django 3.2 LTS,那Python 3.10完全兼容;如果锁的是Django 4.x,Python 3.8以上也能跑。

2.3 settings.py里必须核对的三类配置项

依赖装完,先别急着runserver。打开settings.py,核对三个地方:

# config/settings.py INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "apps.scenic", "apps.routes", "apps.order", ] DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } } LANGUAGE_CODE = "zh-hans" TIME_ZONE = "Asia/Shanghai"

第一项是INSTALLED_APPS,写完业务应用后必须在这里登记,否则Django完全不知道apps.scenic存在,makemigrations时也找不到对应模型。第二项是数据库连接,源码包默认用SQLite,好处是零配置、文件型数据库,适合本地起步。第三项是语言和时区,LANGUAGE_CODE设为zh-hans才能让admin后台显示中文,TIME_ZONE不设成Asia/Shanghai的话,DateTimeField的自动写入时间会跟北京时间差8小时。

2.4 迁移、创建管理员、启动一条链

配置核对完毕,执行以下命令完成数据库初始化和登录账户创建:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000

makemigrations会扫描INSTALLED_APPS中每个应用的models.py,把模型类翻译成迁移文件,相当于生成一张"待执行的数据库变更清单"。migrate才是真正把变更清单落到数据库里的动作,包括Django自带的用户表、权限表也会在这一步创建。createsuperuser交互式地让你输入用户名、邮箱、密码,这是进入admin后台的凭证。runserver 0.0.0.0:8000中,0.0.0.0表示监听所有网卡,允许局域网其他机器访问开发服务器,默认为127.0.0.1时只能本机访问。

启动成功后,访问http://127.0.0.1:8000/admin,能看到后台登录页。这里能登录,说明项目骨架已经活了。接下来的一切,都是在验证这个骨架里长了哪些器官。

3. 核心模型与Admin后台:景点、线路、订单的数据层设计

3.1 Django ORM模型设计:一张表就是一个Python类

旅游信息管理系统的数据模型,不外乎几个核心业务对象:景点(ScenicSpot)、旅游线路(TourRoute)、酒店(Hotel)、下单记录(Order)。它们之间的关系很典型:一条线路包含多个景点,一个用户可以下多笔订单。这种"一对多"与"多对多"的组合,是Django ORM设计能力的分水岭。

# apps/scenic/models.py from django.db import models from django.contrib.auth.models import User class ScenicSpot(models.Model): name = models.CharField("景点名称", max_length=128) city = models.CharField("所在城市", max_length=64) level = models.CharField("景区等级", max_length=16, choices=[ ("A", "A级"), ("AA", "AA级"), ("AAA", "AAA级"), ("AAAA", "AAAA级"), ("AAAAA", "AAAAA级"), ], default="A") price = models.DecimalField("门票价格", max_digits=10, decimal_places=2) img = models.ImageField("景点图片", upload_to="spot/", blank=True) detail = models.TextField("景点介绍", blank=True) class Meta: db_table = "scenic_spot" def __str__(self): return self.name class TourRoute(models.Model): name = models.CharField("线路名称", max_length=128) days = models.PositiveIntegerField("行程天数") spots = models.ManyToManyField(ScenicSpot, related_name="routes", verbose_name="包含景点") price = models.DecimalField("线路价格", max_digits=10, decimal_places=2) class Meta: db_table = "tour_route" class Order(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="orders", verbose_name="下单用户") route = models.ForeignKey(TourRoute, on_delete=models.PROTECT, verbose_name="预订线路") num = models.PositiveIntegerField("预订人数", default=1) status = models.CharField("订单状态", max_length=16, choices=[ ("unpaid", "待支付"), ("paid", "已支付"), ("canceled", "已取消"), ], default="unpaid") created_at = models.DateTimeField("下单时间", auto_now_add=True) class Meta: db_table = "order"

这里的每个字段都有讲究。DecimalField用于价格而不是FloatField,是因为浮点数做金额计算会产生精度误差,max_digits=10, decimal_places=2表示最多10位数字且保留2位小数,足够覆盖万元级票价和线路价。ManyToManyField自动生成中间关联表,related_name="routes"ScenicSpot对象可以通过.routes.all()反查出包含该景点的所有线路。on_delete=models.CASCADE在用户删除时级联删掉他的订单,符合业务语义;models.PROTECT用在订单和线路上,防止删掉一条仍有订单关联的线路,这是"受保护删除"策略。

3.2 Django Admin界面美化:list_display、list_filter与search_fields

admin后台是这套源码里"看得见"的部分。默认注册模型后,后台列表页只显示__str__的返回值,信息量不足。注册并配置展示字段是每个人拿到源码后必做的第一项定制:

# apps/scenic/admin.py from django.contrib import admin from .models import ScenicSpot, TourRoute, Order @admin.register(ScenicSpot) class ScenicSpotAdmin(admin.ModelAdmin): list_display = ("id", "name", "city", "level", "price") list_filter = ("city", "level") search_fields = ("name", "city") list_editable = ("price",) list_per_page = 20 @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ("id", "user", "route", "num", "status", "created_at") list_filter = ("status", "created_at") search_fields = ("user__username", "route__name")

list_display把元组里的字段各渲染成一列,list_filter会在右侧生成按城市和等级过滤的筛选面板,search_fields指定搜索框匹配的字段。注意user__username这种写法,双下划线在Django ORM里表示"跨表查询",即通过Orderuser外键去匹配User.username字段。

list_editable可以直接在列表页修改价格。但有一个限制:list_editable的字段不能同时出现在list_display里作为第一个字段,因为第一个字段默认被用作"修改入口"链接。原因是Django的admin会为第一个列生成指向编辑页的<a>标签,若该列也可编辑,链接与输入框会冲突。

3.3 执行查询与删除对象:双下划线和非级联的边界

Django学科里,objects.all()get()谁都会用,真正容易出错的是带条件的删除和跨表过滤:

# apps/scenic/views.py 示例 orders = Order.objects.filter( user__username="testuser", status="paid" ) deleted, _ = Order.objects.filter(status="canceled").delete()

filter(user__username="testuser")通过外键链访问用户表并精确匹配用户名,等价于SQL里的JOIN加上WHERE.delete()不是所有情况下都返回删除条数,deleted返回一个二元组,第一项是删除总条数。注意on_delete=models.CASCADE会让Django递归删除关联记录,而PROTECT会直接抛出ProtectedError异常。这对不同业务语义的模型做不同配置,是模型层设计成熟与否的重要标志。

4. 前台浏览与业务闭环:URLconf、视图、模板的联动实现

4.1 URLconf里的反向解析:url模板标签与reverse函数

前台页面加载出来的过程,是浏览器请求一个URL,Django根据urls.py里的路由规则把请求交给对应的视图函数,视图返回渲染后的HTML。这套机制里,path中新增的参数和name命名是双向绑定的关键:

# config/urls.py from django.urls import path, include urlpatterns = [ path("admin/", admin.site.urls), path("scenic/", include("apps.scenic.urls")), ]
# apps/scenic/urls.py from django.urls import path from . import views app_name = "scenic" urlpatterns = [ path("", views.scenic_list, name="scenic_list"), path("detail/<int:pk>/", views.scenic_detail, name="scenic_detail"), ]

app_name = "scenic"把当前应用的URL命名空间隔离出来,避免多个应用出现同名路由时冲突。<int:pk>是路径转换器,只匹配纯数字,并把值以整数类型传给视图函数。这样设计后,模板里可以用{% url 'scenic:scenic_detail' spot.id %}生成链接,视图里用reverse("scenic:scenic_detail", args=[spot.id])重定向,即使以后改了URL路径,只要name不变,所有引用处自动适配。

4.2 视图与模板的配合:分页、搜索与图片展示

列表页要解决的核心问题是"景点多怎么办"。一次性渲染全部景点,数据量上来了页面就卡。Django里最常用的方案是内置的Paginator

# apps/scenic/views.py from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage from django.shortcuts import render from .models import ScenicSpot def scenic_list(request): query = request.GET.get("q", "").strip() spots_all = ScenicSpot.objects.all().order_by("id") if query: spots_all = spots_all.filter( models.Q(name__icontains=query) | models.Q(city__icontains=query) ) paginator = Paginator(spots_all, 9) page_num = request.GET.get("page", 1) try: page_obj = paginator.page(page_num) except PageNotAnInteger: page_obj = paginator.page(1) except EmptyPage: page_obj = paginator.page(paginator.num_pages) return render(request, "scenic/list.html", { "page_obj": page_obj, "query": query, })

这段逻辑分三步走。用户提交搜索词后,icontains实现了大小写不敏感的包含匹配,models.Q|表示OR条件,两个字段有任何一个命中就返回。Paginator(spots_all, 9)把查询集切片成每页9条,page_obj本身就是包含当前页数据的对象。页面渲染时模板里用{% for spot in page_obj %}遍历数据,用{% if page_obj.has_previous %}控制上一页按钮的显隐。EmptyPage捕获用户手动把page=999的情况,回落到最后一页,避免直接抛404。

搜索框和分页按钮的组合是这类前台页面的标配,核心思路是"查询集不急着求值,等Paginator切完才真正执行SQL"。Queryset是惰性的,这为分页和组合过滤提供了天然的性能优势,也让过滤条件能够串联叠加而不产生多个查询。

4.3 权限与操作闭环:登录后才能下单

订单模块把未登录用户和已登录用户区分开,Django用装饰器就能完成这个控制:

from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404 @login_required def create_order(request, route_id): if request.method == "POST": route = get_object_or_404(TourRoute, id=route_id) num = int(request.POST.get("num", 1)) Order.objects.create( user=request.user, route=route, num=num, ) return redirect("route:route_list") return render(request, "order/create.html", {"route": get_object_or_404(TourRoute, id=route_id)})

@login_required默认行为是重定向到登录页,登录后在request.user上保留用户对象。get_object_or_404objects.get()的安全版本,查不到时抛404而不是DoesNotExist异常。Order.objects.create()一步完成实例化和保存,比自己save()少一行。这里的核心业务逻辑是把"登录用户""选定线路""预订人数"三项拼装成订单记录。

前台浏览、下单、后台管理、订单状态变更,整套系统就是由"列表页查询 → 详情页展示 → 带权限提交订单 → admin处理订单状态"这个闭环串起来的。Django把这条链路拆解成URL路由、视图函数、模板、ORM四次跳转,每个跳转点都能独立测试,也是这套源码最值得学习的地方。

5. 静态文件丢失、数据库切换与宝塔部署的三个收尾技巧

5.1 切MySQL数据库:mysqlclient的安装与utf8mb4

源码默认SQLite,但生产环境通常换MySQL。改动集中在settings.pyDATABASES配置,以及一套需要额外安装的驱动:

DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "tour_db", "USER": "tour_user", "PASSWORD": "你的强密码", "HOST": "127.0.0.1", "PORT": "3306", "OPTIONS": { "charset": "utf8mb4", }, } }

换驱动时提到mysqlclient,很多人在Linux上装这个包会直接报错,因为缺少mysql_config和编译头文件。Ubuntu系统的解决方式:

sudo apt-get install python3-dev default-libmysqlclient-dev build-essential pip install mysqlclient

default-libmysqlclient-dev提供了连接MySQL的C客户端库,build-essential提供了gcc编译器。mysqlclient是一个C扩展包,不是纯Python实现,所以编译依赖比pymysql重得多。如果项目里确实用pymysql,在__init__.py写入import pymysql; pymysql.install_as_MySQLdb()也可以,但性能与兼容性上多数项目仍青睐mysqlclient。切换后务必执行python manage.py migrate重建表结构,SQLite生成的db.sqlite3文件无法直接搬进MySQL。

5.2 admin后台样式丢失:STATIC_ROOT与collectstatic

runserver时admin后台样式正常,一旦改用Nginx提供服务,后台就剩一堆纯文本链接。这是静态文件收集环节没做。Django在开发阶段由runserver自动服务静态文件,生产模式下需要统一收集并交给Nginx托管:

python manage.py collectstatic

collectstatic把所有应用(包括admin自带的static目录)里的静态文件复制到STATIC_ROOT指定的目录。配置上要同时设好三件套:

STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "staticfiles" MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media"

MEDIA_ROOTMEDIA_URL是用户上传文件的存放位置和访问前缀,给ImageField上传的图片用。Nginx配置里把/static/指向staticfiles目录,把/media/指向media目录即可。很多源码包里的图片是ImageField的外链或者本地占位图,部署时忘记设置MEDIA_ROOT的话,前台图片会全部裂开。

5.3 宝塔部署Django:Gunicorn + Nginx的转发关系

宝塔面板等于把Nginx配置、Python环境、进程守护可视化。部署Django的常规链路是:Gunicorn作为Python应用服务器运行wsgi应用,Nginx反向代理接收外部请求并转发给Gunicorn。用gunicorn启动项目:

cd /www/wwwroot/tourism_django source venv/bin/activate gunicorn config.wsgi:application --bind 127.0.0.1:8001 --workers 3

config.wsgi:application指到项目里config/wsgi.py中的application对象。--bind只在本地端口监听,不直接暴露公网;--workers按照CPU核心数一般设为2*核数+1。随后宝塔的Nginx站点配置里把server_nameproxy_pass串起来:

location / { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /static/ { alias /www/wwwroot/tourism_django/staticfiles/; }

proxy_set_header X-Forwarded-For比较关键,Django的request.META['REMOTE_ADDR']在反代模式下拿到的是Nginx的地址,不是真实访客IP,这些请求头补齐之后视图里才能正确记录日志和调试。测试命令直接跑curl -I http://127.0.0.1:8001看返回头是不是200,排除Gunicorn的独立故障后再检查Nginx。

5.4 图片与周边资源的绝对化处理

最后补一个小技巧:settings里增加MEDIA相关的模板标签使用,模板中图片的地址应当写为{{ spot.img.url }}而不是{{ spot.img}}ImageField.url会在前面拼接上MEDIA_URL,避免图片相对路径导致页面上全部404。

这套源码的完整度,往往就体现在这些细节里。从本地SQLite跑通,到换MySQL,再到Nginx服务静态文件和Gunicorn处理动态请求,三步走完,一个能出门演示"基于Django的旅游信息管理系统"才算真正落地。

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

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

高职大数据与财务管理专业的数据分析教学实践

1. 为什么这个专业必须直面数据分析1.1 先看清行业变化&#xff1a;财务管理正在被数据重构这几年我跟不少高职院校的老师交流&#xff0c;大家普遍反映一个问题&#xff1a;大数据与财务管理专业每年招进来几百号学生&#xff0c;但很多学校实际教学还是老会计那套——基础会计…

作者头像 李华
网站建设 2026/9/13 15:44:30

Redis key数量失控?Shell脚本组合拳实现高效清理与长效治理

去年秋天我接到一单线上求助&#xff1a;某个 Redis 集群的内存其实还在可控范围&#xff0c;但是INFO keyspace里单节点 key 数量已经超过 1.2 亿。业务方一开始想扩容&#xff0c;我去看了一眼接入方式&#xff0c;发现问题的核心根本不在于内存&#xff0c;而在于 key 数量失…

作者头像 李华
网站建设 2026/9/13 15:43:19

Python实现地铁ACC数据分钟级客流预测系统

简介&#xff1a;本资源是一套基于Python开发的地铁客流预测系统完整实现&#xff0c;面向城市轨道交通数据分析初学者、交通工程专业学生及Python Web开发学习者&#xff0c;聚焦于利用ACC系统用户行程与站点数据开展线路级与站点级客流建模、预测与可视化预警。系统采用B/S架…

作者头像 李华
网站建设 2026/9/13 15:41:52

AR硬件交互原型系统:从光学标定到失效兜底的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:40:57

Prompt as Code:工业级提示词基础设施实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:40:14

CLT层合板Python建模:正交各向异性与A/B/D矩阵计算

简介&#xff1a;本资源是一套专为复合材料层压板力学分析设计的Python类库pyPLY&#xff0c;面向航空航天、汽车及土木工程领域的工程师与高校研究人员&#xff0c;解决经典层压理论&#xff08;CLT&#xff09;建模中材料定义、叠层配置、应力应变计算与失效评估等核心问题。…

作者头像 李华