统计观看时长/登录日志

This commit is contained in:
caoqianming 2022-10-12 16:38:30 +08:00
parent 7b956f5ef5
commit d819dccc08
36 changed files with 663 additions and 26 deletions

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
server/apps/exam/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ExamConfig(AppConfig):
name = 'exam'

View File

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

6
server/apps/exam/urls.py Normal file
View File

@ -0,0 +1,6 @@
from django.urls import path
API_BASE_URL = 'api/exam/'
HTML_BASE_URL = 'exam/'
urlpatterns = [
]

View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

3
server/apps/ops/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
server/apps/ops/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class OpsConfig(AppConfig):
name = 'ops'
verbose_name = '系统监控'

View File

@ -0,0 +1 @@
LOG_NOT_FONED = {"code": "log_not_found", "detail": "日志不存在"}

View File

@ -0,0 +1,7 @@
from django_filters import rest_framework as filters
class DrfLogFilterSet(filters.FilterSet):
start_request = filters.DateTimeFilter(field_name="requested_at", lookup_expr='gte')
end_request = filters.DateTimeFilter(field_name="requested_at", lookup_expr='lte')
id = filters.CharFilter()

View File

@ -0,0 +1,42 @@
# Generated by Django 3.0.5 on 2022-10-12 06:04
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='DrfRequestLog',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('requested_at', models.DateTimeField(db_index=True)),
('response_ms', models.PositiveIntegerField(default=0)),
('path', models.CharField(db_index=True, help_text='请求地址', max_length=400)),
('view', models.CharField(blank=True, db_index=True, help_text='执行视图', max_length=400, null=True)),
('view_method', models.CharField(blank=True, db_index=True, max_length=20, null=True)),
('remote_addr', models.GenericIPAddressField()),
('host', models.URLField()),
('method', models.CharField(max_length=10)),
('query_params', models.TextField(blank=True, null=True)),
('data', models.TextField(blank=True, null=True)),
('response', models.TextField(blank=True, null=True)),
('errors', models.TextField(blank=True, null=True)),
('agent', models.TextField(blank=True, null=True)),
('status_code', models.PositiveIntegerField(blank=True, db_index=True, null=True)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'DRF请求日志',
},
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.0.5 on 2022-10-12 06:55
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ops', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='drfrequestlog',
name='data',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='drfrequestlog',
name='query_params',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='drfrequestlog',
name='response',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
),
]

View File

235
server/apps/ops/mixins.py Normal file
View File

