统计观看时长/登录日志
This commit is contained in:
parent
7b956f5ef5
commit
d819dccc08
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ExamConfig(AppConfig):
|
||||
name = 'exam'
|
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,6 @@
|
|||
from django.urls import path
|
||||
|
||||
API_BASE_URL = 'api/exam/'
|
||||
HTML_BASE_URL = 'exam/'
|
||||
urlpatterns = [
|
||||
]
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OpsConfig(AppConfig):
|
||||
name = 'ops'
|
||||
verbose_name = '系统监控'
|
|
@ -0,0 +1 @@
|
|||
LOG_NOT_FONED = {"code": "log_not_found", "detail": "日志不存在"}
|
|
@ -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()
|
|
@ -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请求日志',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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
|
|
@ -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)
|
|
@ -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()
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -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())
|
||||
]
|
|
@ -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()
|
|
@ -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)
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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 = '字典'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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='总观看秒数'),
|
||||
),
|
||||
]
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
@ -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 = {
|
||||
|
|
|
@ -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.
|
@ -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(
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue