diff --git a/server/apps/exam/filters.py b/server/apps/exam/filters.py index 2e7dca0..8b14f43 100644 --- a/server/apps/exam/filters.py +++ b/server/apps/exam/filters.py @@ -16,5 +16,6 @@ class ExamFilter(filters.FilterSet): fields = { 'close_time': ['exact', 'gte', 'lte'], 'paper': ['exact'], - 'code': ['exact'] + 'code': ['exact'], + 'is_open': ['exact'] } \ No newline at end of file diff --git a/server/apps/exam/migrations/0005_exam_is_open.py b/server/apps/exam/migrations/0005_exam_is_open.py new file mode 100644 index 0000000..d637bd7 --- /dev/null +++ b/server/apps/exam/migrations/0005_exam_is_open.py @@ -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='是否公开'), + ), + ] diff --git a/server/apps/exam/models.py b/server/apps/exam/models.py index 203f6a9..3b088b0 100644 --- a/server/apps/exam/models.py +++ b/server/apps/exam/models.py @@ -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 diff --git a/server/apps/exam/serializers.py b/server/apps/exam/serializers.py index 5c4afbf..6212763 100644 --- a/server/apps/exam/serializers.py +++ b/server/apps/exam/serializers.py @@ -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 diff --git a/server/apps/vod/migrations/0007_auto_20221116_1446.py b/server/apps/vod/migrations/0007_auto_20221116_1446.py new file mode 100644 index 0000000..777c129 --- /dev/null +++ b/server/apps/vod/migrations/0007_auto_20221116_1446.py @@ -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, + }, + ), + ] diff --git a/server/apps/vod/migrations/0008_auto_20221116_1453.py b/server/apps/vod/migrations/0008_auto_20221116_1453.py new file mode 100644 index 0000000..3d52d1f --- /dev/null +++ b/server/apps/vod/migrations/0008_auto_20221116_1453.py @@ -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')}, + ), + ] diff --git a/server/apps/vod/migrations/0009_auto_20221117_0941.py b/server/apps/vod/migrations/0009_auto_20221117_0941.py new file mode 100644 index 0000000..5c726c0 --- /dev/null +++ b/server/apps/vod/migrations/0009_auto_20221117_0941.py @@ -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='视频分类'), + ), + ] diff --git a/server/apps/vod/models.py b/server/apps/vod/models.py index 694055f..76b5c24 100644 --- a/server/apps/vod/models.py +++ b/server/apps/vod/models.py @@ -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'), # 联合唯一 + ) \ No newline at end of file diff --git a/server/apps/vod/serializers.py b/server/apps/vod/serializers.py index c9e6703..6c03717 100644 --- a/server/apps/vod/serializers.py +++ b/server/apps/vod/serializers.py @@ -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'] \ No newline at end of file + 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="展示数量") \ No newline at end of file diff --git a/server/apps/vod/urls.py b/server/apps/vod/urls.py index 77a38a5..59eb67e 100644 --- a/server/apps/vod/urls.py +++ b/server/apps/vod/urls.py @@ -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//myview/', MyViewRecordAPIView.as_view()), diff --git a/server/apps/vod/views.py b/server/apps/vod/views.py index 402529c..884a9e0 100644 --- a/server/apps/vod/views.py +++ b/server/apps/vod/views.py @@ -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} """ diff --git a/server/apps/vod/views2.py b/server/apps/vod/views2.py new file mode 100644 index 0000000..208b268 --- /dev/null +++ b/server/apps/vod/views2.py @@ -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']])) \ No newline at end of file diff --git a/server/utils/model.py b/server/utils/model.py index c6ba63b..ff59a73 100644 --- a/server/utils/model.py +++ b/server/utils/model.py @@ -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) diff --git a/server/utils/sql.py b/server/utils/sql.py new file mode 100644 index 0000000..af94efd --- /dev/null +++ b/server/utils/sql.py @@ -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)) \ No newline at end of file