first commit
This commit is contained in:
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
55
utils/basemodels.py
Normal file
55
utils/basemodels.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
from django.db import models # 基础模型
|
||||
from django.utils.translation import ugettext_lazy as _ # 引入延迟加载方法,只有在视图渲染时该字段才会呈现出翻译值
|
||||
from TimeConvert import TimeConvert as tc
|
||||
|
||||
class CreateUpdateMixin(models.Model):
|
||||
"""模型创建和更新时间戳Mixin"""
|
||||
status = models.BooleanField(_(u'状态'), default=True, help_text=u'状态', db_index=True) # 状态值, True和False
|
||||
created_at = models.DateTimeField(_(u'创建时间'), auto_now_add=True, editable=True, help_text=_(u'创建时间')) # 创建时间
|
||||
updated_at = models.DateTimeField(_(u'更新时间'), auto_now=True, editable=True, help_text=_(u'更新时间')) # 更新时间
|
||||
|
||||
class Meta:
|
||||
abstract = True # 抽象类,只用作继承用,不会生成表
|
||||
|
||||
|
||||
class MediaMixin(models.Model):
|
||||
# 图片
|
||||
image_url = models.CharField(_(u'图片链接'), max_length=255, blank=True, null=True, help_text=u'题目图片')
|
||||
# 音频
|
||||
audio_url = models.CharField(_(u'音频链接'), max_length=255, blank=True, null=True, help_text=u'题目音频')
|
||||
audio_time = models.IntegerField(_(u'音频时长'), default=0, help_text=u'题目音频时长')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
return [
|
||||
{
|
||||
'type': 'image',
|
||||
'image_url': self.image_url,
|
||||
},
|
||||
{
|
||||
'type': 'audio',
|
||||
'audio_url': self.audio_url,
|
||||
'audio_time': self.audio_time,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class ModelHelper(object):
|
||||
def upload_path(self, instance, filename):
|
||||
# file will be uploaded to MEDIA_ROOT/file/<ym>/<stamp>.<ext>
|
||||
return '{ym}/{stamp}{ext}'.format(
|
||||
ym=tc.local_string(format='%Y%m'),
|
||||
stamp=tc.local_timestamp(ms=True),
|
||||
ext=os.path.splitext(filename)[1].lower(),
|
||||
)
|
||||
|
||||
|
||||
__mh = ModelHelper()
|
||||
upload_path = __mh.upload_path
|
||||
63
utils/check_utils.py
Normal file
63
utils/check_utils.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from competition.models import ChoiceInfo, FillInBlankInfo
|
||||
|
||||
|
||||
def check_correct_num(rlist):
|
||||
if isinstance(rlist, list):
|
||||
total = len(rlist)
|
||||
correct = 0
|
||||
correct_list = []
|
||||
wrong_list = []
|
||||
for i in rlist:
|
||||
if isinstance(i, str):
|
||||
i = i.split(',')
|
||||
t = i[0][0] # qtype
|
||||
p = i[0][2:] # pk
|
||||
v = i[1] # answer
|
||||
|
||||
# 转换类型
|
||||
try:
|
||||
pk = int(p)
|
||||
except ValueError:
|
||||
continue
|
||||
# 判断题目类型
|
||||
if t == 'c':
|
||||
try:
|
||||
c = ChoiceInfo.objects.get(pk=pk)
|
||||
except ChoiceInfo.DoesNotExist:
|
||||
continue
|
||||
|
||||
if "A" in v:
|
||||
if str(c.item1) == str(c.answer):
|
||||
correct += 1
|
||||
correct_list.append(i)
|
||||
elif "B" in v:
|
||||
if str(c.item2) == str(c.answer):
|
||||
correct_list.append(i)
|
||||
correct += 1
|
||||
elif "C" in v:
|
||||
if str(c.item3) == str(c.answer):
|
||||
correct += 1
|
||||
correct_list.append(i)
|
||||
elif "D" in v:
|
||||
if str(c.item4) == str(c.answer):
|
||||
correct += 1
|
||||
correct_list.append(i)
|
||||
else:
|
||||
wrong_list.append(i)
|
||||
|
||||
if t == 'f':
|
||||
try:
|
||||
f = FillInBlankInfo.objects.get(pk=pk)
|
||||
except FillInBlankInfo.DoesNotExist:
|
||||
continue
|
||||
|
||||
if v.strip() == f.answer:
|
||||
correct += 1
|
||||
|
||||
wrong = total - correct
|
||||
|
||||
return total, correct, wrong, correct_list, wrong_list
|
||||
|
||||
return 0, 0, 0, [], []
|
||||
109
utils/codegen.py
Normal file
109
utils/codegen.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
||||
picture code generator: reference from web blog;
|
||||
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
||||
|
||||
|
||||
class CodeGen(object):
|
||||
def __init__(self, text_str=None, size=None, background=None):
|
||||
"""
|
||||
text_str: 验证码显示的字符组成的字符串
|
||||
size: 图片大小
|
||||
background: 背景颜色
|
||||
"""
|
||||
|
||||
self.text_str = text_str or '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
self.size = size or (150, 50)
|
||||
self.background = background or 'white'
|
||||
self.text_list = list(self.text_str)
|
||||
|
||||
def create_pic(self):
|
||||
"""
|
||||
创建一张图片
|
||||
"""
|
||||
|
||||
self.width, self.height = self.size
|
||||
self.img = Image.new("RGB", self.size, self.background)
|
||||
# 实例化画笔
|
||||
self.draw = ImageDraw.Draw(self.img)
|
||||
|
||||
def create_point(self, num, color):
|
||||
"""
|
||||
num: 画点的数量
|
||||
color: 点的颜色
|
||||
功能:画点
|
||||
"""
|
||||
|
||||
for i in range(num):
|
||||
self.draw.point(
|
||||
(random.randint(0, self.width), random.randint(0, self.height)),
|
||||
fill=color
|
||||
)
|
||||
|
||||
def create_line(self, num, color):
|
||||
"""
|
||||
num: 线条的数量
|
||||
color: 线条的颜色
|
||||
功能:画线条
|
||||
"""
|
||||
|
||||
for i in range(num):
|
||||
self.draw.line(
|
||||
[
|
||||
(random.randint(0, self.width), random.randint(0, self.height)),
|
||||
(random.randint(0, self.width), random.randint(0, self.height))
|
||||
],
|
||||
fill=color
|
||||
)
|
||||
|
||||
def create_text(self, font_type, font_size, font_color, font_num, start_xy):
|
||||
"""
|
||||
font_type: 字体
|
||||
font_size: 文字大小
|
||||
font_color: 文字颜色
|
||||
font_num: 文字数量
|
||||
start_xy: 第一个字左上角坐标,元组类型,如 (5,5)
|
||||
功能: 画文字
|
||||
"""
|
||||
|
||||
font = ImageFont.truetype(font_type, font_size)
|
||||
check = random.sample(self.text_list, font_num)
|
||||
self.draw.text(start_xy, " ".join(check), font=font, fill=font_color)
|
||||
return check
|
||||
|
||||
def opera(self):
|
||||
"""
|
||||
功能:给画出来的线条,文字,扭曲一下,缩放一下,位移一下,滤镜一下。
|
||||
就是让它看起来有点歪,有点扭。
|
||||
"""
|
||||
|
||||
params = [
|
||||
1 - float(random.randint(1, 2)) / 100,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1 - float(random.randint(1, 10)) / 100,
|
||||
float(random.randint(1, 2)) / 500,
|
||||
0.001,
|
||||
float(random.randint(1, 2)) / 500
|
||||
]
|
||||
self.img = self.img.transform(self.size, Image.PERSPECTIVE, params)
|
||||
self.img = self.img.filter(ImageFilter.EDGE_ENHANCE_MORE)
|
||||
|
||||
|
||||
def get_pic_code():
|
||||
__cg = CodeGen()
|
||||
__cg.create_pic()
|
||||
__cg.create_point(500, (220, 220, 220))
|
||||
__cg.create_line(30, (220, 220, 220))
|
||||
__check = __cg.create_text("web/static/font/simsun.ttc", 24, (0, 0, 205), 4, (7, 7))
|
||||
__cg.opera()
|
||||
|
||||
return __cg.img, __check
|
||||
91
utils/decorators.py
Normal file
91
utils/decorators.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from functools import wraps
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.conf import settings
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from competition.models import CompetitionKindInfo
|
||||
from utils.errors import (CompetitionError, CompetitionHasEnded,
|
||||
CompetitionNotFound, CompetitionNotStarted,
|
||||
ProfileError, ProfileNotFound)
|
||||
from utils.response import json_response
|
||||
|
||||
|
||||
def check_login(func=None):
|
||||
@wraps(func)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
uid = request.session.get('uid', '')
|
||||
|
||||
if not uid:
|
||||
if request.path.startswith('/bs'):
|
||||
return render(request, 'err.html', ProfileNotFound)
|
||||
elif request.path.startswith('/api'):
|
||||
return json_response(*ProfileError.ProfileNotFound)
|
||||
|
||||
return func(request, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def check_copstatus(func=None):
|
||||
@wraps(func)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
kind_id = request.GET.get('kind_id', '') or request.POST.get('kind_id', '')
|
||||
|
||||
try:
|
||||
kind_info = CompetitionKindInfo.objects.get(kind_id=kind_id)
|
||||
except CompetitionKindInfo.DoesNotExist:
|
||||
if request.path.startswith('/bs'):
|
||||
return render(request, 'err.html', CompetitionNotFound)
|
||||
if request.path.startswith('/api'):
|
||||
return json_response(*CompetitionError.CompetitionNotFound)
|
||||
|
||||
if kind_info.cop_finishat < datetime.datetime.now(tz=datetime.timezone.utc):
|
||||
if request.path.startswith('/bs'):
|
||||
return render(request, 'err.html', CompetitionHasEnded)
|
||||
if request.path.startswith('/api'):
|
||||
return json_response(*CompetitionError.CompetitionHasEnded)
|
||||
|
||||
if kind_info.cop_startat > datetime.datetime.now(tz=datetime.timezone.utc):
|
||||
if request.path.startswith('/bs'):
|
||||
return render(request, 'err.html', CompetitionNotStarted)
|
||||
if request.path.startswith('/api'):
|
||||
return json_response(*CompetitionError.CompetitionNotStarted)
|
||||
|
||||
return func(request, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
def logerr(func=None):
|
||||
@wraps(func)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
name = func.__name__
|
||||
if not settings.DEBUG:
|
||||
logger = logging.getLogger('file')
|
||||
|
||||
try:
|
||||
logger.debug('func=%s&flag=%s&content=%s', name, 'BODY', request.body)
|
||||
except Exception as e:
|
||||
logger.error('func=%s&flag=%s&content=%s', name, 'ERROR', e)
|
||||
|
||||
if request.method == 'GET':
|
||||
logger.debug('func=%s&flag=%s&content=%s', name, 'GET', request.GET)
|
||||
|
||||
if request.method == 'POST':
|
||||
logger.debug('func=%s&flag=%s&content=%s', name, 'POST', request.POST)
|
||||
response = func(request, *args, **kwargs)
|
||||
try:
|
||||
logger.debug('func=%s&flag=%s&content=%s', name, 'RESPONSE', response.content)
|
||||
except Exception as e:
|
||||
logger.error('func=%s&flag=%s&content=%s', name, 'ERROR', e)
|
||||
|
||||
return response
|
||||
return func(request, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
63
utils/errors.py
Normal file
63
utils/errors.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
"""API ERROR"""
|
||||
|
||||
|
||||
class CompetitionError(object):
|
||||
CompetitionNotFound = (100001, 'Competition Not Found')
|
||||
CompetitionHasEnded = (100002, 'Competition Has Ended')
|
||||
CompetitionNotStarted = (100003, 'Competition Not Started')
|
||||
BankInfoNotFound = (100004, 'Bank Info Not Found')
|
||||
QuestionNotSufficient = (100005, 'Question Not Sufficient')
|
||||
QuestionNotFound = (100006, 'Question Not Found')
|
||||
|
||||
|
||||
class ProfileError(object):
|
||||
ProfileNotFound = (200001, 'Profile Not Found')
|
||||
|
||||
|
||||
class UserError(object):
|
||||
UserNotFound = (300001, 'User Not Found')
|
||||
PasswordError = (300002, 'Password Error')
|
||||
VeriCodeError = (300003, 'Vericode Error')
|
||||
UserHasExists = (300004, 'User Has Exists')
|
||||
UserHasSentEmail = (300005, 'User Has Sent Email')
|
||||
UserSendEmailFailed = (300006, 'User Send Email Failed')
|
||||
|
||||
|
||||
class SetError(object):
|
||||
FileNotFound = (400001, 'File Not Found')
|
||||
FileTypeError = (400002, 'File Type Error')
|
||||
BankTypeError = (400003, 'Bank Type Error')
|
||||
BankInfoNotFound = (400004, 'Bank Info Not Found')
|
||||
|
||||
|
||||
class BizError(object):
|
||||
BizAccountExists = (500001, 'Business Account Exists')
|
||||
BizAccountNotFound = (500002, 'Business Account Not Found')
|
||||
|
||||
|
||||
"""RENDER ERROR"""
|
||||
|
||||
|
||||
def message(errtitle, errmsg):
|
||||
return {"errtitle": errtitle, "errmsg": errmsg}
|
||||
|
||||
|
||||
CompetitionNotFound = message("比赛不存在", "您要找的比赛不存在")
|
||||
BankInfoNotFound = message("题库不存在", "当前比赛未配置题库")
|
||||
QuestionLogNotFound = message("比赛记录不存在", "您的答题记录不存在")
|
||||
CompetitionHasEnded = message("比赛已经结束", "您要参与的比赛已经结束")
|
||||
CompetitionNotStarted = message("比赛尚未开始", "您要参加的比赛还没开始")
|
||||
QuestionNotSufficient = message("题目数量过少", "当前题库题目数量过少")
|
||||
ProfileNotFound = message("用户不存在", "您的账户还没有登录,点击右上角登录吧~")
|
||||
BizAccountNotFound = message("未注册机构", "只有机构账户才能创建活动,先注册成为机构吧~")
|
||||
UserNotFound = message("账户不存在", "我们没有找到这个邮箱的账户,密码重置失败")
|
||||
VeriCodeError = message("校验码错误", "您的校验码出现错误,激活失败")
|
||||
VeriCodeTimeOut = message("校验码超时", "由于您过长时间未校验邮件,导致校验失败")
|
||||
VerifyFailed = message("激活失败", "我们没有找到您的注册信息,激活失败")
|
||||
PasswordResetFailed = message("密码重置失败", "可能因为较长时间未得到确认,您的密码重置失败了!")
|
||||
TemplateNotFound = message("模板不存在", "题库模板没有找到,请联系管理员~")
|
||||
FileNotFound = message("文件不存在", "传入的模板为空")
|
||||
FileTypeError = message("文件类型错误", "传入的文件出错了")
|
||||
52
utils/jsonencoder.py
Normal file
52
utils/jsonencoder.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import decimal
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from django.core.serializers import serialize
|
||||
from django.db import models
|
||||
from django.utils.timezone import is_aware
|
||||
|
||||
|
||||
class JsonEncoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
# for datetime
|
||||
if isinstance(o, datetime.datetime):
|
||||
t = o.isoformat()
|
||||
t = t[:23] + t[26:] if o.microsecond else t[:]
|
||||
t = t[:-6] + 'Z' if t.endswith('+00:00') else t[:]
|
||||
return t
|
||||
|
||||
# for date
|
||||
if isinstance(o, datetime.date):
|
||||
return o.isoformat()
|
||||
|
||||
# for time
|
||||
if isinstance(o, datetime.time):
|
||||
if is_aware(o):
|
||||
raise ValueError("Timezone aware times can't be serialized.")
|
||||
t = o.isoformat()
|
||||
t = t[:12] if o.microsecond else t[:]
|
||||
return t
|
||||
|
||||
# for decimal
|
||||
if isinstance(o, decimal.Decimal):
|
||||
return str(o)
|
||||
|
||||
# for uuid
|
||||
if isinstance(o, uuid.UUID):
|
||||
return str(o)
|
||||
|
||||
# for single model serialize
|
||||
if isinstance(o, models.Model):
|
||||
data = serialize('json', [o])
|
||||
data = data.lstrip('[').rstrip(']')
|
||||
return data
|
||||
|
||||
# for multi model serialize
|
||||
if isinstance(o, models.QuerySet):
|
||||
return serialize('json', o)
|
||||
0
utils/mysql/__init__.py
Normal file
0
utils/mysql/__init__.py
Normal file
55
utils/mysql/connect.py
Normal file
55
utils/mysql/connect.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
||||
MySQL Db Connector:
|
||||
Instantiaze ConnectDb class
|
||||
Parameters for ConnectDB's __init__ function should be a python dict:
|
||||
{
|
||||
'HOST': 'localhost',
|
||||
'USER': 'root',
|
||||
'PASSWORD': '',
|
||||
'NAME': '',
|
||||
}
|
||||
ConnectDb class got two functions: connector,dict_cursor
|
||||
ConnectDbInstance.connector returns a database connector
|
||||
ConnectDbInstance.dict_cursor returns database cursor with DictCursorClass
|
||||
You can use cursor.execute() or cursor.execute_many() to operating database
|
||||
You can use cursor.fetchone() or cursor.fetchall() to get latest queies from database
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import pymysql
|
||||
|
||||
|
||||
def mysql_conf(conf):
|
||||
return {
|
||||
'host': conf.get('HOST', 'localhost'),
|
||||
'user': conf.get('USER', 'root'),
|
||||
'passwd': conf.get('PASSWORD', ''),
|
||||
'database': conf.get('NAME', 'exam'),
|
||||
'port': conf.get('PORT', 3306),
|
||||
}
|
||||
|
||||
|
||||
class ConnectDb(object):
|
||||
def __init__(self, conf={}):
|
||||
self.__conf = conf
|
||||
|
||||
def connector(self):
|
||||
try:
|
||||
conn = pymysql.connect(**mysql_conf(self.__conf))
|
||||
except pymysql.err.OperationalError as e:
|
||||
raise e
|
||||
|
||||
return conn
|
||||
|
||||
def dict_cursor(self):
|
||||
return self.connector().cursor(cursor=pymysql.cursors.DictCursor)
|
||||
|
||||
|
||||
connect = ConnectDb(settings.DATABASES.get('default', {}))
|
||||
connection = connect.connector()
|
||||
dict_cursor = connect.dict_cursor()
|
||||
40
utils/processors.py
Normal file
40
utils/processors.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class ConstExtendIntField(int):
|
||||
def __new__(cls, flag, version=''):
|
||||
obj = int.__new__(cls, flag)
|
||||
obj.version = version
|
||||
return obj
|
||||
|
||||
|
||||
class UserAgentDetectionMiddleware(MiddlewareMixin):
|
||||
|
||||
def process_request(self, request):
|
||||
ua = request.META.get('HTTP_USER_AGENT', '').lower()
|
||||
|
||||
# ####### Device、OS #######
|
||||
# Windows
|
||||
request.Windows = 'windows nt' in ua
|
||||
# Linux
|
||||
request.Linux = 'linux x86_64' in ua
|
||||
# iMac、iPhone、iPad、iPod
|
||||
request.iMac, request.iPhone, request.iPad, request.iPod = 'macintosh' in ua, 'iphone' in ua, 'ipad' in ua, 'ipod' in ua
|
||||
# PC
|
||||
request.PC = request.Windows or request.Linux or request.iMac
|
||||
# iOS
|
||||
request.iOS = request.iPhone or request.iPad or request.iMac or request.iPod
|
||||
# Android and Version
|
||||
adr = re.findall(r'android ([\d.]+)', ua)
|
||||
request.Android = ConstExtendIntField(True, adr[0]) if adr else ConstExtendIntField('android' in ua, '')
|
||||
|
||||
# ####### APP #######
|
||||
# Weixin/Wechat and Version
|
||||
wx = re.findall(r'micromessenger[\s/]([\d.]+)', ua)
|
||||
request.weixin = request.wechat = ConstExtendIntField(True, wx[0]) if wx else ConstExtendIntField(False, '')
|
||||
|
||||
return None
|
||||
0
utils/redis/__init__.py
Normal file
0
utils/redis/__init__.py
Normal file
26
utils/redis/connect.py
Normal file
26
utils/redis/connect.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
||||
Redis Connect function:
|
||||
|
||||
"""
|
||||
|
||||
import redis
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def redis_conf(conf):
|
||||
return {
|
||||
'host': conf.get('HOST', 'localhost'),
|
||||
'port': conf.get('PORT', 6379),
|
||||
'password': '{}:{}'.format(conf.get('USER', ''), conf.get('PASSWORD', '')) if conf.get('USER') else '',
|
||||
'db': conf.get('db', 0),
|
||||
}
|
||||
|
||||
|
||||
def connector(conf):
|
||||
return redis.StrictRedis(connection_pool=redis.ConnectionPool(**redis_conf(conf)))
|
||||
|
||||
|
||||
r = connector(settings.REDIS.get('default', {}))
|
||||
23
utils/redis/constants.py
Normal file
23
utils/redis/constants.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# extensions consts
|
||||
KEY_PREFIX = 'r'
|
||||
|
||||
# rank consts
|
||||
REDIS_RANK = 'x:competition:rank:%s' # 排行榜名次(params: kind_id; value: uid)
|
||||
REDIS_RANK_USER_INFO = 'x:competition:rank:user:info:%s' # 排行榜用户信息(params: kind_id; value: uid; data: profile data)
|
||||
|
||||
|
||||
# profile consts
|
||||
REDIS_USER_INFO = 'x:competition:user:info:%s' # 用户信息(params: uid, value: profile data)
|
||||
USERINFO_HAS_ENTERED = 'x:competition:userinfo:has:entered:%s:%s' # 用户是否已经填写表单(params: kind_id, uid)
|
||||
USER_LOGIN_VCODE = 'x:competition:user:login:vcode:%s' # 用户登录验证码(params: sign, data: value)
|
||||
USER_SIGN_VCODE = 'x:competition:user:sign:vcode:%s' # 用户注册邮箱验证码(params: sign, data: value)
|
||||
USER_PASSWORD_RESET = 'x:competition:user:password:reset:%s' # 用户重置密码记录(params: uid, data: password)
|
||||
USER_HAS_SENT_EMAIL = 'x:competition:has:sent:email:%s' # 用户是否已经发送邮件验证(params: email, data: true, false)
|
||||
USER_HAS_SENT_REGEMAIL = 'x:competition:has:sent:reg:email:%s' # 用户是否已经发送注册验证邮件(params: email, data: true, false)
|
||||
|
||||
|
||||
# page config consts
|
||||
PAGE_CONFIG_INFO = 'x:competition:page:config:info:%s' # 页面配置信息(params: app_id, value: app_data)
|
||||
FORM_REGEX_CONFIG = 'x:competition:form:regex:config' # 表单正则表达式配置(params: field_name, value: config)
|
||||
20
utils/redis/extensions.py
Normal file
20
utils/redis/extensions.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from utils.redis.constants import KEY_PREFIX
|
||||
|
||||
|
||||
# Quote/UnQuote Section
|
||||
def __quote_key(self, name):
|
||||
return '{0}quote:{1}'.format(KEY_PREFIX, name)
|
||||
|
||||
|
||||
def quote(self, s, ex=True, time=1800, short_uuid=False):
|
||||
identifier = self.__uuid(short_uuid)
|
||||
identifier_key = self.__quote_key(identifier)
|
||||
self.setex(identifier_key, time, s) if ex else self.set(identifier_key, s)
|
||||
return identifier
|
||||
|
||||
|
||||
def unquote(self, identifier, buf=False):
|
||||
identifier_key = self.__quote_key(identifier)
|
||||
return self.get(identifier_key) if buf else self.get_delete(identifier_key)[0]
|
||||
44
utils/redis/rpageconfig.py
Normal file
44
utils/redis/rpageconfig.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
from utils.jsonencoder import JsonEncoder
|
||||
from utils.redis.connect import r
|
||||
from utils.redis.constants import PAGE_CONFIG_INFO, FORM_REGEX_CONFIG
|
||||
|
||||
|
||||
def set_pageconfig(data):
|
||||
if isinstance(data, dict):
|
||||
app_id = data.get('app_id', '')
|
||||
key = PAGE_CONFIG_INFO % app_id
|
||||
data = json.dumps(data, cls=JsonEncoder)
|
||||
r.set(key, data)
|
||||
|
||||
|
||||
def get_pageconfig(app_id):
|
||||
key = PAGE_CONFIG_INFO % app_id
|
||||
data = r.get(key).decode('utf-8') if r.get(key) else '{}'
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
def get_pageconfig_json(app_id):
|
||||
key = PAGE_CONFIG_INFO % app_id
|
||||
data = r.get(key).decode('utf-8') if r.get(key) else '{}'
|
||||
return data
|
||||
|
||||
|
||||
def set_form_regex(field, value):
|
||||
key = FORM_REGEX_CONFIG
|
||||
value = json.dumps(value) if isinstance(value, dict) else ''
|
||||
r.hset(key, field, value)
|
||||
|
||||
|
||||
def get_form_regex(field):
|
||||
key = FORM_REGEX_CONFIG
|
||||
value = r.hget(key, field).decode('utf-8') if r.hget(key, field) else '{}'
|
||||
return json.loads(value)
|
||||
|
||||
|
||||
def rem_form_regex(field):
|
||||
key = FORM_REGEX_CONFIG
|
||||
r.hdel(key, field)
|
||||
82
utils/redis/rprofile.py
Normal file
82
utils/redis/rprofile.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
from utils.jsonencoder import JsonEncoder
|
||||
from utils.redis.connect import r
|
||||
from utils.redis.constants import (REDIS_USER_INFO, USER_LOGIN_VCODE, USER_PASSWORD_RESET,
|
||||
USER_SIGN_VCODE, USERINFO_HAS_ENTERED, USER_HAS_SENT_EMAIL,
|
||||
USER_HAS_SENT_REGEMAIL)
|
||||
|
||||
|
||||
def set_profile(data={}):
|
||||
if isinstance(data, dict):
|
||||
uid = data.get('uid', '')
|
||||
key = REDIS_USER_INFO % uid
|
||||
data = json.dumps(data, cls=JsonEncoder)
|
||||
r.set(key, data)
|
||||
|
||||
|
||||
def delete_profile(uid):
|
||||
r.delete(REDIS_USER_INFO % uid)
|
||||
|
||||
|
||||
def get_profile(uid):
|
||||
ret = r.get(REDIS_USER_INFO % uid).decode('utf-8') if r.get(REDIS_USER_INFO % uid) else '{}'
|
||||
return json.loads(ret)
|
||||
|
||||
|
||||
def enter_userinfo(kind_id, uid):
|
||||
key = USERINFO_HAS_ENTERED % (kind_id, uid)
|
||||
r.set(key, 1)
|
||||
|
||||
|
||||
def get_enter_userinfo(kind_id, uid):
|
||||
key = USERINFO_HAS_ENTERED % (kind_id, uid)
|
||||
return r.get(key)
|
||||
|
||||
|
||||
def set_vcode(sign, value):
|
||||
key = USER_LOGIN_VCODE % sign
|
||||
r.setex(key, 180, value)
|
||||
|
||||
|
||||
def get_vcode(sign):
|
||||
key = USER_LOGIN_VCODE % sign
|
||||
return r.get(key)
|
||||
|
||||
|
||||
def set_signcode(sign, value):
|
||||
key = USER_SIGN_VCODE % sign
|
||||
r.setex(key, 1800, value)
|
||||
|
||||
|
||||
def get_signcode(sign):
|
||||
key = USER_SIGN_VCODE % sign
|
||||
return r.get(key)
|
||||
|
||||
|
||||
def set_passwd(sign, passwd):
|
||||
key = USER_PASSWORD_RESET % sign
|
||||
r.setex(key, 1860, passwd)
|
||||
|
||||
|
||||
def get_passwd(sign):
|
||||
key = USER_PASSWORD_RESET % sign
|
||||
return r.get(key)
|
||||
|
||||
|
||||
def set_has_sentemail(email):
|
||||
r.setex(USER_HAS_SENT_EMAIL % email, 1800, 1)
|
||||
|
||||
|
||||
def get_has_sentemail(email):
|
||||
return r.get(USER_HAS_SENT_EMAIL % email)
|
||||
|
||||
|
||||
def set_has_sentregemail(email):
|
||||
r.setex(USER_HAS_SENT_REGEMAIL % email, 60, 1)
|
||||
|
||||
|
||||
def get_has_sentregemail(email):
|
||||
return r.get(USER_HAS_SENT_REGEMAIL % email)
|
||||
46
utils/redis/rrank.py
Normal file
46
utils/redis/rrank.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
from utils.jsonencoder import JsonEncoder
|
||||
from utils.redis.connect import r
|
||||
from utils.redis.constants import REDIS_RANK, REDIS_RANK_USER_INFO
|
||||
from utils.redis.rprofile import get_profile
|
||||
|
||||
|
||||
def add_to_rank(uid, kind_id, score, time):
|
||||
key = REDIS_RANK % kind_id
|
||||
pre_score = int(r.zscore(key, uid)) if r.zscore(key, uid) else 0
|
||||
rank_socre = score * 100000000 + (86400000 - time)
|
||||
|
||||
if pre_score == 0 or (pre_score != 0 and rank_socre > pre_score):
|
||||
r.zadd(key, rank_socre, uid)
|
||||
user_info = get_profile(uid)
|
||||
ret = {
|
||||
'nickname': user_info.get('nickname', ''),
|
||||
'numid': user_info.get('numid', ''),
|
||||
'avatar': user_info.get('avatar', ''),
|
||||
'score': score,
|
||||
'time': time
|
||||
}
|
||||
r.hset(REDIS_RANK_USER_INFO % kind_id, uid, json.dumps(ret, cls=JsonEncoder))
|
||||
|
||||
|
||||
def get_rank(kind_id, uid):
|
||||
key = REDIS_RANK % kind_id
|
||||
rank = r.zrevrank(key, uid)
|
||||
return (int(rank) + 1) if rank is not None else None
|
||||
|
||||
def get_user_rank(kind_id, uid):
|
||||
key = REDIS_RANK_USER_INFO % kind_id
|
||||
ret = r.hget(key, uid)
|
||||
return json.loads(ret.decode('utf-8')) if ret else {}
|
||||
|
||||
|
||||
def get_rank_data(kind_id, start=0, end=-1):
|
||||
ranks = r.zrevrange(REDIS_RANK % kind_id, start, end)
|
||||
if not ranks:
|
||||
return [], []
|
||||
ret = r.hmget(REDIS_RANK_USER_INFO % kind_id, ranks)
|
||||
ret = [json.loads(i.decode('utf-8') if i else '{}') for i in ret]
|
||||
return ranks, ret
|
||||
15
utils/response.py
Normal file
15
utils/response.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.http import JsonResponse
|
||||
|
||||
|
||||
def __response_data(status_code=200, message=None, data={}, **kwargs):
|
||||
return dict({
|
||||
'status': status_code,
|
||||
'message': message,
|
||||
'data': data,
|
||||
}, **kwargs)
|
||||
|
||||
|
||||
def json_response(status_code=200, message=None, data={}, **kwargs):
|
||||
return JsonResponse(__response_data(status_code, message, data, **kwargs), safe=False)
|
||||
35
utils/small_utils.py
Normal file
35
utils/small_utils.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
class IntDict(int):
|
||||
def __new__(cls, value, *args, **kwargs):
|
||||
if isinstance(value, int):
|
||||
return {str(value): value}
|
||||
return
|
||||
|
||||
|
||||
class String(str):
|
||||
"""
|
||||
Initialize a str object or bytes object to str object
|
||||
a = 'I love you'.encode('utf-8') # bytes type
|
||||
b = String(a)
|
||||
print(b) # 'I love you' str type
|
||||
"""
|
||||
|
||||
def __new__(cls, value, *args, **kwargs):
|
||||
if isinstance(value, bytes):
|
||||
return value.decode('utf-8')
|
||||
return value
|
||||
|
||||
|
||||
def get_today_string():
|
||||
now = datetime.datetime.now()
|
||||
return str(now.year) + str(now.month) + str(now.day)
|
||||
|
||||
|
||||
def get_now_string(string=''):
|
||||
now = datetime.datetime.now()
|
||||
str_now = str(now.hour) + str(now.minute) + str(now.second)
|
||||
return str_now + '_' + string if string else str_now
|
||||
46
utils/upload_questions.py
Normal file
46
utils/upload_questions.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import xlrd # xlrd库
|
||||
from django.db import transaction # 数据库事物
|
||||
from competition.models import ChoiceInfo, FillInBlankInfo # 题目数据模型
|
||||
|
||||
def check_vals(val): # 检查值是否被转换成float,如果是,将.0结尾去掉
|
||||
val = str(val)
|
||||
if val.endswith('.0'):
|
||||
val = val[:-2]
|
||||
return val
|
||||
@transaction.atomic
|
||||
def upload_questions(file_path=None, bank_info=None):
|
||||
book = xlrd.open_workbook(file_path) # 读取文件
|
||||
table = book.sheets()[0] # 获取第一张表
|
||||
nrows = table.nrows # 获取行数
|
||||
choice_num = 0 # 选择题数量
|
||||
fillinblank_num = 0 # 填空题数量
|
||||
for i in range(1, nrows):
|
||||
rvalues = table.row_values(i) # 获取行中的值
|
||||
if (not rvalues[0]) or rvalues[0].startswith('说明'): # 取出多余行
|
||||
break
|
||||
if '##' in rvalues[0]: # 选择题
|
||||
FillInBlankInfo.objects.select_for_update().create(
|
||||
bank_id=bank_info.bank_id,
|
||||
question=check_vals(rvalues[0]),
|
||||
answer=check_vals(rvalues[1]),
|
||||
image_url=rvalues[6],
|
||||
source=rvalues[7]
|
||||
)
|
||||
fillinblank_num += 1 # 填空题数加1
|
||||
else: # 填空题
|
||||
ChoiceInfo.objects.select_for_update().create(
|
||||
bank_id=bank_info.bank_id,
|
||||
question=check_vals(rvalues[0]),
|
||||
answer=check_vals(rvalues[1]),
|
||||
item1=check_vals(rvalues[2]),
|
||||
item2=check_vals(rvalues[3]),
|
||||
item3=check_vals(rvalues[4]),
|
||||
item4=check_vals(rvalues[5]),
|
||||
image_url=rvalues[6],
|
||||
source=rvalues[7]
|
||||
)
|
||||
choice_num += 1 # 选择题数加1
|
||||
bank_info.choice_num = choice_num
|
||||
bank_info.fillinblank_num = fillinblank_num
|
||||
bank_info.save()
|
||||
return choice_num, fillinblank_num
|
||||
76
utils/wechat_utils.py
Normal file
76
utils/wechat_utils.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import urllib.request
|
||||
|
||||
import requests
|
||||
|
||||
"""
|
||||
AUTHORIZE_URI: 微信授权链接
|
||||
QRCODE_AUTHORIZE_URI: 二维码授权链接
|
||||
ACCESSTOKEN_URI: 获取access_token链接
|
||||
USERINFO_URI: 获取用户信息链接
|
||||
LOGIN_JSAPI: 登录用JS API
|
||||
"""
|
||||
|
||||
|
||||
API_DOMAIN = 'https://api.weixin.qq.com'
|
||||
OPEN_DOMAIN = 'https://open.weixin.qq.com'
|
||||
MCH_DOMAIN = 'https://api.mch.weixin.qq.com'
|
||||
AUTHORIZE_URI = OPEN_DOMAIN + '/connect/oauth2/authorize?appid={appid}&redirect_uri={redirect_uri}&response_type=code&scope={scope}&state={state}#wechat_redirect'
|
||||
QRCODE_AUTHORIZE_URI = OPEN_DOMAIN + 'https://open.weixin.qq.com/connect/qrconnect?appid={appid}&redirect_uri={redirect_uri}&response_type=code&scope={scope}&state={state}#wechat_redirect'
|
||||
ACCESSTOKEN_URI = API_DOMAIN + '/sns/oauth2/access_token?appid={appid}&secret={secret}&code={code}&grant_type=authorization_code'
|
||||
USERINFO_URI = API_DOMAIN + '/sns/userinfo?access_token={access_token}&openid={openid}'
|
||||
LOGIN_JSAPI = 'http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
|
||||
|
||||
|
||||
class Oauth(object):
|
||||
def __init__(self, authorize_uri=None, accesstoken_uri=None, userinfo_uri=None, qrcode_authorize_uri=None):
|
||||
self.WECHAT_OAUTH2_AUTHORIZE = authorize_uri or AUTHORIZE_URI
|
||||
self.WECHAT_OAUTH2_ACCESS_TOKEN = accesstoken_uri or ACCESSTOKEN_URI
|
||||
self.WECHAT_OAUTH2_USERINFO = userinfo_uri or USERINFO_URI
|
||||
self.WECHAT_QRCODE_AUTHORIZE = qrcode_authorize_uri or QRCODE_AUTHORIZE_URI
|
||||
|
||||
def get(self, url, verify=False, encoding='utf-8', res_to_encoding=True, res_to_json=True, res_processor_func=None, resjson_processor_func=None, **kwargs):
|
||||
# When ``verify=True`` and ``cacert.pem`` not match ``https://xxx.weixin.qq.com``, will raise
|
||||
# SSLError: [Errno 1] _ssl.c:510: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
|
||||
res = requests.get(url.format(**kwargs), verify=verify)
|
||||
if res_to_encoding:
|
||||
res.encoding = encoding
|
||||
if res_processor_func:
|
||||
return res_processor_func(res)
|
||||
if not res_to_json:
|
||||
return res
|
||||
resjson = res.json()
|
||||
return resjson_processor_func(resjson) if resjson_processor_func else resjson
|
||||
|
||||
def get_oauth_code_url(self, appid=None, redirect_uri=None, scope='snsapi_base', redirect_url=None):
|
||||
return self.WECHAT_OAUTH2_AUTHORIZE.format(
|
||||
appid=appid,
|
||||
redirect_uri=urllib.request.quote(redirect_uri),
|
||||
scope=scope,
|
||||
state=urllib.request.quote(redirect_url)
|
||||
)
|
||||
|
||||
def get_access_info(self, appid=None, secret=None, code=None):
|
||||
return self.get(self.WECHAT_OAUTH2_ACCESS_TOKEN, appid=appid, secret=secret, code=code)
|
||||
|
||||
def get_userinfo(self, access_token=None, openid=None):
|
||||
return self.get(self.WECHAT_OAUTH2_USERINFO, access_token=access_token, openid=openid)
|
||||
|
||||
def get_oauth_redirect_url(self, oauth_uri, scope='snsapi_base', redirect_url=None, default_url=None, direct_redirect=None):
|
||||
"""
|
||||
# https://a.com/wx/oauth2?redirect_url=redirect_url
|
||||
# https://a.com/wx/oauth2?redirect_url=redirect_url&default_url=default_url
|
||||
# https://a.com/wx/oauth2?scope=snsapi_base&redirect_url=redirect_url
|
||||
# https://a.com/wx/oauth2?scope=snsapi_base&redirect_url=redirect_url&default_url=default_url
|
||||
# https://a.com/wx/oauth2?scope=snsapi_base&redirect_url=redirect_url&default_url=default_url&direct_redirect=true
|
||||
"""
|
||||
oauth_url = oauth_uri.format(scope, urllib.request.quote(redirect_url), urllib.request.quote(default_url)) if default_url else oauth_uri.format(scope, urllib.request.quote(redirect_url))
|
||||
return '{0}&direct_redirect=true'.format(oauth_url) if direct_redirect else oauth_url
|
||||
|
||||
|
||||
oauth = Oauth()
|
||||
get_oauth_code_url = oauth.get_oauth_code_url
|
||||
get_access_info = oauth.get_access_info
|
||||
get_userinfo = oauth.get_userinfo
|
||||
get_oauth_redirect_url = oauth.get_oauth_redirect_url
|
||||
Reference in New Issue
Block a user