This commit is contained in:
shijing 2022-11-25 14:26:18 +08:00
commit 3509d73487
14 changed files with 530 additions and 19 deletions

View File

@ -16,5 +16,6 @@ class ExamFilter(filters.FilterSet):
fields = {
'close_time': ['exact', 'gte', 'lte'],
'paper': ['exact'],
'code': ['exact']
'code': ['exact'],
'is_open': ['exact']
}

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.5 on 2022-11-22 02:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('exam', '0004_auto_20221114_1108'),
]
operations = [
migrations.AddField(
model_name='exam',
name='is_open',
field=models.BooleanField(default=True, verbose_name='是否公开'),
),
]

View File

@ -87,6 +87,7 @@ class Exam(CommonAModel):
proctor_phone = models.CharField('监考人联系方式', max_length=100, null=True, blank=True)
chance = models.IntegerField('考试机会', default=3)
paper = models.ForeignKey(Paper, verbose_name='使用的试卷', on_delete=models.CASCADE, null=True, blank=True)
is_open = models.BooleanField('是否公开', default=True)
def __str__(self):
return self.name

View File

@ -136,7 +136,7 @@ class ExamRecordDetailSerializer(serializers.ModelSerializer):
def get_questions_(self, obj):
objs = AnswerDetail.objects.select_related('question').filter(
examrecord=obj).order_by('question__type')
examrecord=obj).order_by('id')
return AnswerDetailSerializer(instance=objs, many=True).data

View File

@ -0,0 +1,86 @@
# Generated by Django 3.0.5 on 2022-11-16 06:46
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('vod', '0006_viewrecord_total_seconds'),
]
operations = [
migrations.AlterField(
model_name='video',
name='views',
field=models.IntegerField(default=0, verbose_name='总观看次数'),
),
migrations.AlterField(
model_name='video',
name='viewsp',
field=models.IntegerField(default=0, verbose_name='总观看人数'),
),
migrations.AlterField(
model_name='viewrecord',
name='video',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewrecord_video', to='vod.Video', verbose_name='点播视频'),
),
migrations.AlterField(
model_name='viewrecord',
name='views',
field=models.IntegerField(default=0, verbose_name='总观看次数'),
),
migrations.CreateModel(
name='ViewItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('current', models.IntegerField(default=0, verbose_name='当前观看进度(秒)')),
('total_seconds', models.PositiveIntegerField(default=0, verbose_name='本次总观看秒数')),
('create_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewitem_user', to=settings.AUTH_USER_MODEL, verbose_name='观看人')),
('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewitem_video', to='vod.Video', verbose_name='点播视频')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='View2',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('current', models.IntegerField(default=0, verbose_name='当前观看进度(秒)')),
('views', models.IntegerField(default=0, verbose_name='总观看次数')),
('total_seconds', models.PositiveIntegerField(default=0, verbose_name='总观看秒数')),
('is_completed', models.BooleanField(default=False)),
('create_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='view2_user', to=settings.AUTH_USER_MODEL, verbose_name='观看人')),
('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='view2_video', to='vod.Video', verbose_name='点播视频')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='View1',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('views', models.IntegerField(default=0, verbose_name='总观看次数')),
('viewsp', models.IntegerField(default=0, verbose_name='总观看人数')),
('video', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='view1_video', to='vod.Video')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.0.5 on 2022-11-16 06:53
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('vod', '0007_auto_20221116_1446'),
]
operations = [
migrations.AlterUniqueTogether(
name='view2',
unique_together={('create_by', 'video')},
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.0.5 on 2022-11-17 01:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0022_delete_historicaldict'),
('vod', '0008_auto_20221116_1453'),
]
operations = [
migrations.AddField(
model_name='video',
name='category_big',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='video_catebig', to='system.Dict', verbose_name='视频大类'),
),
migrations.AlterField(
model_name='video',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='video_cate', to='system.Dict', verbose_name='视频分类'),
),
]

View File

@ -7,14 +7,15 @@ from apps.system.models import User, CommonAModel, Dict
class Video(CommonAModel):
name = models.CharField(verbose_name='视频名称', max_length=100)
category = models.ForeignKey(Dict, verbose_name='视频分类', on_delete=models.DO_NOTHING)
category_big = models.ForeignKey(Dict, verbose_name='视频大类', on_delete=models.SET_NULL, null=True, blank=True, related_name='video_catebig')
category = models.ForeignKey(Dict, verbose_name='视频分类', on_delete=models.SET_NULL, null=True, blank=True, related_name='video_cate')
description = models.TextField(verbose_name='视频描述', default='')
fileid = models.CharField(verbose_name='云点播视频id', unique=True, max_length=200)
mediaurl = models.CharField(verbose_name='视频地址', max_length=200)
coverurl = models.CharField(verbose_name='封面地址', max_length=200)
duration = models.IntegerField(verbose_name='时长(秒)', default=0)
views = models.IntegerField(verbose_name='观看次数', default=0)
viewsp = models.IntegerField(verbose_name='观看人数', default=0)
views = models.IntegerField(verbose_name='观看次数', default=0)
viewsp = models.IntegerField(verbose_name='观看人数', default=0)
sort_str = models.CharField('排序字符', max_length=10, null=True, blank=True)
class Meta:
@ -25,16 +26,51 @@ class Video(CommonAModel):
class ViewRecord(BaseModel):
# 观看记录
"""
某视频-某人的观看记录统计
"""
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='观看人', related_name='viewrecord_user')
views = models.IntegerField(verbose_name='观看次数', default=0)
current = models.IntegerField(verbose_name='当前观看进度(秒)', default=0)
video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='record_video')
video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='viewrecord_video')
views = models.IntegerField(verbose_name='总观看次数', default=0)
total_seconds = models.PositiveIntegerField(verbose_name='总观看秒数', default=0)
class Meta:
verbose_name = '点播观看记录'
verbose_name_plural = verbose_name
class ViewItem(BaseModel):
"""
单次观看记录
"""
create_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='观看人', related_name='viewitem_user')
video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='viewitem_video')
current = models.IntegerField(verbose_name='当前观看进度(秒)', default=0)
total_seconds = models.PositiveIntegerField(verbose_name='本次总观看秒数', default=0)
class View1(BaseModel):
"""
视频播放统计
"""
video = models.OneToOneField(Video, on_delete=models.CASCADE, related_name='view1_video')
views = models.IntegerField(verbose_name='总观看次数', default=0)
viewsp = models.IntegerField(verbose_name='总观看人数', default=0)
class View2(BaseModel):
"""
某视频-某人的观看记录统计
"""
create_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='观看人', related_name='view2_user')
current = models.IntegerField(verbose_name='当前观看进度(秒)', default=0)
video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='view2_video')
views = models.IntegerField(verbose_name='总观看次数', default=0)
total_seconds = models.PositiveIntegerField(verbose_name='总观看秒数', default=0)
is_completed = models.BooleanField(default=False)
class Meta:
unique_together = (
('create_by','video'), # 联合唯一
)

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Video, ViewRecord
from .models import Video, ViewRecord, ViewItem, View1, View2
from apps.system.serializers import UserSimpleSerializer
class VideoSerializer(serializers.ModelSerializer):
@ -7,13 +7,34 @@ class VideoSerializer(serializers.ModelSerializer):
class Meta:
model = Video
fields = '__all__'
def create(self, validated_data):
video = super().create(validated_data)
cate = video.category
video.category_big = cate
if cate.pid:
video.category_big = cate.pid
video.save()
View1.objects.get_or_create(video=video, defaults={'video': video})
return video
class VideoUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Video
fields = ['name', 'category', 'description', 'sort_str']
def update(self, instance, validated_data):
video = super().update(instance, validated_data)
cate = video.category
video.category_big = cate
if cate.pid:
video.category_big = cate.pid
video.save()
return video
class VideoListDetailSerializer(serializers.ModelSerializer):
views_n = serializers.IntegerField(source='view1_video.views', read_only=True, label="总观看次数")
viewsp_n = serializers.IntegerField(source='view1_video.viewsp', read_only=True, label="总观看秒数")
class Meta:
model = Video
exclude = ['mediaurl']
@ -38,4 +59,29 @@ class VRecordSerializer(serializers.ModelSerializer):
class VRecordUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = ViewRecord
fields=['num', 'current']
fields=['num', 'current']
class ViewItemSerializer(serializers.ModelSerializer):
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
video_ = VideoSimpleSerializer(source='video', read_only=True)
class Meta:
model = ViewItem
fields = '__all__'
class ViewItemUpdateSerializer(serializers.Serializer):
current = serializers.IntegerField(min_value=1)
seconds = serializers.IntegerField(min_value=0)
class View2Serializer(serializers.Serializer):
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
video_ = VideoSimpleSerializer(source='video', read_only=True)
class Meta:
model = View2
fields = '__all__'
class DatetimeSerializer(serializers.Serializer):
start_time = serializers.DateTimeField(label="开始时间")
end_time = serializers.DateTimeField(label="结束时间")
limit = serializers.IntegerField(min_value=10, label="展示数量")

