Commit 7032fb40 authored by Davve's avatar Davve

增加获取和上下线接口

parent 9e484353
......@@ -3,4 +3,35 @@
# __author__ = "chenwei"
# Date: 2018/11/15
from utils.base import APIView
class PickListView(APIView):
def get(self, request):
page = request.GET.get('page', 1)
limit = request.GET.get('limit', 10)
try:
data = self.rpc['venus/community/pick/get'](offset=page, limit=limit).unwrap()
except Exception as e:
raise e
print(data)
return data
def post(self, request):
pass
class UpdateOrCreateView(APIView):
def get(self, request):
pass
def post(self, request):
ids = request.POST.get('ids', '').split()
type = request.POST.get('type', '')
try:
self.rpc['venus/community/pick/offline_pick'](type=type, ids=ids).unwrap()
except Exception as e:
raise e
return {
"message": "更新成功"
}
\ No newline at end of file
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "chenwei"
# Date: 2018/11/15
from django.conf.urls import url
from .pick import *
urlpatterns = [
# pick相关
url(r'pick/list$', PickListView.as_view()),
url(r'pick/update_or_create', UpdateOrCreateView.as_view()),
]
\ No newline at end of file
......@@ -4,3 +4,54 @@
# Date: 2018/11/15
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
import helios.rpc
from helios.rpc.object_mapping import SimpleMapper
import gm_logging.django.middleware
from django.conf import settings
from utils.logger import auth_logger, log_error
switch_key = lambda s, d: "SESSION_SWITCH:{}::{}".format(s, d)
rpc_invoker = helios.rpc.create_default_invoker(debug=settings.DEBUG).with_config(dump_curl=settings.DEBUG)
simple_mapper = SimpleMapper()
rpc_mapper = rpc_invoker.with_config(object_mapper=simple_mapper)
class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
class RPC(object):
def __init__(self, session, request):
self.rpc_invoker = rpc_invoker.with_config(
session_key=session,
client_info=gm_logging.django.middleware.get_client_info_of_request(request),
)
self.origin = self.rpc_invoker
self.mapper = self.rpc_invoker.with_config(object_mapper=simple_mapper)
class RPCSetupMiddleware(MiddlewareMixin):
def process_request(self, request):
ori_session = request.COOKIES.get(settings.USER_COOKIE_NAME, '')
request.login_session = ori_session
rpc = RPC(request=request, session=ori_session)
request.rpc = rpc
return None
......@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
from .settings_local import *
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
......@@ -36,15 +37,17 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'middleware.rpc.RPCSetupMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
......@@ -73,12 +76,12 @@ WSGI_APPLICATION = 'sun.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
# Password validation
......@@ -118,3 +121,13 @@ USE_TZ = True
# https://docs.djangoproject.com/en/1.10/howto/static-files/
STATIC_URL = '/static/'
PAGE_SIZE = 10
USER_COOKIE_NAME = 'sun_session_key'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'vu', 'dist', 'static'),
]
......@@ -3,4 +3,3 @@
# __author__ = "chenwei"
# Date: 2018/11/15
......@@ -13,9 +13,10 @@ Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.conf.urls import url, include
# from django.contrib import admin
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^api/', include('api.urls')),
]
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# Author : RobertDing
# E-mail : robertdingx@gmail.com
# Date : 16/03/25 13:15:41
# Desc : view 等 基类
#
from functools import wraps
from distutils.version import LooseVersion
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.views.generic import View
from django.views.decorators.csrf import ensure_csrf_cookie
from django.shortcuts import render
from django.http import JsonResponse
from django.http.response import HttpResponseBase
from helios.rpc.exceptions import RPCFaultException
from gm_types.gaia import PLATFORM_CHOICES
from gm_types.ascle.error import ERROR
from utils.exception import SunException
from utils.user_util import require_login
from utils.logger import log_error
_EMPTY_OBJECT = object()
class LazyAttrDict(object):
def __init__(self, query_dict, configs, request_version):
self._configs = configs
self._query_dict = query_dict
self.request_version = self.request_version
def __getattr__(self, attr):
config = self._configs.get(attr)
if config is None: # 若是没有写config直接返回值
return self._query_dict.get(attr)
default = config.get('default', _EMPTY_OBJECT)
raw = self._query_dict.get(attr, default)
if raw is _EMPTY_OBJECT:
if self.request_version: # 目前使用参数version来判断请求来源
version_add = config.get('version_add', '0.0.1')
if LooseVersion(self.request_version) >= LooseVersion(version_add):
version_deprecated = config.get('version_deprecated')
if not version_deprecated or LooseVersion(self.request_version) < LooseVersion(version_deprecated):
raise SunException(ERROR.ARG_MISS, '缺少参数: %s' % attr)
else:
raise SunException(ERROR.ARG_MISS, '缺少参数: %s' % attr)
if raw is default:
return default
else:
try:
access = config.get('access', str)
if access == bool: # 处理Ajax传Boolean
if raw == 1 or raw == '1' or raw.lower() == 'true':
raw = True
else:
raw = False
else:
raw = access(raw)
except:
raise SunException(ERROR.ARG_ERROR, '错误参数: %s' % attr)
# 因为有'access': lambda s: s.strip()类似的情况,需要在转换之后去判断blank
if raw == '' and not config.get("blank", True): # 提取出来的参数不允许为''
raise SunException(ERROR.ARG_ERROR, '参数不能为空: %s' % attr)
return raw
def get(self, k):
return self.__getattr__(k)
def to_dict(self, exclude=[]):
"""
只转化 config 中配置的参数, 方便用于表单提交
@param exclude: 为排出不做提交的参数
"""
p = {}
for k in self._configs.keys():
if k not in exclude:
p[k] = getattr(self, k)
return p
def __len__(self):
return len(self._query_dict)
def __iter__(self):
return iter(self._query_dict)
def handler_exception(fn):
@wraps(fn)
def _wrapper(view, request, *args, **kwargs):
try:
response = fn(view, request, *args, **kwargs)
except Exception as exception:
if settings.DEBUG and not isinstance(exception, (RPCFaultException, SunException)):
log_error()
raise
if isinstance(exception, RPCFaultException):
if exception.error == 401: # gaia返回的需要登录重新处理
return require_login(request, origin='RPCFaultException_401')
elif exception.error == 404:
log_error()
exception = SunException(exception.error, exception.message)
elif isinstance(exception, SunException):
pass
else:
# 服务器错误
# 参照 http://git.gengmei.cc/backend/gm-types/blob/master/gm_types/ascle/error.py
exception = SunException(ERROR.ASCLE_ERROR)
log_error()
data = {
'data': None,
'error': exception.code,
'message': exception.message,
}
response = JsonResponse(data)
return response
return _wrapper
class APIView(View):
"""
API 基类
- 如果方法返回的数据是 HttpResponseBase 的子类则不做 JsonResponse 处理
处理 GET 与 POST 参数
要求子类显示声明要求的参数, 格式如下
```
args_GET: {
name: {
'access': int, # 处理参数并返回结果
'default': 0 # 不写default,则表示参数不能为None
'version_add': '2.8.0' # client 请求参数添加版本, 默认是APP兼容最低版本
'version_deprecated': '2.9.0' # client请求参数废弃版本,默认为None
'blank' : True, # 默认为True, 代表允许从request解析到参数允许为''或者' '
}
}
* version_add 和 version_deprecated 需要根据请求参数version去确认是否必须
args_POST: ...
```
"""
args_GET = {}
args_POST = {}
decorators = []
template = None # 返回html页面
# 兼容起始版本,方法名后缀,低于版本号执行此方法, 从小到大排序
"""
1) 若compatible_versions = ['2.4.0', '2.8.0', ]
请求参数version<'2.4.0', 执行 get_240 / render_get_240
请求参数'2.4.0'<=version<'2.8.0', 执行 get_270 / render_get_270
请求参数version>='2.8.0', 执行 get / render_get
2) 其他method(post/delete)方法和get方法同理
3) 可参考示例:api/client/account.py ClientLoginView
"""
compatible_versions = []
@classmethod
def as_view(cls, enable_csrf=True, **initkwargs):
view = super().as_view(**initkwargs)
if ensure_csrf_cookie not in cls.decorators:
cls.decorators.insert(0, ensure_csrf_cookie)
# 客户端需要添加忽略csrftoken的校验
if not enable_csrf and csrf_exempt not in cls.decorators:
cls.decorators.insert(0, csrf_exempt)
for deco in cls.decorators[::-1]:
view = deco(view)
return view
def prepare(self, request, *args, **kwargs):
self.request = request
# 请求是否来自客户端, client js请求也会带上version的
self.request_version = request.GET.get('version')
self.request_from_client = False
if self.request_version:
self.request_from_client = True
self.args_default = ClientDefaultArgs(request.GET)
if self.request_from_client:
# 判断是否是从hybrid js请求的
self.args_default.hybrid = request.GET.get('hybrid') == 'true'
self.args_get = LazyAttrDict(request.GET, self.args_GET, self.request_version)
self.args_post = LazyAttrDict(request.POST, self.args_POST, self.request_version)
self.rpc = request.rpc.origin
if getattr(request, 'doctor_user', None) is not None:
self.doctor = request.doctor_user
@handler_exception
def dispatch(self, request, *args, **kwargs):
self.prepare(request, *args, **kwargs)
version = None
method_name = request.method.lower()
if method_name in self.http_method_names:
handler = getattr(self, method_name, None)
if self.request_from_client:
# 若是客户端请求,做兼容处理,带上版本号
version = self.args_default.get_min_verion(self.compatible_versions)
if version:
version = version.replace('.', '')
client_handler = getattr(self, '{}_{}'.format(method_name, version), None)
if callable(client_handler):
handler = client_handler
if callable(handler):
data = handler(request, *args, **kwargs)
else:
raise SunException(ERROR.HTTP_METHOD_NOT_ALLOW)
else:
raise SunException(ERROR.HTTP_METHOD_NOT_ALLOW)
if isinstance(data, HttpResponseBase):
return data
# 找是否有组织数据的函数
render_name = 'render_{}'.format(method_name)
render_method = getattr(self, render_name, None)
if version:
# 若是客户端请求有兼容版本
client_render = getattr(self, '{}_{}'.format(render_name, version), None)
if callable(client_render):
render_method = client_render
# 执行组织数据函数
if callable(render_method):
if data is not None:
data = render_method(data, *args, **kwargs)
else:
data = render_method(*args, **kwargs)
if isinstance(data, HttpResponseBase):
return data
elif self.template is not None:
if settings.DEBUG and self.args_get.debug:
return JsonResponse(data, safe=False)
else:
return render(request, self.template, data)
else:
return JsonResponse(self.write_success(data))
def write_fail(self, code, message):
response = {
'error': code,
'data': None,
'message': message
}
return response
def write_success(self, data, message=""):
response = {
'error': 0,
'data': data,
'message': message,
}
return response
def write_response(self, data, message):
""" 需要自定义message的使用这个函数, 只允许在render_{method}方法中调用
"""
return JsonResponse(self.write_success(data, message))
def write_404(self):
return render(self.request, 'client/404.jinja.html')
def start_num(self, page_size=settings.PAGE_SIZE):
"""
获取 列表的start_num 为了兼容客户端
"""
# TODO Deprecated 过几个版本之后可以去掉,等app都改过来
start_num = int(self.request.GET.get('start_num', 0))
page = int(self.request.GET.get('page', 0))
if page > 0:
start_num = (page - 1) * page_size
return start_num
class ClientDefaultArgs(LazyAttrDict):
"""
客户端每个接口默认传递的参数
"""
args_CLIENT = {
'app_name': {}, # gengmei_doctor
'version': {}, # 应用版本
'channel': {'default': None}, # 去掉,eg: benzhan / AppStore
'lng': {'access': float, 'default': 0}, # 经度
'lat': {'access': float, 'default': 0}, # 纬度
'platform': {}, # choices in [android / iPhone]
'os_version': {'default': None}, # 系统版本
'model': {'default': None}, # 移动设备型号
'screen': {'default': None}, # 屏幕分辨率
'device_id': {'default': None}, # 设备唯一标识
'class_name': {'default': None}, # 仅Android:Application类名
'idfa': {'default': None}, # 仅iOS:广告唯一标识
'idfv': {'default': None}, # 仅iOS,iOS10之后idfa可以关掉
}
def __init__(self, query_dict):
super(ClientDefaultArgs, self).__init__(query_dict, self.args_CLIENT, None)
@property
def is_android(self):
return self.platform == PLATFORM_CHOICES.ANDROID
def get_min_verion(self, versions):
"""
获取兼容到最小版本的版本号
"""
version = None
versions.sort()
for v in versions:
if LooseVersion(self.version) < LooseVersion(v):
version = v
break
return version
def get_device_id(self):
device_id = self.device_id or self.idfv or self.idfa
return device_id
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# Author : RobertDing
# E-mail : robertdingx@gmail.com
# Date : 16/03/25 11:55:13
# Desc : 异常类
#
from __future__ import absolute_import, division, with_statement
from gm_types.ascle.error import ERROR
from gm_types.artemis.error import ERROR as ARTEMIS_ERROR
from gm_types.error import ERROR as GAIA_ERROR
class SunException(Exception):
"""
医生后台普通错误
"""
def __init__(self, code=0, message=None, data=None):
self.code = code
self.message = message or ERROR.getDesc(code) or GAIA_ERROR.getDesc(code) or ARTEMIS_ERROR.getDesc(code)
super().__init__(self.code, self.message)
# -*- coding: UTF-8 -*-
import logging
import traceback
from django.conf import settings
from raven.contrib.django.raven_compat.models import client
info_logger = logging.getLogger('info_logger')
error_logger = logging.getLogger('error_logger')
auth_logger = logging.getLogger('auth_logger')
profile_logger = logging.getLogger("profile_logger")
pay_logger = logging.getLogger("pay_logger")
def log_error():
msg = traceback.format_exc()
if settings.DEBUG:
info_logger.debug(msg)
error_logger.error(traceback.format_exc())
client.captureException()
#!/usr/bin/env python
# coding=utf-8
import re
import datetime
import json
from django.conf import settings
from django.http import JsonResponse
from django.http.response import HttpResponseBadRequest
from utils.logger import auth_logger
def require_login(request, origin=''):
"""
医生后台和app的返回需要登陆的方式不一样
@param: origin 需要登录发生的位置来源
!!!!! 这个方法只能结合 return 一起使用
"""
if 'api/client' in request.path_info:
# TODO Skyler 这个暂时这么处理,之后需要换种方式处理
login_require = HttpResponseBadRequest(status=403)
else:
login_require = JsonResponse({
'error': 401,
'message': '需要登录',
'data': None,
})
# 打印日志
info_msg = '!ascle {origin} {path} {session} {doctor_id} _dd:{target_uid}'
auth_logger.info(info_msg.format(
origin=origin,
path=request.path,
session=request.COOKIES.get(settings.USER_COOKIE_NAME, '-'),
doctor_id=getattr(request, 'doctor_user', {}).get('doctor', {}).get('id', '-'),
target_uid=request.GET.get('_dd', '-'),
))
return login_require
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment