统计观看时长/登录日志

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 simple_history.admin import SimpleHistoryAdmin
from .models import User, Organization, Role, Permission, DictType, Dict, File
# Register your models here.
admin.site.register(User)
@ -7,5 +6,5 @@ admin.site.register(Organization)
admin.site.register(Role)
admin.site.register(Permission)
admin.site.register(DictType)
admin.site.register(Dict, SimpleHistoryAdmin)
admin.site.register(Dict)
admin.site.register(File)

View File

@ -4,7 +4,6 @@ from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import simple_history.models
class Migration(migrations.Migration):
@ -54,7 +53,6 @@ class Migration(migrations.Migration):
'ordering': ('-history_date', '-history_id'),
'get_latest_by': 'history_date',
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
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 utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
class Province(models.Model):
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,
on_delete=models.SET_NULL, verbose_name='')
is_used = models.BooleanField('是否有效', default=True)
history = HistoricalRecords()
class Meta:
verbose_name = '字典'

View File

@ -1,4 +1,5 @@
import logging
from re import L
from django.conf import settings
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.viewsets import GenericViewSet, ModelViewSet
from rest_framework_simplejwt.tokens import RefreshToken
from apps.ops.mixins import MyLoggingMixin
from utils.pagination import PageOrNot
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,
Position, Province, Role, User, UserThird)
from .permission import RbacPermission, get_permission_list
from rest_framework_simplejwt.views import TokenObtainPairView
from .permission_data import RbacFilterSet
from .serializers import (CitySerializer, DictSerializer, DictTypeSerializer, FileSerializer,
OrganizationSerializer, PermissionSerializer,
@ -71,8 +74,11 @@ def get_tokens_for_user(user):
}
import datetime
class MyTokenView(MyLoggingMixin, TokenObtainPairView):
def should_log(self, request, response):
return response.status_code == 200
class Login2View(APIView):
class Login2View(MyLoggingMixin, APIView):
"""
邮箱验证码登录
"""
@ -88,6 +94,9 @@ class Login2View(APIView):
user = User.objects.get(username=mail)
return Response(get_tokens_for_user(user), status=status.HTTP_200_OK)
return Response('验证码错误', status=status.HTTP_400_BAD_REQUEST)
def should_log(self, request, response):
return response.status_code == 200
class sendMsg(APIView):
authentication_classes = []
@ -407,7 +416,7 @@ class UserViewSet(PageOrNot, ModelViewSet):
class WXMPlogin(APIView):
class WXMPlogin(MyLoggingMixin, APIView):
authentication_classes=[]
permission_classes=[]
@ -428,6 +437,9 @@ class WXMPlogin(APIView):
return Response(get_tokens_for_user(user), status=status.HTTP_200_OK)
except:
raise AuthenticationFailed
def should_log(self, request, response):
return response.status_code == 200

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 utils.model import BaseModel
from apps.system.models import User, CommonAModel, Dict
@ -30,6 +31,7 @@ class ViewRecord(BaseModel):
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')
total_seconds = models.PositiveIntegerField(verbose_name='总观看秒数', default=0)
class Meta:

View File

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

Binary file not shown.

View File

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

View File

@ -19,17 +19,13 @@ from django.contrib import admin
from django.urls import include, path
from rest_framework import routers
from rest_framework.documentation import include_docs_urls
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
from rest_framework_simplejwt.views import 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
router = routers.DefaultRouter()
router.register('file', FileViewSet, basename="file")
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 drf_yasg import openapi
from drf_yasg.views import get_schema_view
@ -54,7 +50,7 @@ urlpatterns = [
path('api/admin/', admin.site.urls),
path('api/mediaauth/',mediaauth),
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/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/black/', LogoutView.as_view(), name='token_black'),
@ -64,6 +60,8 @@ urlpatterns = [
path('api/quality/', include('apps.quality.urls')),
path('api/vod/', include('apps.vod.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=[])),
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):
"""
自增ID,带有create_time, update_time, is_deleted
"""
create_time = models.DateTimeField(
default=timezone.now, verbose_name='创建时间', help_text='创建时间')
update_time = models.DateTimeField(

View File

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