View File

@ -1,11 +1,15 @@
from django.db.models import base
from django.urls import path, include
from .views import ClassView, PlayCodeAPIView, SignatureAPIView, VideoView, VideoViewSet, VRecordViewSet, MyViewRecordAPIView
from .views import ClassView, PlayCodeAPIView, SignatureAPIView, ViewItemViewSet, VideoViewSet, VRecordViewSet, MyViewRecordAPIView, View2ViewSet
from .views2 import AnalyseViewSet
from rest_framework import routers
router = routers.DefaultRouter()
router.register('video', VideoViewSet, basename="video")
router.register('viewrecord', VRecordViewSet, basename='viewrecord')
router.register('viewitem', ViewItemViewSet, basename='viewitem')
router.register('view2', View2ViewSet, basename='view2')
router.register('analyse', AnalyseViewSet, basename='analyse')
urlpatterns = [
path('', include(router.urls)),
path('video/<int:id>/myview/', MyViewRecordAPIView.as_view()),

View File

@ -1,9 +1,9 @@
from datetime import timedelta
from time import timezone
from apps.system.models import Dict
from rest_framework.mixins import ListModelMixin
from apps.vod.serializers import VRecordSerializer, VRecordUpdateSerializer, VideoListDetailSerializer, VideoSerializer, VideoUpdateSerializer
from apps.vod.models import Video, ViewRecord
from rest_framework.mixins import ListModelMixin, UpdateModelMixin
from apps.vod.serializers import VRecordSerializer, VRecordUpdateSerializer, VideoListDetailSerializer, VideoSerializer, VideoUpdateSerializer, ViewItemSerializer, ViewItemUpdateSerializer, View2Serializer, DatetimeSerializer
from apps.vod.models import Video, ViewRecord, ViewItem, View1, View2
from django.shortcuts import render
from .vodclient import getAllClass, getPlayCode, searchMedia, getSignature
from rest_framework.views import APIView
@ -17,6 +17,8 @@ from rest_framework.status import HTTP_400_BAD_REQUEST
from django.utils import timezone
from rest_framework.exceptions import ParseError
from utils.queryset import get_child_queryset2
from django.db.models import Sum
from django.db import connection
# Create your views here.
class ClassView(APIView):
@ -69,6 +71,8 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
@action(methods=['get'], detail=False, perms_map={'get':'video_view'})
def myview(self, request, *args, **kwargs):
"""
废弃接口
个人观看记录
"""
queryset = ViewRecord.objects.filter(user=request.user).order_by('-id')
@ -78,10 +82,72 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
return self.get_paginated_response(serializer.data)
serializer = VRecordSerializer(queryset, many=True)
return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def my(self, request, *args, **kwargs):
"""
本视频的我的观看统计
本视频的我的观看统计
"""
video = self.get_object()
user = request.user
ins, _ = View2.objects.get_or_create(create_by=user, video=video, defaults={'video': video, 'create_by': user})
return Response(View2Serializer(instance=ins).data)
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def start(self, request, *args, **kwargs):
"""
开始播放
本次播放开始返回记录ID,后续计时使用
"""
video = self.get_object()
user = request.user
vi = ViewItem.objects.create(create_by=user, video=video)
cal_view1(vi)
return Response({"vi": vi.id})
# @action(methods=['get'], detail=False, perms_map={'get':'*'})
# def correct_cate(self, request, *args, **kwargs):
# for video in Video.objects.get_queryset(all=True).all():
# cate = video.category
# video.category_big = cate
# if cate.pid:
# video.category_big = cate.pid
# video.save()
# return Response()
def cal_view1(vi: ViewItem):
"""
统计视频播放数量
统计视频播放数量
"""
v1, _ = View1.objects.get_or_create(video=vi.video, defaults={"video": vi.video})
v2, _ = View2.objects.get_or_create(video=vi.video, create_by=vi.create_by, defaults={"video": vi.video, "create_by": vi.create_by})
v1.views = ViewItem.objects.filter(video=vi.video).count()
v1.viewp = View2.objects.filter(video=vi.video).count()
v1.save()
def cal_view2(vi: ViewItem):
"""
统计个人播放记录
统计个人播放记录
"""
v2, _ = View2.objects.get_or_create(video=vi.video, create_by=vi.create_by, defaults={"video": vi.video, "create_by": vi.create_by})
v2.views = ViewItem.objects.filter(video=vi.video).count()
v2.total_seconds = ViewItem.objects.filter(video=vi.video).aggregate(total=Sum('total_seconds'))['total']
v2.save()
class VRecordViewSet(ListModelMixin, GenericViewSet):
"""
废弃接口
废弃接口
"""
perms_map = {'get':'viewrecord_view'}
queryset = ViewRecord.objects.all()
search_fields = ['user__name', 'video__name']
@ -89,12 +155,93 @@ class VRecordViewSet(ListModelMixin, GenericViewSet):
ordering = ['-update_time']
class ViewItemViewSet(ListModelMixin, UpdateModelMixin, GenericViewSet):
perms_map = {'get': 'viewitem_view', 'put': '*'}
queryset = ViewItem.objects.select_related('create_by', 'video').all()
search_fields = ['create_by__name', 'video__name']
serializer_class = ViewItemSerializer
ordering = ['-id']
def get_serializer_class(self):
if self.action == "update":
return ViewItemUpdateSerializer
return super().get_serializer_class()
@action(methods=['get'], detail=False, perms_map={'get':'*'})
def my(self, request, *args, **kwargs):
"""
我的观看记录
我的观看记录
"""
queryset = ViewItem.objects.filter(create_by=request.user).order_by('-id')
page = self.paginate_queryset(queryset)
if page is not None:
serializer = ViewItemSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = ViewItemSerializer(queryset, many=True)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
user = request.user
data = request.data
obj = self.get_object()
if obj.create_by != user:
raise ParseError('非创建人')
if ViewItem.objects.filter(video=obj.video, create_by=user).order_by('-id').first() != obj:
raise ParseError('存在新播放记录')
obj.current = data['current']
obj.total_seconds = obj.total_seconds + data['seconds']
obj.save()
cal_view2(obj)
return Response({'id': obj.id, 'current': obj.current, 'total_seconds': obj.total_seconds})
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def complete(self, request, *args, **kwargs):
"""
完成播放
完成播放
"""
obj = self.get_object()
obj.current = 0
v2 = View2.objects.get(video=obj.video, create_by=obj.create_by)
if v2.total_seconds >= obj.video.duration * 0.6:
v2.is_completed = True
v2.save()
return Response()
raise ParseError('观看时长不足')
class View2ViewSet(ListModelMixin, GenericViewSet):
perms_map = {'get': 'view2_view', 'put': '*'}
queryset = View2.objects.select_related('create_by', 'video').all()
search_fields = ['create_by__name', 'video__name']
serializer_class = View2Serializer
ordering = ['-id']
@action(methods=['get'], detail=False, perms_map={'get':'*'})
def my(self, request, *args, **kwargs):
"""
我的观看统计
我的观看统计
"""
queryset = View2.objects.filter(create_by=request.user).order_by('-id')
page = self.paginate_queryset(queryset)
if page is not None:
serializer = View2Serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = View2Serializer(queryset, many=True)
return Response(serializer.data)
class MyViewRecordAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, id , format=None):
"""
废弃接口
该视频的本人观看信息
"""
try:
@ -111,6 +258,8 @@ class MyViewRecordAPIView(APIView):
def put(self, request, id, format=None):
"""
废弃接口
更新该视频本人的观看信息
params: {current:int}
"""