@ -0,0 +1,235 @@
import ast
import json
import logging
import uuid
from django.utils.timezone import now
import traceback
from django.db import connection
from apps.ops.models import DrfRequestLog
import ipaddress
from user_agents import parse
# 实例化myLogger
myLogger = logging.getLogger('log')
class MyLoggingMixin(object):
"""Mixin to log requests"""
CLEANED_SUBSTITUTE = "********************"
# logging_methods = "__all__"
logging_methods = '__all__'
sensitive_fields = {}
def __init__(self, *args, **kwargs):
assert isinstance(
self.CLEANED_SUBSTITUTE, str
), "CLEANED_SUBSTITUTE must be a string."
super().__init__(*args, **kwargs)
def initial(self, request, *args, **kwargs):
request_id = uuid.uuid4()
self.log = {"requested_at": now(), "id": request_id}
setattr(request, 'request_id', request_id)
if not getattr(self, "decode_request_body", False):
self.log["data"] = ""
else:
self.log["data"] = self._clean_data(request.body)
super().initial(request, *args, **kwargs)
try:
# Accessing request.data *for the first time* parses the request body, which may raise
# ParseError and UnsupportedMediaType exceptions. It's important not to swallow these,
# as (depending on implementation details) they may only get raised this once, and
# DRF logic needs them to be raised by the view for error handling to work correctly.
data = self.request.data.dict()
except AttributeError:
data = self.request.data
self.log["data"] = self._clean_data(data)
def handle_exception(self, exc):
response = super().handle_exception(exc)
self.log["errors"] = traceback.format_exc()
return response
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(
request, response, *args, **kwargs
)
# Ensure backward compatibility for those using _should_log hook
should_log = (
self._should_log if hasattr(self, "_should_log") else self.should_log
)
if should_log(request, response):
if (connection.settings_dict.get("ATOMIC_REQUESTS") and
getattr(response, "exception", None) and connection.in_atomic_block):
# response with exception (HTTP status like: 401, 404, etc)
# pointwise disable atomic block for handle log (TransactionManagementError)
connection.set_rollback(True)
connection.set_rollback(False)
if response.streaming:
rendered_content = None
elif hasattr(response, "rendered_content"):
rendered_content = response.rendered_content
else:
rendered_content = response.getvalue()
self.log.update(
{
"remote_addr": self._get_ip_address(request),
"view": self._get_view_name(request),
"view_method": self._get_view_method(request),
"path": self._get_path(request),
"host": request.get_host(),
"method": request.method,
"query_params": self._clean_data(request.query_params.dict()),
"user": self._get_user(request),
"response_ms": self._get_response_ms(),
"response": self._clean_data(rendered_content),
"status_code": response.status_code,
"agent": self._get_agent(request),
}
)
try:
self.handle_log()
except Exception:
# ensure that all exceptions raised by handle_log
# doesn't prevent API call to continue as expected
myLogger.exception("Logging API call raise exception!")
return response
def handle_log(self):
"""
Hook to define what happens with the log.
Defaults on saving the data on the db.
"""
DrfRequestLog(**self.log).save()
def _get_path(self, request):
"""Get the request path and truncate it"""
return request.path
def _get_ip_address(self, request):
"""Get the remote ip address the request was generated from."""
ipaddr = request.META.get("HTTP_X_FORWARDED_FOR", None)
if ipaddr:
ipaddr = ipaddr.split(",")[0]
else:
ipaddr = request.META.get("REMOTE_ADDR", "")
# Account for IPv4 and IPv6 addresses, each possibly with port appended. Possibilities are:
# <ipv4 address>
# <ipv6 address>
# <ipv4 address>:port
# [<ipv6 address>]:port
# Note that ipv6 addresses are colon separated hex numbers
possibles = (ipaddr.lstrip("[").split("]")[0], ipaddr.split(":")[0])
for addr in possibles:
try:
return str(ipaddress.ip_address(addr))
except ValueError:
pass
return ipaddr
def _get_view_name(self, request):
"""Get view name."""
method = request.method.lower()
try:
attributes = getattr(self, method)
return (
type(attributes.__self__).__module__ + "." + type(attributes.__self__).__name__
)
except AttributeError:
return None
def _get_view_method(self, request):
"""Get view method."""
if hasattr(self, "action"):
return self.action or None
return request.method.lower()
def _get_user(self, request):
"""Get user."""
user = request.user
if user.is_anonymous:
return None
return user
def _get_agent(self, request):
"""Get os string"""
return str(parse(request.META['HTTP_USER_AGENT']))
def _get_response_ms(self):
"""
Get the duration of the request response cycle is milliseconds.
In case of negative duration 0 is returned.
"""
response_timedelta = now() - self.log["requested_at"]
response_ms = int(response_timedelta.total_seconds() * 1000)
return max(response_ms, 0)
def should_log(self, request, response):
"""
Method that should return a value that evaluated to True if the request should be logged.
By default, check if the request method is in logging_methods.
"""
return self.logging_methods == "__all__" or response.status_code > 404 or response.status_code == 400 \
or (request.method in self.logging_methods and response.status_code not in [401, 403, 404])
def _clean_data(self, data):
"""
Clean a dictionary of data of potentially sensitive info before
sending to the database.
Function based on the "_clean_credentials" function of django
(https://github.com/django/django/blob/stable/1.11.x/django/contrib/auth/__init__.py#L50)
Fields defined by django are by default cleaned with this function
You can define your own sensitive fields in your view by defining a set
eg: sensitive_fields = {'field1', 'field2'}
"""
if isinstance(data, bytes):
data = data.decode(errors="replace")
try:
data = json.loads(data)
except:
pass
if isinstance(data, list):
return [self._clean_data(d) for d in data]
if isinstance(data, dict):
SENSITIVE_FIELDS = {
"api",
"token",
"key",
"secret",
"password",
"signature",
"access",
"refresh"
}
data = dict(data)
if self.sensitive_fields:
SENSITIVE_FIELDS = SENSITIVE_FIELDS | {
field.lower() for field in self.sensitive_fields
}
for key, value in data.items():
try:
value = ast.literal_eval(value)
except (ValueError, SyntaxError):
pass
if isinstance(value, (list, dict)):
data[key] = self._clean_data(value)
if key.lower() in SENSITIVE_FIELDS:
data[key] = self.CLEANED_SUBSTITUTE
return data

