作者 wanweibin

完善后台接口

# Default ignored files
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="SQLite - db.sqlite3" uuid="40358238-ad1d-4a4e-aa6e-a585a40363bc">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db.sqlite3</jdbc-url>
</data-source>
</component>
</project>
\ No newline at end of file
... ...
<component name="ProjectDictionaryState">
<dictionary name="a2">
<words>
<w>abelzhu</w>
<w>agentid</w>
<w>authsucc</w>
<w>blocksize</w>
<w>btntxt</w>
<w>corpid</w>
<w>corpsecret</w>
<w>corsheaders</w>
<w>getuserdetail</w>
<w>getuserinfo</w>
<w>lconf</w>
<w>msgtype</w>
<w>simplejwt</w>
<w>textcard</w>
<w>touser</w>
<w>usercenter</w>
<w>userid</w>
<w>userlogin</w>
<w>vanwhebin</w>
<w>wxlogin</w>
<w>xlsx</w>
</words>
</dictionary>
... ...
... ... @@ -10,6 +10,7 @@
# @Date 2018-02-24
#
#
from abc import ABC
from .CorpApi import *
... ... @@ -26,8 +27,9 @@ SERVICE_CORP_API_TYPE = {
}
class ServiceCorpApi(CorpApi):
def __init__(self, suite_id, suite_secret, suite_ticket, auth_corpid=None, permanent_code=None):
class ServiceCorpApi(CorpApi, ABC):
def __init__(self, suite_id, suite_secret, suite_ticket, corpid, secret, auth_corpid=None, permanent_code=None):
super().__init__(corpid, secret)
self.suite_id = suite_id
self.suite_secret = suite_secret
self.suite_ticket = suite_ticket
... ... @@ -39,7 +41,7 @@ class ServiceCorpApi(CorpApi):
self.access_token = None
self.suite_access_token = None
## override CorpApi 的 refreshAccessToken, 使用第三方服务商的方法
# override CorpApi 的 refreshAccessToken, 使用第三方服务商的方法
def getAccessToken(self):
if self.access_token is None:
self.refreshAccessToken()
... ... @@ -54,8 +56,6 @@ class ServiceCorpApi(CorpApi):
})
self.access_token = response.get('access_token')
##
def getSuiteAccessToken(self):
if self.suite_access_token is None:
self.refreshSuiteAccessToken()
... ...
... ... @@ -10,6 +10,7 @@
# @Date 2018-02-26
#
#
from abc import ABC
from .AbstractApi import *
... ... @@ -18,13 +19,14 @@ SERVICE_PROVIDER_API_TYPE = {
'GET_LOGIN_INFO': ['/cgi-bin/service/get_login_info?access_token=PROVIDER_ACCESS_TOKEN', 'POST'],
'GET_REGISTER_CODE': ['/cgi-bin/service/get_register_code?provider_access_token=PROVIDER_ACCESS_TOKEN', 'POST'],
'GET_REGISTER_INFO': ['/cgi-bin/service/get_register_info?provider_access_token=PROVIDER_ACCESS_TOKEN', 'POST'],
'SET_AGENT_SCOPE': ['/cgi-bin/agent/set_scope', 'POST'], ### TODO
'SET_AGENT_SCOPE': ['/cgi-bin/agent/set_scope', 'POST'], # TODO
'SET_CONTACT_SYNC_SUCCESS': ['/cgi-bin/sync/contact_sync_success', 'GET'],
}
class ServiceProviderApi(AbstractApi):
class ServiceProviderApi(AbstractApi, ABC):
def __init__(self, corpid, provider_secret):
super().__init__()
self.corpid = corpid
self.provider_secret = provider_secret
... ...
... ... @@ -8,7 +8,7 @@
# @Date 2018-02-23
# 设置为true会打印一些调试信息
from wxProject.wxProject.settings import DEBUG
from wxProject.settings import DEBUG
DEBUG = DEBUG
... ... @@ -19,10 +19,10 @@ Conf = {
"CORP_ID": "ww0f3efc2873ad11c3",
# "通讯录同步"应用的secret, 开启api接口同步后,可以在管理端->"通讯录同步"看到
"CONTACT_SYNC_SECRET": "xWPfryWX7Fv1BcJSpivflpPQXC_v5iH0HY2zfu1soTA",
"CONTACT_SYNC_SECRET": "7MHpdQICiegx9rIc4iZrEPunb1aYUqdJYKSW9v7a1A8",
# 运维组ID
"OPS_DEP_ID": '346',
# "OPS_DEP_ID": '346',
# 某个自建应用的id及secret, 在管理端 -> 企业应用 -> 自建应用, 点进相应应用可以看到
"APP_ID": '1000078',
... ...
# Generated by Django 3.1.1 on 2020-10-07 04:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0006_auto_20200930_1745'),
]
operations = [
migrations.AlterField(
model_name='project',
name='auditor',
field=models.ManyToManyField(blank=True, related_name='project_auditor', to='project.Auditor', verbose_name='审核人员'),
),
]
... ...
# Generated by Django 3.1.1 on 2020-10-07 07:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0007_auto_20201007_1221'),
]
operations = [
migrations.AddField(
model_name='project',
name='is_done',
field=models.BooleanField(blank=True, default=False, verbose_name='是否完成'),
),
migrations.AddField(
model_name='project',
name='is_pass',
field=models.BooleanField(blank=True, default=False, verbose_name='是否通过'),
),
]
... ...
# Generated by Django 3.1.1 on 2020-10-07 08:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('project', '0008_auto_20201007_1544'),
]
operations = [
migrations.AlterModelOptions(
name='project',
options={'ordering': ('-is_done',)},
),
migrations.RenameField(
model_name='result',
old_name='program',
new_name='project',
),
migrations.AddField(
model_name='project',
name='creator',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='project_creator', to=settings.AUTH_USER_MODEL, verbose_name='创建人员'),
),
]
... ...
# Generated by Django 3.1.1 on 2020-10-07 08:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('project', '0009_auto_20201007_1626'),
]
operations = [
migrations.AlterField(
model_name='project',
name='creator',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_creator', to=settings.AUTH_USER_MODEL, verbose_name='创建人员'),
),
]
... ...
# Generated by Django 3.1.1 on 2020-10-07 09:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0010_auto_20201007_1626'),
]
operations = [
migrations.AlterModelOptions(
name='auditor',
options={'ordering': ('order',)},
),
migrations.AddField(
model_name='auditor',
name='order',
field=models.PositiveSmallIntegerField(blank=True, default=0, null=True, verbose_name='排序'),
),
]
... ...
... ... @@ -20,10 +20,14 @@ class Auditor(models.Model):
null=True,
blank=True,
verbose_name="上级领导")
order = models.PositiveSmallIntegerField(default=0, null=True, blank=True, verbose_name="排序")
def __str__(self):
return self.user.username
class Meta:
ordering = ('order', )
class Project(models.Model):
category = models.CharField(max_length=100, default="", verbose_name="产品类目")
... ... @@ -31,14 +35,17 @@ class Project(models.Model):
market_share_analysis = models.TextField(default="", verbose_name="产品市场占有率分析")
context_analysis = models.TextField(default="", verbose_name="产品场景分析")
attachments = models.CharField(max_length=800, default="", verbose_name="附件地址")
auditor = models.ManyToManyField(Auditor, related_name="project_auditor", verbose_name="审核人员")
auditor = models.ManyToManyField(Auditor, related_name="project_auditor", blank=True, verbose_name="审核人员")
is_done = models.BooleanField(default=False, blank=True, verbose_name="是否完成")
is_pass = models.BooleanField(default=False, blank=True, verbose_name="是否通过")
create_time = models.DateTimeField(auto_now_add=True)
creator = models.ForeignKey(User, related_name="project_creator", on_delete=models.CASCADE, verbose_name="创建人员")
def __str__(self):
return self.category + '-' + self.model_type
class Meta:
ordering = ('-create_time',)
ordering = ('-is_done',)
class Result(models.Model):
... ... @@ -48,7 +55,7 @@ class Result(models.Model):
)
auditor = models.ForeignKey(Auditor, on_delete=models.CASCADE, related_name="result_auditor", verbose_name="审核人员")
program = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="result_project", verbose_name="审核项目")
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="result_project", verbose_name="审核项目")
is_accept = models.CharField(max_length=10, null=True, choices=ACCEPT_CHOICES, verbose_name="审核项目")
memo = models.CharField(max_length=300, blank=True, default="", verbose_name="审核结果陈述")
create_time = models.DateTimeField(auto_now_add=True)
... ...
... ... @@ -3,7 +3,7 @@
# @Author vanwhebin
from rest_framework import serializers
from .models import Auditor, Project
from .models import Auditor, Project, Result
from usercenter.serializers import UserSerializer
... ... @@ -16,9 +16,18 @@ class AuditorSerializer(serializers.ModelSerializer):
depth = 1
class ProgramSerializer(serializers.ModelSerializer):
class ProjectSerializer(serializers.ModelSerializer):
creator = serializers.ReadOnlyField(source='project_creator.id')
# teacher = serializers.ReadOnlyField(source='teacher.username') # 外键字段 只读
class Meta:
model = Project
fields = '__all__'
depth = 1
class ResultSerializer(serializers.ModelSerializer):
class Meta:
model = Result
fields = '__all__'
... ...
... ... @@ -2,19 +2,25 @@
# @Time : 2020/9/30 11:18
# @Author vanwhebin
from django.urls import re_path, include
from django.urls import path, include
from rest_framework import routers
from .views import AuditorViewSet, ProgramViewSet, AuditorListView
from .views import AuditorViewSet, ProgramViewSet, AuditorListView, ResultViewSet
from .views_cbv import CreateProject, ProjectDetail, AuditProjectsList, AuditProject
router = routers.DefaultRouter()
router.register(r'auditor', AuditorViewSet, basename="auditor")
router.register(r'program', ProgramViewSet, basename="program")
router.register(r'result', ResultViewSet, basename="result")
app_name = "project"
urlpatterns = [
re_path('', include(router.urls)),
path('', include(router.urls)),
# re_path(r'auditor', AuditorListView.as_view()),
# re_path(r'auditor/<int:pk>', AuditorListView.as_view()),
path('create', CreateProject.as_view(), name="create_project"),
path('audit/<int:pk>', AuditProject.as_view(), name="audit_project"),
path('list', AuditProjectsList.as_view(), name="list_projects"),
path('<int:pk>', ProjectDetail.as_view(), name="retrieve_project"),
]
... ...
... ... @@ -2,8 +2,8 @@
# @Time : 2020/9/30 11:02
# @Author vanwhebin
from rest_framework import viewsets, generics, views
from .models import Auditor, Project
from .serializers import AuditorSerializer, ProgramSerializer
from .models import Auditor, Project, Result
from .serializers import AuditorSerializer, ProjectSerializer, ResultSerializer
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
... ... @@ -35,6 +35,11 @@ class AuditorDetailView(generics.RetrieveDestroyAPIView):
class ProgramViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProgramSerializer
serializer_class = ProjectSerializer
permission_classes = (IsAuthenticated, )
class ResultViewSet(viewsets.ModelViewSet):
queryset = Result.objects.all()
serializer_class = ResultSerializer
permission_classes = (IsAuthenticated, )
... ...
# _*_ coding: utf-8 _*_
# @Time : 2020/10/7 10:40
# @Author vanwhebin
from rest_framework.generics import CreateAPIView, RetrieveAPIView, UpdateAPIView, ListAPIView
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.exceptions import ValidationError
from django.urls import reverse
from rest_framework.response import Response
from .serializers import ProjectSerializer
from .models import Auditor, Project, Result
from utils.helpers import WxPushHelper
from utils.pagination import MyPageNumberPagination
class CreateProject(CreateAPIView):
serializer_class = ProjectSerializer
permission_classes = (IsAuthenticated,)
def post(self, request, *args, **kwargs):
super(CreateProject, self).__init__()
wx_client = WxPushHelper()
serializer = ProjectSerializer(data=request.data)
if not serializer.is_valid():
raise ValidationError
else:
serializer.save(creator=request.user, auditor=Auditor.objects.order_by('-order').all())
# 企业微信推送
obj_dict = serializer.data
wx_client.push_card(request.user.wx_token,
reverse("project:retrieve_project", kwargs={"pk": obj_dict['id']}), u"流程创建成功")
return Response(obj_dict)
class ProjectDetail(RetrieveAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
permission_classes = (IsAuthenticated,)
class AuditProject(UpdateAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
permission_classes = (IsAuthenticated, IsAdminUser)
@staticmethod
def _check_audit(project_obj):
# 查看是否已经全部审核完毕 进行更新project表
# 所有的审核人员, 是否有result记录
aud_result_len = Result.objects.filter(project=project_obj).count()
auditor_len = Project.objects.filter(pk=project_obj.id).values('auditor__user_id').count()
return aud_result_len == auditor_len
def partial_update(self, request, *args, **kwargs):
# 对项目进行更新
obj = self.get_object()
accept_choices = (
('accept', '通过'),
('reject', '否决')
)
accept_param = accept_choices[0][0] if request.data.get('is_accept') else accept_choices[1][0]
target = Project.objects.filter(auditor__user_id=request.user.id, pk=obj.id)
if not target:
raise PermissionError
else:
auditor = Auditor.objects.get(user=request.user)
result = Result.objects.filter(auditor=auditor, project=obj).first()
if not result:
Result.objects.create(
auditor=auditor,
project=obj,
is_accept=accept_param,
memo=request.data.get('memo', '')
)
else:
result.is_accept = accept_param
result.save()
wx_client = WxPushHelper()
full_audit_done = self._check_audit(obj)
desc = "产品立项流程所有审批已完成" if full_audit_done else f"{request.user.username}已审批完成"
if full_audit_done:
obj.is_done = True
obj.is_pass = request.data.get('is_accept')
else:
if not accept_param:
obj.is_done = True
obj.is_pass = False
obj.save()
wx_client.push_card(obj.creator.wx_token, reverse("project:retrieve_project", kwargs={"pk": obj.id}), desc)
return Response(ProjectSerializer(obj).data)
class AuditProjectsList(ListAPIView):
serializer_class = ProjectSerializer
pagination_class = MyPageNumberPagination
permission_classes = (IsAuthenticated, IsAdminUser)
def get_queryset(self, **kwargs):
return Project.objects.filter(auditor__user_id=kwargs['user'].id)
def get(self, request, *args, **kwargs):
qs = self.get_queryset(user=request.user)
return Response(ProjectSerializer(qs, many=True).data)
... ...
... ... @@ -21,8 +21,12 @@ class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', 'is_superuser', 'password')
extra_kwargs = {"password": {"write_only": True}}
fields = ('username', 'email', 'is_superuser', 'password', 'wx_token', 'dd_token')
extra_kwargs = {
"password": {"write_only": True},
# "wx_token": {"write_only": True},
"dd_token": {"write_only": True}
}
def create(self, validated_data):
user = User(
... ...
... ... @@ -2,7 +2,7 @@
# @Time : 2020/9/8 14:27
# @Author vanwhebin
from django.urls import re_path, include
from django.urls import path, include
from rest_framework import routers
from .views import UserViewSet, GroupViewSet, PermissionViewSet
... ... @@ -15,5 +15,5 @@ router.register(r'permission', PermissionViewSet, basename="permission")
app_name = "usercenter"
urlpatterns = [
re_path('', include(router.urls)),
path('', include(router.urls)),
]
... ...
... ... @@ -36,3 +36,5 @@ class PermissionViewSet(viewsets.ModelViewSet):
serializer_class = PermissionSerializer
permission_classes = (IsAdminUser,)
... ...
# _*_ coding: utf-8 _*_
# @Time : 2020/10/7 11:26
# @Author vanwhebin
from django.views.generic import View
from django.core.exceptions import PermissionDenied
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.serializers import TokenObtainSerializer, TokenObtainPairSerializer
from libs.qywx.conf import Conf
from libs.qywx.CorpApi import CorpApi, CORP_API_TYPE
from usercenter.models import UserManager, User
from utils.util import response
class WxRequiredMixin(View):
""" 验证是否企业微信登录成功;"""
def dispatch(self, request, *args, **kwargs):
# 状态和文章实例有user属性
if self.get_object().user.username != self.request.user.username:
raise PermissionDenied
return super(WxRequiredMixin, self).dispatch(request, *args, **kwargs)
class WxPushHelper:
api = None
def __init__(self):
self.api = CorpApi(Conf['CORP_ID'], Conf['APP_SECRET'])
def get_corp_user_id_by_code(self, code):
return self.api.httpCall(CORP_API_TYPE['GET_USER_INFO_BY_CODE'], code)
def get_user_info_by_corp_user_id(self, corp_user_id):
return self.api.httpCall(CORP_API_TYPE['USER_GET'], corp_user_id)
def push_text(self, user_wx_id, content):
data = {
"agentid": Conf['APP_ID'], # 企业应用ID
"msgtype": 'text', # 消息类型为文本
"touser": user_wx_id, # 接受消息的对象
"text": {
"content": content # 消息文本
}
}
return self.api.httpCall(CORP_API_TYPE['MESSAGE_SEND'], data)
def push_card(self, user_wx_id, url, description):
data = {
"touser": str(user_wx_id),
"msgtype": "textcard",
"agentid": Conf['APP_ID'], # 企业应用ID
"textcard": {
"title": "产品立项流程通知",
"description": description,
"url": url,
"btntxt": "点击查看"
},
"enable_id_trans": 0,
"enable_duplicate_check": 0,
"duplicate_check_interval": 1800
}
return self.api.httpCall(CORP_API_TYPE['MESSAGE_SEND'], data)
class WxUserlogin(APIView):
allowed_methods = ['post']
permission_classes = (AllowAny, )
@staticmethod
def _get_user_info(code):
client = WxPushHelper()
corp_user = client.get_corp_user_id_by_code(code)
if "errcode" not in corp_user and "UserId" in corp_user:
return client.get_user_info_by_corp_user_id(corp_user['UserId'])
else:
raise RuntimeError(u"获取企业微信用户信息错误")
def post(self, request, *args, **kwargs):
code = request.data.get('code', '').strip()
if not code:
raise PermissionDenied
else:
user = User.objects.filter(wx_token=code).first()
if not user:
info = self._get_user_info(code)
user = UserManager.create_user(
info['name'], "123456", info['email'], {"wx_token": info['userid']})
# 获取token
token_obj = TokenObtainPairSerializer.get_token(user)
return response({"access_token": str(token_obj.access_token)})
... ...
... ... @@ -10,13 +10,12 @@ from rest_framework.generics import CreateAPIView
from usercenter.serializers import MediaSerializer
from usercenter.models import Media
from wxProject.settings import MEDIA_ROOT, UPLOAD_MEDIA_CHOICES
from utils.util import uuid, get_md5_hash
from utils.util import uuid, get_md5_hash, response as res
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework import response, status
class UploadMedia(CreateAPIView):
# queryset = Media
serializer_class = MediaSerializer
permission_classes = (IsAuthenticated, )
... ... @@ -53,7 +52,8 @@ class UploadMedia(CreateAPIView):
extension=uploaded_file_ext
)
os.remove(os.path.join(MEDIA_ROOT, file_path))
return response.Response(MediaSerializer(file, context={'request': request}).data)
# return response.Response(MediaSerializer(file, context={'request': request}).data)
return res(MediaSerializer(file, context={'request': request}).data)
@staticmethod
def allowed_extension(ext):
... ...
... ... @@ -7,5 +7,5 @@ from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
page_size = 10
page_size_query_param = "size"
page_size_query_param = "num"
page_query_param = "page"
... ...
... ... @@ -106,7 +106,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
... ... @@ -129,9 +129,9 @@ RUNTIME_DIR = os.path.join(BASE_DIR, 'runtime')
BASE_LOG_DIR = os.path.join(RUNTIME_DIR, "log")
CORS_ORIGIN_WHITELIST = (
'http://127.0.0.1:8000',
'http://127.0.0.1:8080',
'http://project.tacklifetools.com',
'http://localhost:8000', # 凡是出现在白名单中的域名,都可以访问后端接口
'http://localhost:8080', # 凡是出现在白名单中的域名,都可以访问后端接口
)
... ... @@ -153,6 +153,7 @@ REST_FRAMEWORK = {
'rest_framework.renderers.BrowsableAPIRenderer',
# 'rest_framework_csv.renderers.CSVRenderer',
),
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S"
}
... ...
"""wxProject URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
# _*_ coding: utf-8 _*_
# @Time : 2020/9/30 11:18
# @Author vanwhebin
from django.urls import path, re_path, include
from django.contrib import admin
from django.conf.urls.static import static
from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView
from usercenter.views import MyTokenObtainPairView
from utils.media import UploadMedia
from utils.helpers import WxUserlogin
from wxProject.settings import MEDIA_URL, MEDIA_ROOT
... ... @@ -32,4 +21,5 @@ urlpatterns = [
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
re_path(r'api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
re_path(r'api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
re_path(r'api/login/', WxUserlogin.as_view(), name='wx_login'),
] + static(MEDIA_URL, document_root=MEDIA_ROOT)
... ...