From e12a815cddc4cf1bd4cebf19ddbcab14ee2f6dbf Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 15 Nov 2022 15:32:10 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E7=AD=94=E5=8D=B7=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E6=9C=89=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/apps/exam/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 666ed5968af89a74a2e5207685be5f189866e5f7 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 16 Nov 2022 16:45:54 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vod/migrations/0007_auto_20221116_1446.py | 86 +++++++++++ .../vod/migrations/0008_auto_20221116_1453.py | 19 +++ server/apps/vod/models.py | 51 +++++- server/apps/vod/serializers.py | 30 +++- server/apps/vod/urls.py | 4 +- server/apps/vod/views.py | 145 +++++++++++++++++- 6 files changed, 321 insertions(+), 14 deletions(-) create mode 100644 server/apps/vod/migrations/0007_auto_20221116_1446.py create mode 100644 server/apps/vod/migrations/0008_auto_20221116_1453.py 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/models.py b/server/apps/vod/models.py index 694055f..97c3afb 100644 --- a/server/apps/vod/models.py +++ b/server/apps/vod/models.py @@ -13,8 +13,8 @@ class Video(CommonAModel): 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 +25,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..9b4f3d2 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,6 +7,11 @@ class VideoSerializer(serializers.ModelSerializer): class Meta: model = Video fields = '__all__' + + def create(self, validated_data): + video = super().create(validated_data) + View1.objects.get_or_create(video=video, defaults={'video': video}) + return video class VideoUpdateSerializer(serializers.ModelSerializer): class Meta: @@ -14,6 +19,8 @@ class VideoUpdateSerializer(serializers.ModelSerializer): fields = ['name', 'category', 'description', 'sort_str'] 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 +45,23 @@ 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) + + +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__' \ No newline at end of file diff --git a/server/apps/vod/urls.py b/server/apps/vod/urls.py index 77a38a5..39769e2 100644 --- a/server/apps/vod/urls.py +++ b/server/apps/vod/urls.py @@ -1,11 +1,13 @@ 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 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') 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..2484fe6 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 +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,7 @@ 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 # Create your views here. class ClassView(APIView): @@ -69,6 +70,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 +81,63 @@ 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}) + + +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 +145,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 + 10 + 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 = ViewItemSerializer + 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 +248,8 @@ class MyViewRecordAPIView(APIView): def put(self, request, id, format=None): """ + 废弃接口 + 更新该视频本人的观看信息 params: {current:int} """ From f230990f2a99f2ab2bec0e30fc2ff69897049555 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 16 Nov 2022 17:11:42 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/apps/vod/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/apps/vod/views.py b/server/apps/vod/views.py index 2484fe6..399784b 100644 --- a/server/apps/vod/views.py +++ b/server/apps/vod/views.py @@ -85,9 +85,9 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet): @action(methods=['get'], detail=True, perms_map={'get':'*'}) def my(self, request, *args, **kwargs): """ - 本视频的个人观看统计 + 本视频的我的观看统计 - 本视频的个人观看统计 + 本视频的我的观看统计 """ video = self.get_object() user = request.user @@ -160,9 +160,9 @@ class ViewItemViewSet(ListModelMixin, UpdateModelMixin, GenericViewSet): @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) @@ -206,15 +206,15 @@ 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 = ViewItemSerializer + 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) From 870587e878ac6f191eaf03e220ce8d71f3924c4e Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 17 Nov 2022 09:04:43 +0800 Subject: [PATCH 4/8] ViewItem update --- server/apps/vod/serializers.py | 1 + server/apps/vod/views.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server/apps/vod/serializers.py b/server/apps/vod/serializers.py index 9b4f3d2..ed51292 100644 --- a/server/apps/vod/serializers.py +++ b/server/apps/vod/serializers.py @@ -57,6 +57,7 @@ class ViewItemSerializer(serializers.ModelSerializer): class ViewItemUpdateSerializer(serializers.Serializer): current = serializers.IntegerField(min_value=1) + seconds = serializers.IntegerField(min_value=0) class View2Serializer(serializers.Serializer): diff --git a/server/apps/vod/views.py b/server/apps/vod/views.py index 399784b..ddd7240 100644 --- a/server/apps/vod/views.py +++ b/server/apps/vod/views.py @@ -181,7 +181,7 @@ class ViewItemViewSet(ListModelMixin, UpdateModelMixin, GenericViewSet): 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 + 10 + 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}) From 6d3e304359c5e26d6bd58787ea1030e284b4609e Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 17 Nov 2022 09:50:51 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E5=A2=9E=E5=8A=A0category=20big?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vod/migrations/0009_auto_20221117_0941.py | 25 +++++++++++++++++++ server/apps/vod/models.py | 3 ++- server/apps/vod/serializers.py | 14 +++++++++++ server/apps/vod/views.py | 9 +++++++ server/utils/model.py | 5 ++-- 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 server/apps/vod/migrations/0009_auto_20221117_0941.py 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 97c3afb..76b5c24 100644 --- a/server/apps/vod/models.py +++ b/server/apps/vod/models.py @@ -7,7 +7,8 @@ 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) diff --git a/server/apps/vod/serializers.py b/server/apps/vod/serializers.py index ed51292..6cb4156 100644 --- a/server/apps/vod/serializers.py +++ b/server/apps/vod/serializers.py @@ -10,6 +10,11 @@ class VideoSerializer(serializers.ModelSerializer): 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 @@ -17,6 +22,15 @@ 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="总观看次数") diff --git a/server/apps/vod/views.py b/server/apps/vod/views.py index ddd7240..c752a52 100644 --- a/server/apps/vod/views.py +++ b/server/apps/vod/views.py @@ -107,6 +107,15 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet): 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): """ 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) From 9c7734820a90dfd4b05fabf0832983a832f41849 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 17 Nov 2022 18:17:53 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E7=BB=9F=E8=AE=A1sql=20=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/apps/vod/serializers.py | 7 ++- server/apps/vod/urls.py | 2 + server/apps/vod/views.py | 21 ++++---- server/apps/vod/views2.py | 93 ++++++++++++++++++++++++++++++++++ server/utils/sql.py | 32 ++++++++++++ 5 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 server/apps/vod/views2.py create mode 100644 server/utils/sql.py diff --git a/server/apps/vod/serializers.py b/server/apps/vod/serializers.py index 6cb4156..6c03717 100644 --- a/server/apps/vod/serializers.py +++ b/server/apps/vod/serializers.py @@ -79,4 +79,9 @@ class View2Serializer(serializers.Serializer): video_ = VideoSimpleSerializer(source='video', read_only=True) class Meta: model = View2 - fields = '__all__' \ No newline at end of file + 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 39769e2..59eb67e 100644 --- a/server/apps/vod/urls.py +++ b/server/apps/vod/urls.py @@ -1,6 +1,7 @@ from django.db.models import base from django.urls import path, include from .views import ClassView, PlayCodeAPIView, SignatureAPIView, ViewItemViewSet, VideoViewSet, VRecordViewSet, MyViewRecordAPIView, View2ViewSet +from .views2 import AnalyseViewSet from rest_framework import routers router = routers.DefaultRouter() @@ -8,6 +9,7 @@ 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 c752a52..6ea812c 100644 --- a/server/apps/vod/views.py +++ b/server/apps/vod/views.py @@ -2,7 +2,7 @@ from datetime import timedelta from time import timezone from apps.system.models import Dict from rest_framework.mixins import ListModelMixin, UpdateModelMixin -from apps.vod.serializers import VRecordSerializer, VRecordUpdateSerializer, VideoListDetailSerializer, VideoSerializer, VideoUpdateSerializer, ViewItemSerializer, ViewItemUpdateSerializer, View2Serializer +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 @@ -18,6 +18,7 @@ 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): @@ -107,15 +108,15 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet): 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() + # @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): """ 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/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 From ff32c4e7638b5274abc967325f16fc30710b2e03 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 18 Nov 2022 13:29:32 +0800 Subject: [PATCH 7/8] =?UTF-8?q?bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/apps/vod/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/apps/vod/views.py b/server/apps/vod/views.py index 6ea812c..884a9e0 100644 --- a/server/apps/vod/views.py +++ b/server/apps/vod/views.py @@ -92,7 +92,7 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet): """ video = self.get_object() user = request.user - ins = View2.objects.get_or_create(create_by=user, video=video, defaults={'video': video, 'create_by': 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':'*'}) @@ -124,8 +124,8 @@ 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, _ = 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() @@ -136,7 +136,7 @@ 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, _ = 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() From 4895d0a3343ab77268b6325b7052e7e33f595e9c Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 22 Nov 2022 10:15:07 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E8=80=83=E8=AF=95=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=85=AC=E5=BC=80=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/apps/exam/filters.py | 3 ++- .../apps/exam/migrations/0005_exam_is_open.py | 18 ++++++++++++++++++ server/apps/exam/models.py | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 server/apps/exam/migrations/0005_exam_is_open.py 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