49
server/apps/ops/models.py Normal file
View File

@ -0,0 +1,49 @@
import uuid
from django.db import models
from django.contrib.postgres.fields import JSONField
class DrfRequestLog(models.Model):
"""Logs Django rest framework API requests"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(
'system.user',
on_delete=models.SET_NULL,
null=True,
blank=True,
)
requested_at = models.DateTimeField(db_index=True)
response_ms = models.PositiveIntegerField(default=0)
path = models.CharField(
max_length=400,
db_index=True,
help_text="请求地址",
)
view = models.CharField(
max_length=400,
null=True,
blank=True,
db_index=True,
help_text="执行视图",
)
view_method = models.CharField(
max_length=20,
null=True,
blank=True,
db_index=True,
)
remote_addr = models.GenericIPAddressField()
host = models.URLField()
method = models.CharField(max_length=10)
query_params = JSONField(null=True, blank=True)
data = JSONField(null=True, blank=True)
response = JSONField(null=True, blank=True)
errors = models.TextField(null=True, blank=True)
agent = models.TextField(null=True, blank=True)
status_code = models.PositiveIntegerField(null=True, blank=True, db_index=True)
class Meta:
verbose_name = "DRF请求日志"
def __str__(self):
return "{} {}".format(self.method, self.path)

17
server/apps/ops/tasks.py Normal file
View File

@ -0,0 +1,17 @@
# Create your tasks here
from __future__ import absolute_import, unicode_literals
from datetime import timedelta
from apps.ops.models import DrfRequestLog
# from celery import shared_task
from django.utils import timezone
# @shared_task()
# def clear_drf_log():
# """清除7天前的日志记录
# 清除7天前的日志记录
# """
# now = timezone.now()
# days7_ago = now - timedelta(days=7)
# DrfRequestLog.objects.filter(create_time__lte=days7_ago).delete()

3
server/apps/ops/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

16
server/apps/ops/urls.py Normal file
View File

@ -0,0 +1,16 @@
from django.urls import path, include
from rest_framework import routers
from .views import CpuView, DiskView, DrfRequestLogViewSet, LogView, LogDetailView, MemoryView
API_BASE_URL = 'api/ops/'
HTML_BASE_URL = 'ops/'
router = routers.DefaultRouter()
router.register('request_log', DrfRequestLogViewSet, basename="request_log")
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
path(API_BASE_URL + 'log/', LogView.as_view()),
path(API_BASE_URL + 'log/<str:name>/', LogDetailView.as_view()),
path(API_BASE_URL + 'server/cpu/', CpuView.as_view()),
path(API_BASE_URL + 'server/memory/', MemoryView.as_view()),
path(API_BASE_URL + 'server/disk/', DiskView.as_view())
]

159
server/apps/ops/views.py Normal file
View File

