Update views.py

master
pfy5v82cw 3 months ago
parent a667f550b1
commit 1f0772241d

@ -1,127 +1,237 @@
# Create your views here.
import datetime
import itertools
import json
import logging
from datetime import timezone
from itertools import groupby
import django
import requests
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.http import JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from .models import OwnTrackLog
# 说明:该文件为 Django 视图层核心文件,包含所有 /owntracks/ 路由对应的业务处理逻辑
# 视图函数职责:接收请求、处理数据(数据库读写/第三方接口调用)、返回响应(页面/JSON/状态码)
# 导入标准库模块
import datetime # 处理日期时间相关操作(如日期计算、格式化)
import itertools # 提供迭代器工具(如切片、分组,用于批量处理经纬度数据)
import json # 处理 JSON 数据序列化/反序列化(适配接口请求/响应)
import logging # 日志模块:记录业务日志(信息/错误),便于问题排查
from datetime import timezone # 处理时区相关(确保时间计算一致性)
from itertools import groupby # 分组工具按用户标识tid分组轨迹数据
# 导入第三方库/框架模块
import django # Django 核心模块(用于时区时间处理)
import requests # HTTP 请求库:调用高德地图坐标转换接口
from django.contrib.auth.decorators import login_required # 登录验证装饰器:限制未登录用户访问
from django.http import HttpResponse # 基础响应类:返回文本/状态码响应
from django.http import JsonResponse # JSON 响应类:返回 JSON 格式数据(适配前端异步请求)
from django.shortcuts import render # 页面渲染函数:加载模板并返回 HTML 页面
from django.views.decorators.csrf import csrf_exempt # CSRF 豁免装饰器:关闭跨站请求伪造保护(适配第三方客户端提交数据)
# 导入当前应用模块
from .models import OwnTrackLog # 轨迹数据模型:用于数据库读写操作
# 初始化日志对象:按当前模块名创建日志实例,日志输出会携带模块标识
logger = logging.getLogger(__name__)
@csrf_exempt
@csrf_exempt # 豁免 CSRF 验证:因客户端(如设备/第三方系统)可能无法提供 CSRF Token故关闭保护
def manage_owntrack_log(request):
"""
轨迹数据提交接口视图
功能接收客户端 POST 提交的 JSON 格式轨迹数据tid/经纬度验证后写入数据库
请求方式POST仅支持 POST其他方式会因缺少请求体报错
请求体格式{"tid": "用户标识", "lat": 纬度值, "lon": 经度值}
响应
- 成功写入返回 "ok"HTTP 200
- 数据不完整返回 "data error"HTTP 200
- 异常报错返回 "error"HTTP 200并记录错误日志
"""
try:
# 读取请求体:将 JSON 字符串解码为 Python 字典utf-8 编码适配中文/特殊字符)
s = json.loads(request.read().decode('utf-8'))
# 提取请求数据中的核心字段(用户标识、纬度、经度)
tid = s['tid']
lat = s['lat']
lon = s['lon']
# 记录信息日志:打印提交的轨迹数据(便于追踪数据流转)
logger.info(
'tid:{tid}.lat:{lat}.lon:{lon}'.format(
tid=tid, lat=lat, lon=lon))
tid=tid, lat=lat, lon=lon)
)
# 数据合法性校验:确保核心字段非空(避免写入无效数据)
if tid and lat and lon:
# 创建模型实例并赋值
m = OwnTrackLog()
m.tid = tid
m.lat = lat
m.lon = lon
m.save()
return HttpResponse('ok')
m.tid = tid # 用户标识
m.lat = lat # 纬度
m.lon = lon # 经度
# creation_time 字段使用默认值(当前时间),无需手动赋值
m.save() # 保存到数据库
return HttpResponse('ok') # 响应成功标识
else:
# 数据不完整:返回错误提示
return HttpResponse('data error')
except Exception as e:
logger.error(e)
return HttpResponse('error')
# 捕获所有异常(如 JSON 解析失败、字段缺失、数据库报错等)
logger.error(e) # 记录错误日志(包含异常堆栈信息,便于排查)
return HttpResponse('error') # 响应错误标识
@login_required
@login_required # 登录验证:仅登录用户可访问,未登录自动重定向到登录页
def show_maps(request):
"""
地图展示页面视图
功能渲染包含用户轨迹的地图页面需管理员权限
请求方式GET
请求参数?date=YYYY-MM-DD可选默认当前日期
响应
- 管理员登录返回地图 HTML 页面携带日期参数
- 非管理员登录返回 403 禁止访问
"""
# 权限二次校验仅超级管理员is_superuser=True可访问普通登录用户无权限
if request.user.is_superuser:
# 计算默认日期:当前 UTC 时间的日期格式YYYY-MM-DD
defaultdate = str(datetime.datetime.now(timezone.utc).date())
# 获取请求参数中的日期(若未传则使用默认日期)
date = request.GET.get('date', defaultdate)
# 构造模板上下文:传递日期参数给前端模板(用于筛选该日期的轨迹数据)
context = {
'date': date
}
# 渲染模板:加载 show_maps.html 模板并传入上下文,返回 HTML 响应
return render(request, 'owntracks/show_maps.html', context)
else:
# 非管理员:导入并返回 403 禁止访问响应
from django.http import HttpResponseForbidden
return HttpResponseForbidden()
@login_required
@login_required # 登录验证:仅登录用户可访问
def show_log_dates(request):
"""
轨迹日期列表页面视图
功能查询数据库中所有轨迹数据的日期去重渲染日期列表页面用于前端筛选
请求方式GET
响应返回日期列表 HTML 页面包含去重后的所有轨迹日期
"""
# 查询所有轨迹记录的创建时间(仅取 creation_time 字段flat=True 返回一维列表)
dates = OwnTrackLog.objects.values_list('creation_time', flat=True)
# 日期处理:
# 1. map 转换:将 datetime 对象格式化为 'YYYY-MM-DD' 字符串
# 2. set 去重:去除重复日期
# 3. sorted 排序:按日期升序排列
# 4. list 转换:转为列表用于模板渲染
results = list(sorted(set(map(lambda x: x.strftime('%Y-%m-%d'), dates))))
# 构造上下文:传递日期列表给模板
context = {
'results': results
}
# 渲染日期列表模板
return render(request, 'owntracks/show_log_dates.html', context)
def convert_to_amap(locations):
convert_result = []
it = iter(locations)
"""
高德地图坐标转换工具函数
功能 GPS 坐标系WGS84的经纬度转换为高德坐标系GCJ02
原因GPS 原始坐标在高德地图上会有偏移转换后可精准定位
参数locations - OwnTrackLog 模型实例列表包含 lon/lat 字段
返回值转换后的经纬度字符串格式"lon1,lat1;lon2,lat2;..."
限制高德接口单次最多支持 30 个坐标故分批次转换
"""
convert_result = [] # 存储所有批次的转换结果
it = iter(locations) # 将列表转为迭代器,便于分批次切片
# 循环分批次处理:每次取 30 个坐标(适配高德接口限制)
item = list(itertools.islice(it, 30))
while item:
# 构造坐标字符串:将每个实例的 lon/lat 拼接为 "lon,lat",再用 ";" 连接多个坐标
# set 去重:避免重复坐标提交(减少接口调用量)
datas = ';'.join(
set(map(lambda x: str(x.lon) + ',' + str(x.lat), item)))
set(map(lambda x: str(x.lon) + ',' + str(x.lat), item))
)
key = '8440a376dfc9743d8924bf0ad141f28e'
api = 'http://restapi.amap.com/v3/assistant/coordinate/convert'
# 高德地图坐标转换接口配置
key = '8440a376dfc9743d8924bf0ad141f28e' # 高德开发者密钥(需替换为有效密钥)
api = 'http://restapi.amap.com/v3/assistant/coordinate/convert' # 转换接口地址
query = {
'key': key,
'locations': datas,
'coordsys': 'gps'
'key': key, # 开发者密钥(必填)
'locations': datas, # 待转换的坐标字符串
'coordsys': 'gps' # 源坐标系gpsWGS84
}
# 调用高德接口GET 请求)
rsp = requests.get(url=api, params=query)
# 解析接口响应JSON 转字典)
result = json.loads(rsp.text)
# 若响应包含 "locations" 字段(转换成功),添加到结果列表
if "locations" in result:
convert_result.append(result['locations'])
# 处理下一批次坐标
item = list(itertools.islice(it, 30))
# 拼接所有批次结果,返回统一格式的坐标字符串
return ";".join(convert_result)
@login_required
@login_required # 登录验证:仅登录用户可访问
def get_datas(request):
"""
轨迹数据查询接口视图
功能按日期筛选轨迹数据按用户标识tid分组返回 JSON 格式的轨迹路径经纬度列表
请求方式GET
请求参数?date=YYYY-MM-DD可选默认当前日期
响应JSON 数组格式[{"name": "tid1", "path": [[lon1,lat1], [lon2,lat2], ...]}, ...]
"""
# 获取当前 UTC 时间(带时区信息,确保与数据库时间字段时区一致)
now = django.utils.timezone.now().replace(tzinfo=timezone.utc)
# 构造默认查询日期:当前日期的 00:00:00UTC 时间)
querydate = django.utils.timezone.datetime(
now.year, now.month, now.day, 0, 0, 0)
now.year, now.month, now.day, 0, 0, 0
)
# 若请求携带 date 参数,解析为指定日期的 00:00:00
if request.GET.get('date', None):
# 拆分日期字符串YYYY-MM-DD → [年, 月, 日])并转为整数
date = list(map(lambda x: int(x), request.GET.get('date').split('-')))
querydate = django.utils.timezone.datetime(
date[0], date[1], date[2], 0, 0, 0)
date[0], date[1], date[2], 0, 0, 0
)
# 构造查询结束日期:查询日期的次日 00:00:00即筛选 [querydate, nextdate) 区间的数据)
nextdate = querydate + datetime.timedelta(days=1)
# 数据库查询:筛选指定日期区间内的所有轨迹记录
models = OwnTrackLog.objects.filter(
creation_time__range=(querydate, nextdate))
result = list()
creation_time__range=(querydate, nextdate)
)
result = list() # 存储最终返回的 JSON 数据
# 若查询到数据,按 tid 分组并构造轨迹路径
if models and len(models):
# 1. sorted按 tid 排序(确保相同 tid 的记录连续,为 groupby 分组做准备)
# 2. groupby按 tid 分组key 为分组依据tid
for tid, item in groupby(
sorted(models, key=lambda k: k.tid), key=lambda k: k.tid):
# 构造单个用户的轨迹数据字典
d = dict()
d["name"] = tid
paths = list()
# 使用高德转换后的经纬度
d["name"] = tid # 用户标识(用于前端区分不同用户的轨迹)
paths = list() # 存储该用户的经纬度路径列表
# 【可选】使用高德转换后的经纬度(当前注释未启用,默认使用 GPS 原始坐标)
# locations = convert_to_amap(
# sorted(item, key=lambda x: x.creation_time))
# sorted(item, key=lambda x: x.creation_time) # 按创建时间排序,确保轨迹顺序正确
# )
# for i in locations.split(';'):
# paths.append(i.split(','))
# 使用GPS原始经纬度
# paths.append(i.split(',')) # 拆分坐标为 [lon, lat] 列表
# 使用 GPS 原始经纬度(默认启用)
# 按创建时间排序:确保轨迹点按时间顺序排列(避免路径错乱)
for location in sorted(item, key=lambda x: x.creation_time):
# 转为字符串格式(避免 JSON 序列化时的精度问题),添加到路径列表
paths.append([str(location.lon), str(location.lat)])
d["path"] = paths
result.append(d)
d["path"] = paths # 关联路径列表到用户字典
result.append(d) # 添加到最终结果列表
# 返回 JSON 响应safe=False 允许返回非字典类型(此处为列表)
return JsonResponse(result, safe=False)

Loading…
Cancel
Save