import logging from re import L from django.conf import settings from django.contrib.auth.hashers import check_password, make_password from django.contrib.auth.models import UserManager from django.core.cache import cache from django.db.models import Count from django.http import request from django.http.response import JsonResponse from django_filters.rest_framework import DjangoFilterBackend from rest_framework import status from rest_framework.decorators import (action, api_view, authentication_classes, permission_classes) from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin) from rest_framework.pagination import PageNumberPagination from rest_framework.parsers import (FileUploadParser, JSONParser, MultiPartParser) from rest_framework.permissions import IsAuthenticated 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 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, PositionSerializer, ProvinceSerializer, RoleSerializer, UserCreateSerializer, UserListSerializer, UserModifySerializer) from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page import requests import json from rest_framework.exceptions import AuthenticationFailed from django.db import transaction from rest_framework.exceptions import ParseError from apps.system.permission import has_permission from openpyxl import load_workbook import random import smtplib import string from email.header import Header from email.mime.text import MIMEText from email.utils import formataddr from rest_framework_simplejwt.tokens import RefreshToken logger = logging.getLogger('log') # logger.info('请求成功! response_code:{};response_headers:{};response_body:{}'.format(response_code, response_headers, response_body[:251])) # logger.error('请求出错:{}'.format(error)) class ImpMixin: def get_queryset(self): mydept = self.request.user.dept qs = super().get_queryset() if has_permission('task2', self.request.user): return qs return qs.filter(belong_dept=mydept) def format_date(self, ind, val): new_val = val if isinstance(val, datetime.datetime): new_val = val.date() elif isinstance(val, datetime.date): new_val = val elif isinstance(val, str): try: new_val = datetime.datetime.strptime(val, '%Y-%m-%d').date() except ValueError: raise ParseError(f'第{ind}行, 日期时间格式错误') elif val is None: pass else: raise ParseError(f'第{ind}行, 日期时间格式错误') return new_val def get_enum(self, val, atuple, ind): for i in atuple: if i[1] == val: return i[0] raise ParseError('第{}: 请选择固定选项值'.format(ind)) def F(self, data, sheet, i, etype): raise NotImplementedError() def gen_imp_view(self, request, start: int, mySerializer): if 'file' not in request.data: raise ParseError('请提供文件') path = request.data['file'] if not str(path).endswith('.xlsx'): raise ParseError('请提供xlsx格式文件') fullpath = settings.BASE_DIR + str(path) wb = load_workbook(fullpath,data_only=True) sheet = wb.active # 遍历Excel文件中的数据 data_list = self.build_data(sheet, start) serializer = mySerializer(data=data_list, many=True, context={'request': request}) if serializer.is_valid(): serializer.save(create_by=request.user, belong_dept=request.user.dept) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response({'uploaded': 'File uploaded successfully'}, status=status.HTTP_201_CREATED) class LogoutView(APIView): permission_classes = [] def get(self, request, *args, **kwargs): # 可将token加入黑名单 return Response(status=status.HTTP_200_OK) def get_tokens_for_user(user): refresh = RefreshToken.for_user(user) return { 'refresh': str(refresh), 'access': str(refresh.access_token), } import datetime class MyTokenView(MyLoggingMixin, TokenObtainPairView): def should_log(self, request, response): return response.status_code == 200 class Login2View(MyLoggingMixin, APIView): """ 邮箱验证码登录 """ authentication_classes = [] permission_classes = [] def post(self, request): mail = request.data['mail'] msg = request.data['msg'] if not User.objects.filter(username=mail).exists(): return Response('账户不存在', status=status.HTTP_400_BAD_REQUEST) a_minute_ago=datetime.datetime.now()-datetime.timedelta(minutes=1) if Message.objects.filter(mail=mail, create_time__gte=a_minute_ago).exists() and Message.objects.filter(mail=mail).last().msg == msg: 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 = [] permission_classes = [] def post(self, request): code = random.randint(10000,99999) my_sender = 'gxpt@ctc.ac.cn' my_user = request.data['mail'] my_pass = 'Pintai123' if not User.objects.filter(username=my_user).exists(): return Response('该账户不存在', status=status.HTTP_400_BAD_REQUEST) try: # 邮件内容 msg=MIMEText('您好,共享平台本次登陆验证码为' + str(code),'plain','utf-8') # 括号里的对应发件人邮箱昵称、发件人邮箱账号 msg['From']=formataddr(["国检集团检验检测能力共享平台",my_sender]) # 括号里的对应收件人邮箱昵称、收件人邮箱账号 msg['To']=formataddr(["",my_user]) # 邮件的主题 msg['Subject'] = Header(str(code), 'utf-8').encode() # SMTP服务器,腾讯企业邮箱端口是465,腾讯邮箱支持SSL(不强制), 不支持TLS # qq邮箱smtp服务器地址:smtp.qq.com,端口号:456 # 163邮箱smtp服务器地址:smtp.163.com,端口号:25 server=smtplib.SMTP_SSL("smtp.exmail.qq.com", 465) # 登录服务器,括号中对应的是发件人邮箱账号、邮箱密码 server.login(my_sender, my_pass) # 发送邮件,括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件 server.sendmail(my_sender,[my_user,],msg.as_string()) # 关闭连接 server.quit() Message.objects.filter(mail=my_user).delete() Message.objects.create(mail=my_user, msg=code) except: return Response('验证码发送失败', status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_200_OK) class DictTypeViewSet(ModelViewSet): """ 数据字典类型:增删改查 """ perms_map = {'get': '*', 'post': 'dicttype_create', 'put': 'dicttype_update', 'delete': 'dicttype_delete'} queryset = DictType.objects.all() serializer_class = DictTypeSerializer pagination_class = None search_fields = ['name'] ordering_fields = ['id'] ordering = 'id' class DictViewSet(ModelViewSet): """ 数据字典:增删改查 """ perms_map = {'get': '*', 'post': 'dict_create', 'put': 'dict_update', 'delete': 'dict_delete'} queryset = Dict.objects.all() serializer_class = DictSerializer search_fields = ['name', 'code'] filterset_fields = ['type__code', 'type', 'is_used'] ordering_fields = ['sort'] ordering = 'sort' def paginate_queryset(self, queryset): """ 如果查询参数里没有page但有type或type__code时则不分页,否则请求分页 也可用utils.pageornot方法 """ if self.paginator is None: return None elif (not self.request.query_params.get('page', None)) and ((self.request.query_params.get('type__code', None)) or (self.request.query_params.get('type', None))): return None return self.paginator.paginate_queryset(queryset, self.request, view=self) class PositionViewSet(ModelViewSet): """ 岗位:增删改查 """ perms_map = {'get': '*', 'post': 'position_create', 'put': 'position_update', 'delete': 'position_delete'} queryset = Position.objects.all() serializer_class = PositionSerializer pagination_class = None search_fields = ['name'] ordering_fields = ['id'] ordering = 'id' class TestView(APIView): perms_map = {'get': 'test_view'} # 单个API控权 pass class PermissionViewSet(ModelViewSet): """ 权限:增删改查 """ perms_map = {'get': '*', 'post': 'perm_create', 'put': 'perm_update', 'delete': 'perm_delete'} queryset = Position.objects.all() queryset = Permission.objects.all() serializer_class = PermissionSerializer pagination_class = None search_fields = ['name'] ordering_fields = ['sort'] ordering = 'sort' class OrganizationViewSet(PageOrNot,ModelViewSet): """ 组织机构:增删改查 """ perms_map = {'get': '*', 'post': 'org_create', 'put': 'org_update', 'delete': 'org_delete'} queryset = Organization.objects.all() serializer_class = OrganizationSerializer pagination_class = None search_fields = ['name'] filterset_fields = ['pid','name', 'type','can_supervision'] ordering_fields = ['sort'] ordering = ['sort', 'pk'] def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) if self.request.query_params.get('service_ok', 0): queryset = queryset.exclude(service__isnull=True) return queryset @action(methods=['get'], detail=False, permission_classes=[IsAuthenticated]) def sub(self, request, pk=None): """ 获取本人所在部门的下级部门 """ subdept = get_child_queryset2(request.user.dept).order_by('sort') serializer = OrganizationSerializer(subdept, many=True) return Response(serializer.data) class RoleViewSet(ModelViewSet): """ 角色:增删改查 """ perms_map = {'get': '*', 'post': 'role_create', 'put': 'role_update', 'delete': 'role_delete'} queryset = Role.objects.all() serializer_class = RoleSerializer pagination_class = None search_fields = ['name'] filterset_fields = [] ordering_fields = ['id'] ordering = 'id' class UserExamViewset(ImpMixin, ModelViewSet): """ 用户考试:增删改查 """ perms_map = {'get': '*', 'post': 'user_exam_create', 'put': 'user_exam_create', 'delete': 'user_exam_delete'} queryset = User.objects.all().order_by('-id') serializer_class = UserListSerializer filterset_class = UserFilter search_fields = ['username', 'name', 'phone', 'email'] ordering_fields = ['-id'] def get_queryset(self): queryset = self.queryset dept = self.request.user.dept.id deptqueryset = get_child_queryset2(Organization.objects.get(pk=dept)) queryset = queryset.filter(dept__in=deptqueryset) return queryset def create(self, request, *args, **kwargs): # 创建用户默认添加密码 password = request.data['password'] if 'password' in request.data else None if password: password = make_password(password) else: # password = make_password(''.join(random.sample(string.ascii_letters + string.digits, 8))) password = make_password('0000') exam_role = Role.objects.get(name='考试') request.data['roles'] = [exam_role.id] serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(password=password) return Response(serializer.data) def build_data(self, sheet, start): data_list = [] exam_role = Role.objects.get(name='考试') if not exam_role: return Response({'msg': '考试角色不存在'}) for row in sheet.iter_rows(min_row=start, values_only=True): # 假设第一行是表头,从第二行开始读取数据 if row[0] is not None: dept = Organization.objects.get(name=row[2]) user_depts = get_child_queryset2(request.user.dept).order_by('sort') serializer = OrganizationSerializer(user_depts, many=True) depts = any(i.id==dept.id for i in serializer.data) if depts is False: return Response({'msg': f'本公司下不存在此部门{row[2]}'}) serializer_data = { 'name': row[0], 'username':row[1], 'dept':dept.id, 'roles':[exam_role.id], 'avatar': "/media/default/avatar.png" } data_list.append(serializer_data) return data_list @action(detail=False, methods=['post']) @transaction.atomic def imp(self, request, *args, **kwargs): """ 导入数据 """ return self.gen_imp_view(request, 2, UserListSerializer) class UserViewSet(PageOrNot, ModelViewSet): """ 用户管理:增删改查 """ perms_map = {'get': '*', 'post': 'user_create', 'put': 'user_update', 'delete': 'user_delete'} queryset = User.objects.all().order_by('-id') serializer_class = UserListSerializer filterset_class = UserFilter search_fields = ['username', 'name', 'phone', 'email'] ordering_fields = ['-id'] def get_queryset(self): queryset = self.queryset if hasattr(self.get_serializer_class(), 'setup_eager_loading'): queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化 dept = self.request.query_params.get('dept', None) # 该部门及其子部门所有员工 if dept is not None: deptqueryset = get_child_queryset2(Organization.objects.get(pk=dept)) queryset = queryset.filter(dept__in=deptqueryset) return queryset def get_serializer_class(self): # 根据请求类型动态变更serializer if self.action == 'create': return UserCreateSerializer elif self.action == 'list': return UserListSerializer return UserModifySerializer def create(self, request, *args, **kwargs): # 创建用户默认添加密码 password = request.data['password'] if 'password' in request.data else None if password: password = make_password(password) else: password = make_password(''.join(random.sample(string.ascii_letters + string.digits, 8))) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(password=password) return Response(serializer.data) @action(methods=['put'], detail=False, permission_classes=[IsAuthenticated], url_name='change_password') def password(self, request, pk=None): """ 用户修改密码 """ user = request.user old_password = request.data['old_password'] if check_password(old_password, user.password): new_password1 = request.data['new_password1'] new_password2 = request.data['new_password2'] if new_password1 == new_password2: user.set_password(new_password2) user.save() return Response('密码修改成功!', status=status.HTTP_200_OK) else: return Response('新密码两次输入不一致!', status=status.HTTP_400_BAD_REQUEST) else: return Response('旧密码错误!', status=status.HTTP_400_BAD_REQUEST) # perms_map={'get':'*'}, 自定义action控权 @action(methods=['get'], detail=False, url_name='my_info', permission_classes=[IsAuthenticated]) def info(self, request, pk=None): """ 初始化用户信息 """ user = request.user perms = get_permission_list(user) data = { 'id': user.id, 'username': user.username, 'name': user.name, 'dept': user.dept.pk if user.dept else '', 'dept_name':user.dept.name if user.dept else '', 'roles': user.roles.all().values_list('name', flat=True), # 'avatar': request._request._current_scheme_host + '/media/' + str(user.image), 'avatar': request._request._current_scheme_host + user.avatar, 'perms': perms, } try: usert = UserThird.objects.get(user=user,type='wx_mp') data['wxmp_openid'] = usert.openid except: pass return Response(data) @action(methods=['put'], detail=True, url_name='userpw_reset', permission_classes=[IsAuthenticated]) def resetpw(self, request, pk=None): """ 重置密码 """ if request.user.is_superuser: user = self.get_object() user.set_password('0000') user.save() return Response('密码已重置为0000', status=status.HTTP_200_OK) return Response('权限不足', status=status.HTTP_400_BAD_REQUEST) @action(methods=['post'], detail=False, permission_classes=[IsAuthenticated]) def bindwxmp(self, request, pk=None): """ 绑定微信 """ code = request.data['code'] info = requests.get('https://api.weixin.qq.com/sns/jscode2session?appid='+settings.WX_APPID+'&secret='+settings.WX_APPSECRET+'&js_code=' + code+'&grant_type=authorization_code').content.decode('utf-8') info = json.loads(info) openid = info['openid'] instances = UserThird.objects.filter(openid=openid) if instances.exists(): instance = instances[0] instance.user = request.user instance.save() else: UserThird.objects.get_or_create(openid=openid, type='wx_mp', user=request.user, defaults={'openid':openid, 'user':request.user, 'type':'wx_mp'}) return Response({'wxmp_openid':openid},status=status.HTTP_200_OK) @action(methods=['post'], detail=False, permission_classes=[IsAuthenticated]) def unbindwxmp(self, request, pk=None): """ 解除微信绑定 """ UserThird.objects.filter(user=request.user, type='wx_mp').delete() return Response() @action(methods=['post'], detail=False, perms_map={'post':'user_daoru'}) @transaction.atomic def daoru(self, request, pk=None): from openpyxl import load_workbook wb = load_workbook('./media/default/users.xlsx') sheet = wb.get_sheet_by_name('Sheet2') i = 3 role1 = Role.objects.get(name='能力查询') role2 = Role.objects.get(name='资质能力报送') role3 = Role.objects.get(name='资料分享') role4 = Role.objects.get(name='视频培训') while sheet['b'+str(i)].value: name = sheet['b'+str(i)].value email = sheet['e'+str(i)].value dept = Organization.objects.get(name=sheet['j'+str(i)].value) if not User.objects.filter(username=email).exists(): user = User.objects.create(name=name, username=email, password=make_password(''.join(random.sample(string.ascii_letters + string.digits, 8))), dept=dept) else: user = User.objects.get(username=email) if sheet['f'+str(i)].value: user.roles.add(role1) if sheet['g'+str(i)].value: user.roles.add(role2) if sheet['h'+str(i)].value: user.roles.add(role3) if sheet['i'+str(i)].value: user.roles.add(role4) print(str(i)+name+'已导入') i = i + 1 return Response() class WXMPlogin(MyLoggingMixin, APIView): authentication_classes=[] permission_classes=[] def post(self,request): """ 微信登录 """ code = request.data['code'] info = requests.get('https://api.weixin.qq.com/sns/jscode2session?appid='+settings.WX_APPID+'&secret='+settings.WX_APPSECRET+'&js_code=' + code+'&grant_type=authorization_code').content.decode('utf-8') info = json.loads(info) openid = info.get('openid', None) if not openid: Response('微信id获取失败', status=status.HTTP_400_BAD_REQUEST) session_key = info['session_key'] try: user = UserThird.objects.get(openid=openid,type='wx_mp').user 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 class ProviceViewSet(PageOrNot, ListModelMixin, GenericViewSet): queryset = Province.objects.all() serializer_class = ProvinceSerializer search_fields = ['name'] ordering = 'id' @method_decorator(cache_page(60*60*2)) def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) class CityViewSet(PageOrNot, ListModelMixin, GenericViewSet): queryset = City.objects.all() serializer_class = CitySerializer filterset_fields = ['parent'] search_fields = ['name'] ordeing = 'id' @method_decorator(cache_page(60*60*2)) def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) class FileViewSet(ModelViewSet): """ 文件:增删改查 """ perms_map = None permission_classes=[IsAuthenticated] parser_classes = [MultiPartParser, JSONParser] queryset = File.objects.all() serializer_class = FileSerializer filterset_fields = ['type'] search_fields = ['name'] ordering = '-create_time' def perform_create(self, serializer): fileobj = self.request.data.get('file') name = fileobj._name size = fileobj.size mime = fileobj.content_type type = '其它' if 'image' in mime: type = '图片' elif 'video' in mime: type = '视频' elif 'audio' in mime: type = '音频' elif 'application' or 'text' in mime: type = '文档' instance = serializer.save(create_by = self.request.user, name=name, size=size, type=type, mime=mime) instance.path = settings.MEDIA_URL + instance.file.name instance.save() from rest_framework_simplejwt.state import token_backend from django.http import HttpResponseForbidden, HttpResponse import urllib.parse as urlparse def mediaauth(request): path = request.META.get("HTTP_OURL", '') if '/cert/' in path: return HttpResponse() token = "" token = request.COOKIES.get('token', None) if not token: try: token = request.META.get("HTTP_OURL", None).split('=')[-1] except: pass if token: try: payload = token_backend.decode(token) if payload.get('user_id', None): return HttpResponse() except: return HttpResponseForbidden() return HttpResponseForbidden() class InitAreaView(APIView): def get(self, request): from django.db import transaction with transaction.atomic(): from apps.system.pcity import ps, cs for i in ps: Province.objects.create(id=i['code'], name=i['name']) for i in cs: City.objects.create(id=i['code'], name=i['name'], parent=Province.objects.get(id=i['provinceCode'])) return Response()