93
server/apps/vod/views2.py Normal file
View File

@ -0,0 +1,93 @@
from rest_framework.viewsets import GenericViewSet
from apps.vod.serializers import DatetimeSerializer
from rest_framework.decorators import action
from rest_framework.response import Response
from utils.sql import query_all_dict
class AnalyseViewSet(GenericViewSet):
perms_map = {'post': '*'}
serializer_class = DatetimeSerializer
def is_valid(self, request):
data = request.data
sr = self.get_serializer(data=data)
sr.is_valid(raise_exception=True)
vdata = sr.validated_data
return vdata
@action(methods=['post'], detail=False)
def group_by_video_category_big(self, request):
"""
视频大类播放量统计
视频大类播放量统计
"""
vdata = self.is_valid(request)
sql_str = """select
d.name as "视频大类",
count(v.id) as "视频数量",
count(vi.id) as "观看总次数",
count(distinct vi.create_by_id) as "观看总人数"
from vod_video v
left join system_dict d on d.id = v.category_big_id
left join vod_viewitem vi on vi.video_id = v.id
and vi.create_time >= %s
and vi.create_time <= %s
group by d.id
order by "视频数量" desc, d.sort
limit %s
"""
return Response(query_all_dict(sql_str, [vdata['start_time'], vdata['end_time'], vdata['limit']]))
@action(methods=['post'], detail=False)
def group_by_user_view(self, request):
"""
个人观看量统计
个人观看量统计
"""
vdata = self.is_valid(request)
sql_str = """select
u.name as "姓名",
u.username as "账号",
count(v2.is_completed is true) as "观看完成视频总数",
count(distinct vi.video_id) as "观看视频总数",
count(vi.id) as "观看总次数" ,
sum(vi.total_seconds/60) as "观看总时间"
from vod_viewitem vi
left join vod_video v on v.id = vi.video_id
left join system_user u on u.id = vi.create_by_id
left join vod_view2 v2 on v2.create_by_id = vi.create_by_id and v2.video_id = vi.video_id
where vi.create_time >= %s and vi.create_time <= %s
group by u.id
order by "观看完成视频总数" desc, "账号"
limit %s
"""
return Response(query_all_dict(sql_str, [vdata['start_time'], vdata['end_time'], vdata['limit']]))
@action(methods=['post'], detail=False)
def group_by_org_view(self, request):
"""
单位观看量统计
单位观看量统计
"""
vdata = self.is_valid(request)
sql_str = """select
o.name as "单位名称",
count(v2.is_completed is true) as "观看完成视频总数",
count(distinct vi.video_id) as "观看视频总数",
count(vi.id) as "观看总次数" ,
sum(vi.total_seconds/60) as "观看总时间"
from vod_viewitem vi
left join vod_video v on v.id = vi.video_id
left join system_user u on u.id = vi.create_by_id
left join system_organization o on o.id = u.dept_id
left join vod_view2 v2 on v2.create_by_id = vi.create_by_id and v2.video_id = vi.video_id
where vi.create_time >= %s and vi.create_time <= %s
group by o.id
order by "观看完成视频总数" desc, o.sort
limit %s
"""
return Response(query_all_dict(sql_str, [vdata['start_time'], vdata['end_time'], vdata['limit']]))

View File

@ -33,14 +33,15 @@ class SoftDeletableManagerMixin(object):
'''
_queryset_class = SoftDeletableQuerySet
def get_queryset(self):
def get_queryset(self, all=False):
'''
Return queryset limited to not deleted entries.
'''
kwargs = {'model': self.model, 'using': self._db}
if hasattr(self, '_hints'):
kwargs['hints'] = self._hints
if all:
return self._queryset_class(**kwargs)
return self._queryset_class(**kwargs).filter(is_deleted=False)

32
server/utils/sql.py Normal file
View File

@ -0,0 +1,32 @@
from django.db import connection
def query_all_dict(sql, params=None):
'''
查询所有结果返回字典类型数据
:param sql:
:param params:
:return:
'''
with connection.cursor() as cursor:
if params:
cursor.execute(sql, params=params)
else:
cursor.execute(sql)
columns = [desc[0] for desc in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
def query_one_dict(sql, params=None):
"""
查询一个结果返回字典类型数据
:param sql:
:param params:
:return:
"""
with connection.cursor() as cursor:
if params:
cursor.execute(sql, params=params)
else:
cursor.execute(sql)
columns = [desc[0] for desc in cursor.description]
row = cursor.fetchone()
return dict(zip(columns, row))