@ -0,0 +1,159 @@
from django.shortcuts import render
import psutil
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from django.conf import settings
import os
from rest_framework import serializers
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.exceptions import NotFound
from rest_framework.mixins import ListModelMixin
from apps.ops.filters import DrfLogFilterSet
from apps.ops.models import DrfRequestLog
from rest_framework.viewsets import GenericViewSet
from apps.ops.errors import LOG_NOT_FONED
from rest_framework.decorators import action
# Create your views here.
class CpuView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
"""
获取服务器cpu当前状态
获取服务器cpu当前状态
"""
ret = {'cpu': {}}
ret['cpu']['count'] = psutil.cpu_count()
ret['cpu']['lcount'] = psutil.cpu_count(logical=False)
ret['cpu']['percent'] = psutil.cpu_percent(interval=1)
return Response(ret)
class MemoryView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
"""
获取服务器内存当前状态
获取服务器内存当前状态
"""
ret = {'memory': {}}
memory = psutil.virtual_memory()
ret['memory']['total'] = round(memory.total/1024/1024/1024, 2)
ret['memory']['used'] = round(memory.used/1024/1024/1024, 2)
ret['memory']['percent'] = memory.percent
return Response(ret)
class DiskView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
"""
获取服务器硬盘当前状态
获取服务器硬盘当前状态
"""
ret = {'disk': {}}
disk = psutil.disk_usage('/')
ret['disk']['total'] = round(disk.total/1024/1024/1024, 2)
ret['disk']['used'] = round(disk.used/1024/1024/1024, 2)
ret['disk']['percent'] = disk.percent
return Response(ret)
def get_file_list(file_path):
dir_list = os.listdir(file_path)
if not dir_list:
return
else:
# 注意这里使用lambda表达式将文件按照最后修改时间顺序升序排列
# os.path.getmtime() 函数是获取文件最后修改时间
# os.path.getctime() 函数是获取文件最后创建时间
dir_list = sorted(dir_list, key=lambda x: os.path.getmtime(
os.path.join(file_path, x)), reverse=True)
# print(dir_list)
return dir_list
class LogView(APIView):
@swagger_auto_schema(manual_parameters=[
openapi.Parameter('name', openapi.IN_QUERY,
description='日志文件名', type=openapi.TYPE_STRING)
])
def get(self, request, *args, **kwargs):
"""
查看最近的日志列表
查看最近的日志列表
"""
logs = []
name = request.GET.get('name', None)
# for root, dirs, files in os.walk(settings.LOG_PATH):
# files.reverse()
for file in get_file_list(settings.LOG_PATH):
if len(logs) > 50:
break
filepath = os.path.join(settings.LOG_PATH, file)
if name:
if name in filepath:
fsize = os.path.getsize(filepath)
if fsize:
logs.append({
"name": file,
"filepath": filepath,
"size": round(fsize/1024, 1)
})
else:
fsize = os.path.getsize(filepath)
if fsize:
logs.append({
"name": file,
"filepath": filepath,
"size": round(fsize/1024, 1)
})
return Response(logs)
class LogDetailView(APIView):
def get(self, request, name):
"""
查看日志详情
查看日志详情
"""
try:
with open(os.path.join(settings.LOG_PATH, name)) as f:
data = f.read()
return Response(data)
except Exception:
raise NotFound(**LOG_NOT_FONED)
class DrfRequestLogSerializer(serializers.ModelSerializer):
class Meta:
model = DrfRequestLog
fields = '__all__'
class DrfRequestLogViewSet(ListModelMixin, GenericViewSet):
"""请求日志
请求日志
"""
perms_map = {'get': '*'}
queryset = DrfRequestLog.objects.all()
serializer_class = DrfRequestLogSerializer
ordering = ['-requested_at']
filterset_class = DrfLogFilterSet
@action(methods=['delete'], detail=False, perms_map = {'delete':'log_delete'})
def clear(self, request, *args, **kwargs):
"""
清空日志
"""
DrfRequestLog.objects.all().delete()
return Response()

View File

@ -1,5 +1,4 @@
from django.contrib import admin from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import User, Organization, Role, Permission, DictType, Dict, File from .models import User, Organization, Role, Permission, DictType, Dict, File
# Register your models here. # Register your models here.
admin.site.register(User) admin.site.register(User)
@ -7,5 +6,5 @@ admin.site.register(Organization)
admin.site.register(Role) admin.site.register(Role)
admin.site.register(Permission) admin.site.register(Permission)
admin.site.register(DictType) admin.site.register(DictType)
admin.site.register(Dict, SimpleHistoryAdmin) admin.site.register(Dict)
admin.site.register(File) admin.site.register(File)

View File

@ -4,7 +4,6 @@ from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import simple_history.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -54,7 +53,6 @@ class Migration(migrations.Migration):
'ordering': ('-history_date', '-history_id'), 'ordering': ('-history_date', '-history_id'),
'get_latest_by': 'history_date', 'get_latest_by': 'history_date',
}, },
bases=(simple_history.models.HistoricalChanges, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='File', name='File',

View File

@ -0,0 +1,16 @@
# Generated by Django 3.0.5 on 2022-10-12 06:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('system', '0021_auto_20220530_1520'),
]
operations = [
migrations.DeleteModel(
name='HistoricalDict',
),
]

View File

@ -4,7 +4,6 @@ import django.utils.timezone as timezone
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from utils.model import SoftModel, BaseModel from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
class Province(models.Model): class Province(models.Model):
id = models.CharField('id', primary_key=True, max_length=20) id = models.CharField('id', primary_key=True, max_length=20)
@ -160,7 +159,6 @@ class Dict(SoftModel):
pid = models.ForeignKey('self', null=True, blank=True, pid = models.ForeignKey('self', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='') on_delete=models.SET_NULL, verbose_name='')
is_used = models.BooleanField('是否有效', default=True) is_used = models.BooleanField('是否有效', default=True)
history = HistoricalRecords()
class Meta: class Meta:
verbose_name = '字典' verbose_name = '字典'

