class Group(db.Document): """ 管理组 """ name = db.StringField(verbose_name='组名') power = db.ListField(db.ReferenceField('View'), verbose_name='使用权限') can_create = db.ListField(db.ReferenceField('View'), verbose_name='创建权限') can_edit = db.ListField(db.ReferenceField('View'), verbose_name='编辑权限') can_delete = db.ListField(db.ReferenceField('View'), verbose_name='删除权限') modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') def __unicode__(self): return self.name @cached_property def power_list(self): return [x.name for x in self.power] @cached_property def can_create_list(self): return [x.name for x in self.can_create] @cached_property def can_edit_list(self): return [x.name for x in self.can_edit] @cached_property def can_delete_list(self): return [x.name for x in self.can_delete]
class User(db.Document, UserMixin): """ 用户模型 """ MENU_ICON = 'user' SEX_UNKNOWN = 'unknown' SEX_MALE = 'male' SEX_FEMALE = 'female' SEX_CHOICES = ( (SEX_UNKNOWN, '保密'), (SEX_MALE, '男'), (SEX_FEMALE, '女'), ) SEX_VALUES = [x[0] for x in SEX_CHOICES] SEX_DICT = dict(SEX_CHOICES) SEX_FROM_WECHAT = {0: SEX_UNKNOWN, 1: SEX_MALE, 2: SEX_FEMALE} id = db.IntField(primary_key=True, verbose_name='ID') phone = db.StringField(max_length=20, verbose_name='手机') email = db.StringField(max_length=40, verbose_name='邮箱') password = db.StringField(max_length=40, verbose_name='密码') nickname = db.StringField(max_length=40, verbose_name='昵称') avatar = db.XImageField(verbose_name='头像') birthday = db.DateTimeField(verbose_name='生日') sex = db.StringField(default=SEX_UNKNOWN, choices=SEX_CHOICES, verbose_name='性别') location = db.AreaField(verbose_name='所在地') address = db.StringField(max_length=100, verbose_name='通讯地址') resume = db.StringField(max_length=100, verbose_name='简介') debug = db.BooleanField(default=False, verbose_name='允许调试') active = db.BooleanField(default=True, verbose_name='激活') inviter = db.ReferenceField('User', verbose_name='邀请者') inviter2 = db.ReferenceField('User', verbose_name='邀请者2') inviter3 = db.ReferenceField('User', verbose_name='邀请者3') channel = db.IntField(verbose_name='注册渠道ID') spm = db.StringField(max_length=100, verbose_name='登录SPM') ip = db.StringField(max_length=20, verbose_name='登录IP') generate = db.BooleanField(default=False, verbose_name='生成') error = db.IntField(default=0, verbose_name='登录错误次数') locked = db.DateTimeField(default=lambda: datetime(1970, 1, 1), verbose_name='锁定时间') logined = db.DateTimeField(default=lambda: datetime.now(), verbose_name='登录时间') registered = db.DateTimeField(default=lambda: datetime.now(), verbose_name='注册时间') meta = { 'indexes': [ 'phone', 'nickname', 'ip', '-logined', '-registered', ], } @property def sex_text(self): return self.SEX_DICT[self.sex]
class Field(db.EmbeddedDocument): """ 选项 """ TYPE_INT = 'IntField' TYPE_STRING = 'StringField' TYPE_REF = 'ReferenceField' TYPE_IMAGE = 'XImageField' TYPE_FILE = 'XFileField' TYPE_DATETIME = 'DateTimeField' TYPE_BOOL = 'BooleanField' TYPE_CHOICES = [ (TYPE_INT, TYPE_INT), (TYPE_STRING, TYPE_STRING), (TYPE_REF, TYPE_REF), (TYPE_IMAGE, TYPE_IMAGE), (TYPE_FILE, TYPE_FILE), (TYPE_DATETIME, TYPE_DATETIME), (TYPE_BOOL, TYPE_BOOL), ] key = db.StringField(max_length=100, verbose_name='键名') type = db.StringField(default=TYPE_INT, choices=TYPE_CHOICES, verbose_name='类型') name = db.StringField(max_length=100, verbose_name='名称') default = db.StringField(max_length=100, verbose_name='默认值') model = db.ReferenceField('Model', verbose_name='引用') choices = db.ReferenceField('Choices', verbose_name='选项') def __unicode__(self): return '%s - %s' % (self.key, self.name)
class Action(db.Document): """ 功能模型 """ MENU_ICON = 'bars' key = db.StringField(verbose_name='ID') name = db.StringField(verbose_name='名称') desc = db.StringField(verbose_name='描述') icon = db.Base64ImageField(verbose_name='图标') active_icon = db.Base64ImageField(verbose_name='激活图标') module = choice(db.StringField(verbose_name='模块'), 'action_module', '功能模块') data = db.StringField(verbose_name='数据') target = db.StringField(verbose_name='目标') share = db.EmbeddedDocumentField(Share, verbose_name='分享') sort = db.IntField(verbose_name='排序') android_start = db.ReferenceField(AndroidVersion, verbose_name='安卓版本') android_end = db.ReferenceField(AndroidVersion, verbose_name='安卓最大版本') ios_start = db.ReferenceField(IOSVersion, verbose_name='IOS版本') ios_end = db.ReferenceField(IOSVersion, verbose_name='IOS最大版本') login = db.BooleanField(default=False, verbose_name='登陆') login_show = db.BooleanField(default=False, verbose_name='显示') debug = db.BooleanField(default=False, verbose_name='调试') enable = db.StringField(default=Enable.ENABLED, verbose_name='状态', choices=Enable.CHOICES) modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = { 'indexes': [ 'key', 'sort', '-created', ] } @property def detail(self): return dict( id=self.key, name=self.name, desc=self.desc, icon=self.icon.base64, active_icon=self.active_icon.base64, data=self.data, target=self.target, share=unicode(self.share), login=self.login, login_show=self.login_show, debug=self.debug, )
class ActionItem(db.Document): """ 功能模型 """ MENU_ICON = 'bars' name = db.StringField(verbose_name='名称') key = db.StringField(verbose_name='键名') desc = db.StringField(verbose_name='描述') icon = db.XImageField(verbose_name='图标') module = db.ReferenceField('ActionModule', verbose_name='模块') action = db.StringField(default=Action.DEFAULT, verbose_name='动作', choices=Action.CHOICES) url = db.StringField(verbose_name='链接') share = db.EmbeddedDocumentField(ShareItem, verbose_name='分享') sort = db.IntField(verbose_name='排序') android_version = db.ReferenceField(AndroidVersion, verbose_name='安卓版本') android_version_end = db.ReferenceField(AndroidVersion, verbose_name='安卓最大版本') ios_version = db.ReferenceField(IOSVersion, verbose_name='IOS版本') ios_version_end = db.ReferenceField(IOSVersion, verbose_name='IOS最大版本') login = db.BooleanField(default=False, verbose_name='登陆') login_show = db.BooleanField(default=False, verbose_name='登录显示') enable = db.StringField(default=Enable.ENABLED, verbose_name='状态', choices=Enable.CHOICES) modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = { 'indexes': [ 'key', 'sort', '-created', ] } @property def detail(self): return dict( name=self.name, key=self.key, desc=self.desc, icon=self.icon.link, action=self.action, login=self.login, url=self.url, share=unicode(self.share), extras='', )
class AdminUser(db.Document): """ 管理员 """ xid = db.IntField(verbose_name='XID') username = db.StringField(verbose_name='用户') password = db.StringField(verbose_name='密码') group = db.ReferenceField('Group', verbose_name='组') root = db.BooleanField(default=False, verbose_name='超级管理员') active = db.BooleanField(default=True, verbose_name='激活') freezed = db.DateTimeField(verbose_name='冻结时间') logined = db.DateTimeField(default=datetime.now, verbose_name='登录时间') modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') def __unicode__(self): return self.username def is_user(self): return True def is_authenticated(self): """ 是否登录 """ return True def is_active(self): """ 是否激活 """ return self.active def is_anonymous(self): """ 是否游客 """ return False def get_id(self): s = sign(current_app.config.get('SECRET_KEY'), password=self.password) return '{0}|{1}'.format(self.id, s)
class AdminUserLoginLog(db.Document): """ 管理登录日志 """ TYPE = db.choices(login='******', logout='退出', error='密码错误') user = db.ReferenceField('AdminUser', verbose_name='用户') type = db.StringField(choices=TYPE.CHOICES, verbose_name='类型') spm = db.StringField(max_length=100, verbose_name='SPM') ip = db.StringField(max_length=20, verbose_name='IP') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') def __unicode__(self): return '%s' % self.user.username @staticmethod def log(user, type, spm=None, ip=None): spm = spm if spm else get_spm() ip = ip if ip else get_ip() AdminUserLoginLog(user=user, type=type, spm=spm, ip=ip).save() @staticmethod def login(user): AdminUserLoginLog.log(user, AdminUserLoginLog.TYPE.LOGIN) @staticmethod def logout(user): AdminUserLoginLog.log(user, AdminUserLoginLog.TYPE.LOGOUT) @staticmethod def error(user): AdminUserLoginLog.log(user, AdminUserLoginLog.TYPE.ERROR)
class Slide(db.Document): """ 广告模型 """ MENU_ICON = 'paw' key = db.StringField(verbose_name='ID') name = db.StringField(verbose_name='名称') icon = db.XImageField(verbose_name='图标') module = choice(db.StringField(verbose_name='模块'), 'slide_module', '广告模块') target = db.StringField(verbose_name='目标') share = db.EmbeddedDocumentField(Share, verbose_name='分享') sort = db.IntField(verbose_name='排序') android_start = db.ReferenceField(AndroidVersion, verbose_name='安卓版本') android_end = db.ReferenceField(AndroidVersion, verbose_name='安卓最大版本') ios_start = db.ReferenceField(IOSVersion, verbose_name='IOS版本') ios_end = db.ReferenceField(IOSVersion, verbose_name='IOS最大版本') login = db.BooleanField(default=False, verbose_name='登陆') enable = db.StringField(default=Enable.ENABLED, verbose_name='状态', choices=Enable.CHOICES) modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = { 'indexes': [ 'key', 'sort', '-created', ] } @property def detail(self): return dict( id=self.key, name=self.name, icon=self.icon.link, action=self.action, login=self.login, url=self.url, share=unicode(self.share), extras='', )
class SlideItem(db.Document): """ 广告模型 """ MENU_ICON = 'paw' name = db.StringField(verbose_name='名称') key = db.StringField(verbose_name='键名') icon = db.XImageField(verbose_name='图标') module = db.ReferenceField('SlideModule', verbose_name='模块') action = db.StringField(default=Action.DEFAULT, verbose_name='动作', choices=Action.CHOICES) url = db.StringField(verbose_name='链接') share = db.EmbeddedDocumentField(ShareItem, verbose_name='分享') sort = db.IntField(verbose_name='排序') login = db.BooleanField(default=False, verbose_name='登陆') enable = db.StringField(default=Enable.ENABLED, verbose_name='状态', choices=Enable.CHOICES) modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = { 'indexes': [ 'key', 'sort', '-created', ] } @property def detail(self): return dict( name=self.name, key=self.key, icon=self.icon.link, action=self.action, login=self.login, url=self.url, share=unicode(self.share), extras='', )
class TraceLog(db.Document): """ 监控统计 """ MENU_ICON = 'bug' user = db.ReferenceField('User', verbose_name='用户') key = db.StringField(verbose_name='KEY') tid = db.StringField(verbose_name='TID') label = db.StringField(verbose_name='标识') value = db.StringField(verbose_name='结果') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = { 'indexes': [ 'key', 'tid', 'user', 'label', '-created', ] }
class ShareLog(db.Document): """ 分享日志 """ STATUS = db.choices(success='成功', cancel='取消', error='错误') MEDIA = db.choices(timeline='朋友圈', message='消息', qq='QQ', qzone='qzone') user = db.ReferenceField('User', verbose_name='用户') media = db.StringField(verbose_name='平台', choices=MEDIA.CHOICES) title = db.StringField(verbose_name='标题') desc = db.StringField(verbose_name='描述') link = db.StringField(verbose_name='链接') image = db.StringField(verbose_name='图片链接') status = db.StringField(verbose_name='状态', choices=STATUS.CHOICES) created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = { 'indexes': [ 'user', 'link', '-created', ] }
class AdminChangeLog(db.Document): """ 管理员操作日志 """ TYPE = db.choices(edit='修改', created='创建', delete='删除') user = db.ReferenceField('AdminUser', verbose_name='用户') model = db.StringField(verbose_name='模块') before_data = db.StringField(verbose_name='操作前') after_data = db.StringField(verbose_name='操作后') type = db.StringField(verbose_name='类型', choices=TYPE.CHOICES) spm = db.StringField(verbose_name='spm') ip = db.StringField(verbose_name='IP') headers = db.StringField(verbose_name='头信息') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = dict(indexes=['-created'], ) @staticmethod def log(user, model, before_data, after_data, type, **kwargs): ip = kwargs.get('ip', get_ip()) spm = kwargs.get('spm', get_spm()) headers = kwargs.get('headers', request.headers) AdminChangeLog(user=user, model=model, before_data=before_data, after_data=after_data, type=type, ip=ip, spm=spm, headers=str(headers)).save() @staticmethod def modify_data(user, model, **kwargs): # 使用 before = after = dict(id=model.id) 到有内存引用的问题 before = dict(id=model.id) after = dict(id=model.id) if kwargs.get('form'): try: for k, v in kwargs.get('form').data.iteritems(): if v != model[k]: before[k] = model[k] after[k] = v except: pass else: before = model.to_mongo() if kwargs.get('type') == 'delete': after = '' AdminChangeLog.log( user=user, model=model.__class__.__name__, before_data=str(before), after_data=str(after), type=kwargs.get('type'), ) @staticmethod def dropdown_modify(user, model, **kwargs): before_data = dict(id=kwargs.get('id')) after_data = dict(id=kwargs.get('id')) key = kwargs.get('key') before_data[key] = kwargs.get('before_data') after_data[key] = kwargs.get('after_data') AdminChangeLog.log(user=user, model=model.__name__, before_data=str(before_data), after_data=str(after_data), type='edit', what='waht')
class View(db.Document): """ 管理 """ MENU_ICON = 'futbol-o' TYPE_VIEW = 'view' TYPE_MODEL = 'model' TYPE_CATE = 'cate' TYPE_CHOICES = [ (TYPE_VIEW, '默认'), (TYPE_MODEL, '模型'), (TYPE_CATE, '分类'), ] name = db.StringField(max_length=100, verbose_name='名称') label = db.StringField(max_length=100, verbose_name='标识') type = db.StringField(default=TYPE_VIEW, choices=TYPE_CHOICES, verbose_name='类型') model = db.ReferenceField('Model', verbose_name='模型') icon = db.StringField(max_length=100, verbose_name='图标') page_size = db.IntField(default=50, verbose_name='分页数') can_create = db.BooleanField(default=True, verbose_name='能创建') can_edit = db.BooleanField(default=True, verbose_name='能修改') can_delete = db.BooleanField(default=True, verbose_name='能删除') column_default_sort = db.StringField(max_length=100, verbose_name='默认排序') column_list = db.ListField(db.StringField(), verbose_name='显示列表') column_center_list = db.ListField(db.StringField(), verbose_name='居中列表') column_hidden_list = db.ListField(db.StringField(), verbose_name='隐藏列表') column_filters = db.ListField(db.StringField(), verbose_name='过滤器列表') column_sortable_list = db.ListField(db.StringField(), verbose_name='排序列表') column_searchable_list = db.ListField(db.StringField(), verbose_name='查找列表') form_excluded_columns = db.ListField(db.StringField(), verbose_name='表单隐藏列表') modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') def __unicode__(self): return self.name def setup(self, admin, view): view.name = self.label or view.name view.menu_icon_value = self.icon or view.menu_icon_value if hasattr(view, 'model'): if not view.menu_icon_value: if view.model and hasattr(view.model, 'MENU_ICON'): view.menu_icon_value = view.model.MENU_ICON else: view.menu_icon_value = 'file-o' view.page_size = self.page_size or view.page_size view.can_create = self.can_create view.can_edit = self.can_edit view.can_delete = self.can_delete if self.column_default_sort: try: view.column_default_sort = json.loads( self.column_default_sort) except: pass view.column_list = self.column_list or view.column_list view.column_center_list = self.column_center_list or getattr( view, 'column_center_list', None) view.column_hidden_list = self.column_hidden_list or getattr( view, 'column_hidden_list', None) view.column_filters = self.column_filters or view.column_filters view.column_sortable_list = self.column_sortable_list or view.column_sortable_list view.column_searchable_list = self.column_searchable_list or view.column_searchable_list view.form_excluded_columns = self.form_excluded_columns or view.form_excluded_columns view._refresh_cache() elif not view.menu_icon_value: if hasattr(view, 'MENU_ICON'): view.menu_icon_value = view.MENU_ICON admin._refresh() def save(self): super(View, self).save() if current_app.cool_manager and not current_app.cool_manager.loading: for admin in current_app.extensions.get('admin', []): for view in admin._views: if view.__class__.__name__ == self.name: self.setup(admin, view) break def add_text(self, key): attr = getattr(self, key) if attr: return ' %s = %s' % (key, json.dumps(attr)) @property def code_text(self): texts = [] keys = [ 'column_list', 'column_center_list', 'column_hidden_list', 'column_filters', 'column_sortable_list', 'column_searchable_list', 'form_excluded_columns' ] for key in keys: text = self.add_text(key) if text: texts.append(text) return '\n'.join(texts)
class QRCode(db.Document): """ 二维码 """ MENU_ICON = 'qrcode' user = db.ReferenceField('User', verbose_name='用户') url = db.StringField(verbose_name='链接') image = db.XImageField(verbose_name='二维码') modified = db.DateTimeField(default=datetime.now, verbose_name='修改时间') created = db.DateTimeField(default=datetime.now, verbose_name='创建时间') meta = dict(indexes=['user', '-created']) @staticmethod def get(user, url=None): qr = QRCode.objects(user=user.id).first() if not qr: qr = QRCode(user=user.id, url=url) qr.save() if not qr.url and url: qr.url = url config = current_app.config.get('QRCODE', {}) if config.get('wxclient', True) and (not qr.url or datetime.now() > qr.modified + timedelta(days=25)): data = dict( expire_seconds=2592000, action_name='QR_SCENE', action_info=dict(scene=dict(scene_id=user.id)), ) qr.url = current_app.wxclient.create_qrcode(**data).get('url') qr.modified = datetime.now() if qr.url and not qr.image: @retry(3) def simple(): qr.create_image(user) qr.save() return qr def create_qrcode(self, config): logo = config.get('logo', current_app.get_data_path('imgs/logo.jpg')) A, B, C = 250, 66, 58 qr = qrcode.QRCode(version=2, box_size=10, border=1, error_correction=qrcode.constants.ERROR_CORRECT_M) qr.add_data(self.url) qr.make(fit=True) im = qr.make_image() im = im.convert("RGBA") im = im.resize((A, A), Image.BILINEAR) if config.get('qr_logo', True): em = Image.new("RGBA", (B, B), "white") im.paste(em, ((A - B) / 2, (A - B) / 2), em) with open(logo) as fd: icon = Image.open(StringIO(fd.read())) icon = icon.resize((C, C), Image.ANTIALIAS) icon = icon.convert("RGBA") im.paste(icon, ((A - C) / 2, (A - C) / 2), icon) qr_width = config.get('qr_width', im.size[0]) im = im.resize((qr_width, qr_width), Image.BILINEAR) return im def create_bg(self, config, user, qr): logo = config.get('logo', current_app.get_data_path('imgs/logo.jpg')) bgpath = config.get('bg') if bgpath: with open(bgpath) as fd: bg = Image.open(StringIO(fd.read())) qr_x = config.get('qr_x', (bg.size[0] - qr.size[0]) / 2) qr_y = config.get('qr_y', bg.size[1] / 2) bg.convert("RGBA") bg.paste(qr, (qr_x, qr_y), qr) if user.avatar: ic = Image.open(StringIO(user.avatar.content)) else: with open(logo) as fd: ic = Image.open(StringIO(fd.read())) avatar_width = config.get('avatar_width', ic.size[0]) avatar_x = config.get('avatar_x', (bg.size[0] - avatar_width) / 2) avatar_y = config.get('avatar_y', bg.size[1] / 2) ic = ic.resize((avatar_width, avatar_width), Image.ANTIALIAS) ic = ic.convert("RGBA") if config.get('avatar_circle', False): bigsize = (ic.size[0] * 3, ic.size[1] * 3) mask = Image.new('L', bigsize, 0) draw = ImageDraw.Draw(mask) draw.ellipse((0, 0) + bigsize, fill=255) del draw mask = mask.resize(ic.size, Image.ANTIALIAS) ic.putalpha(mask) bg.paste(ic, (avatar_x, avatar_y), ic) return bg return qr def textsize(self, user, draw, font, width, texts): w, has_nick = 0, False for text in texts: if type(text) in [list, tuple]: text = text[0] if '<nickname>' in text: has_nick = True text = text.replace('<id>', str(user.id)) text = text.replace('<nickname>', user.nickname or '佚名') text = text.replace('<expire>', (self.modified + timedelta(days=30)).strftime('%Y-%m-%d')) w += draw.textsize(text, font=font)[0] limit = len(user.nickname) if has_nick: nick_width = draw.textsize(user.nickname or '佚名', font=font)[0] while limit > 4 and width - w + draw.textsize( user.nickname[:limit], font=font)[0] < nick_width: limit -= 1 w += draw.textsize(user.nickname[:limit], font=font)[0] - nick_width return (width - w) / 2, limit def draw_texts(self, config, user, bg): draw = ImageDraw.Draw(bg) default = config.get('font', current_app.get_data_path('fonts/yh.ttf')) for line in config.get('lines', []): size = line.get('size', 18) font = ImageFont.truetype(line.get('font', default), size) x = line.get('x', 0) y = line.get('y', 0) texts = line.get('texts', []) if x == 'center': x, limit = self.textsize(user, draw, font, bg.size[0], texts) for text in texts: color = line.get('color', '#333333') if type(text) in [list, tuple]: text, color = text[0], text[1] text = text.replace('<id>', str(user.id)) text = text.replace('<nickname>', user.nickname[:limit] or '佚名') text = text.replace('<expire>', (self.modified + timedelta(days=30)).strftime('%Y-%m-%d')) width, _ = draw.textsize(text, font=font) draw.text((x, y), text, font=font, fill=color) x += width del draw return bg def create_image(self, user): config = current_app.config.get('QRCODE', {}) qr = self.create_qrcode(config) bg = self.create_bg(config, user, qr) bg = self.draw_texts(config, user, bg) stream = StringIO() bg.save(stream, format='png') self.image = dict(stream=stream, format='png') self.save()