View File

@ -1,4 +1,5 @@
import logging import logging
from re import L
from django.conf import settings from django.conf import settings
from django.contrib.auth.hashers import check_password, make_password from django.contrib.auth.hashers import check_password, make_password
@ -23,6 +24,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
from apps.ops.mixins import MyLoggingMixin
from utils.pagination import PageOrNot from utils.pagination import PageOrNot
from utils.queryset import get_child_queryset2 from utils.queryset import get_child_queryset2
@ -30,6 +32,7 @@ from .filters import UserFilter
from .models import (City, Dict, DictType, File, Message, Organization, Permission, from .models import (City, Dict, DictType, File, Message, Organization, Permission,
Position, Province, Role, User, UserThird) Position, Province, Role, User, UserThird)
from .permission import RbacPermission, get_permission_list from .permission import RbacPermission, get_permission_list
from rest_framework_simplejwt.views import TokenObtainPairView
from .permission_data import RbacFilterSet from .permission_data import RbacFilterSet
from .serializers import (CitySerializer, DictSerializer, DictTypeSerializer, FileSerializer, from .serializers import (CitySerializer, DictSerializer, DictTypeSerializer, FileSerializer,
OrganizationSerializer, PermissionSerializer, OrganizationSerializer, PermissionSerializer,
@ -71,8 +74,11 @@ def get_tokens_for_user(user):
} }
import datetime import datetime
class MyTokenView(MyLoggingMixin, TokenObtainPairView):
def should_log(self, request, response):
return response.status_code == 200
class Login2View(APIView): class Login2View(MyLoggingMixin, APIView):
""" """
邮箱验证码登录 邮箱验证码登录
""" """
@ -89,6 +95,9 @@ class Login2View(APIView):
return Response(get_tokens_for_user(user), status=status.HTTP_200_OK) return Response(get_tokens_for_user(user), status=status.HTTP_200_OK)
return Response('验证码错误', status=status.HTTP_400_BAD_REQUEST) return Response('验证码错误', status=status.HTTP_400_BAD_REQUEST)
def should_log(self, request, response):
return response.status_code == 200
class sendMsg(APIView): class sendMsg(APIView):
authentication_classes = [] authentication_classes = []
permission_classes = [] permission_classes = []
@ -407,7 +416,7 @@ class UserViewSet(PageOrNot, ModelViewSet):
class WXMPlogin(APIView): class WXMPlogin(MyLoggingMixin, APIView):
authentication_classes=[] authentication_classes=[]
permission_classes=[] permission_classes=[]
@ -429,6 +438,9 @@ class WXMPlogin(APIView):
except: except:
raise AuthenticationFailed raise AuthenticationFailed
def should_log(self, request, response):
return response.status_code == 200
class ProviceViewSet(PageOrNot, ListModelMixin, GenericViewSet): class ProviceViewSet(PageOrNot, ListModelMixin, GenericViewSet):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.5 on 2022-10-12 08:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vod', '0005_video_sort_str'),
]
operations = [
migrations.AddField(
model_name='viewrecord',
name='total_seconds',
field=models.PositiveIntegerField(default=0, verbose_name='总观看秒数'),
),
]

View File

@ -1,3 +1,4 @@
from tabnanny import verbose
from django.db import models from django.db import models
from utils.model import BaseModel from utils.model import BaseModel
from apps.system.models import User, CommonAModel, Dict from apps.system.models import User, CommonAModel, Dict
@ -30,6 +31,7 @@ class ViewRecord(BaseModel):
views = models.IntegerField(verbose_name='观看次数', default=0) views = models.IntegerField(verbose_name='观看次数', default=0)
current = 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='record_video')
total_seconds = models.PositiveIntegerField(verbose_name='总观看秒数', default=0)
class Meta: class Meta:

View File

@ -133,6 +133,9 @@ class MyViewRecordAPIView(APIView):
record.views = record.views + 1 record.views = record.views + 1
video.views = video.views + 1 video.views = video.views + 1
video.save() video.save()
else:
record.total_seconds = record.total_seconds + 10
record.save()
record.save() record.save()
return Response() return Response()

Binary file not shown.

View File

@ -48,7 +48,9 @@ INSTALLED_APPS = [
'apps.supervision', 'apps.supervision',
'apps.quality', 'apps.quality',
'apps.vod', 'apps.vod',
'apps.consulting' 'apps.consulting',
'apps.exam',
'apps.ops'
] ]
@ -167,8 +169,8 @@ REST_FRAMEWORK = {
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S', 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',
'DATE_FORMAT': '%Y-%m-%d', 'DATE_FORMAT': '%Y-%m-%d',
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'UNAUTHENTICATED_USER': None, # 'UNAUTHENTICATED_USER': None,
'UNAUTHENTICATED_TOKEN': None, # 'UNAUTHENTICATED_TOKEN': None,
} }
# simplejwt配置 # simplejwt配置
SIMPLE_JWT = { SIMPLE_JWT = {

View File

@ -19,17 +19,13 @@ from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from rest_framework import routers from rest_framework import routers
from rest_framework.documentation import include_docs_urls from rest_framework.documentation import include_docs_urls
from rest_framework_simplejwt.views import (TokenObtainPairView, from rest_framework_simplejwt.views import TokenRefreshView
TokenRefreshView)
from apps.system.views import FileViewSet, LogoutView, Login2View from apps.system.views import FileViewSet, LogoutView, Login2View, MyTokenView
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register('file', FileViewSet, basename="file") router.register('file', FileViewSet, basename="file")
from django.conf.urls import url from django.conf.urls import url
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenViewBase
from apps.system.views import WXMPlogin,mediaauth from apps.system.views import WXMPlogin,mediaauth
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
@ -54,7 +50,7 @@ urlpatterns = [
path('api/admin/', admin.site.urls), path('api/admin/', admin.site.urls),
path('api/mediaauth/',mediaauth), path('api/mediaauth/',mediaauth),
path('api/wxmplogin/',WXMPlogin.as_view()), path('api/wxmplogin/',WXMPlogin.as_view()),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/', MyTokenView.as_view(), name='token_obtain_pair'),
path('api/token2/', Login2View.as_view(), name='token_obtain_2'), path('api/token2/', Login2View.as_view(), name='token_obtain_2'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/black/', LogoutView.as_view(), name='token_black'), path('api/token/black/', LogoutView.as_view(), name='token_black'),
@ -64,6 +60,8 @@ urlpatterns = [
path('api/quality/', include('apps.quality.urls')), path('api/quality/', include('apps.quality.urls')),
path('api/vod/', include('apps.vod.urls')), path('api/vod/', include('apps.vod.urls')),
path('api/consulting/', include('apps.consulting.urls')), path('api/consulting/', include('apps.consulting.urls')),
path('', include('apps.ops.urls')),
path('', include('apps.exam.urls')),
path('api/docs/', include_docs_urls(title="接口文档",authentication_classes=[], permission_classes=[])), path('api/docs/', include_docs_urls(title="接口文档",authentication_classes=[], permission_classes=[])),
url(r'^api/swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), url(r'^api/swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),

Binary file not shown.

View File

@ -49,6 +49,9 @@ class SoftDeletableManager(SoftDeletableManagerMixin, models.Manager):
class BaseModel(models.Model): class BaseModel(models.Model):
"""
自增ID,带有create_time, update_time, is_deleted
"""
create_time = models.DateTimeField( create_time = models.DateTimeField(
default=timezone.now, verbose_name='创建时间', help_text='创建时间') default=timezone.now, verbose_name='创建时间', help_text='创建时间')
update_time = models.DateTimeField( update_time = models.DateTimeField(

View File

@ -7,11 +7,9 @@ class MyPagination(PageNumberPagination):
page_size_query_param = 'page_size' page_size_query_param = 'page_size'
class PageOrNot: class PageOrNot:
def paginate_queryset(self, queryset): def paginate_queryset(self, queryset, request, view=None):
if (self.paginator is None): if request.query_params.get('pageoff', None) or request.query_params.get('page', None) == '0':
return None if queryset.count() < 500:
elif (self.request.query_params.get('pageoff', None)) and queryset.count()<500: return None
return None raise ParseError('单次请求数据量大,请分页获取')
elif (self.request.query_params.get('pageoff', None)) and queryset.count()>=500: return super().paginate_queryset(queryset, request, view=view)
raise ParseError('单次请求数据量大,请求中止')
return self.paginator.paginate_queryset(queryset, self.request, view=self)