add first

This commit is contained in:
pengtao 2020-12-01 16:31:04 +08:00
commit 3dc1e04432
2061 changed files with 247188 additions and 0 deletions

217
.gitignore vendored Normal file
View File

@ -0,0 +1,217 @@
*.pyc
.DS_Store
.ropeproject
.idea
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

4
README.md Normal file
View File

@ -0,0 +1,4 @@
基于python开发的一套内容管理系统
框架采用facebook开源的tornado

41
app.py Normal file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# import importlib,sys
# importlib.reload(sys)
# sys.setdefaultencoding('utf8')
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os
from tornado.options import define, options
from libs import db_session, engine # import the engine to bind
#from libs.memcache import cache
if os.getenv("ENV")=='dev':
from dev_settings import url_handlers, settings
else:
from settings import url_handlers, settings
define("port", default=8000, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self, handlers, **settings):
tornado.web.Application.__init__(self, handlers, **settings)
# Have one global connection. or call:session
self.db = db_session
#self.cache = cache
#self.redis = redis.StrictRedis()
application = Application(url_handlers, **settings)
if __name__ == "__main__":
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

59
config.py Normal file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python
# encoding: utf-8
#后台自定义设置
SAVE_LOG_OPEN = 0 # 开启后台日志记录
MAX_LOGIN_TIMES = 9 # 最大登录失败次数防止为0时不能登录因此不包含第一次登录
LOGIN_WAIT_TIME = 60 # 登录次数达到后需要等待时间才能再次登录,单位:分钟
DATAGRID_PAGE_SIZE = 20 # 列表默认分页数
# 单独配置,会覆盖全局配置
FILE_UPLOAD_CONFIG = {
'exts': ['zip', 'rar', 'tar', 'gz', '7z', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'],
'maxSize': 102400
}
FILE_UPLOAD_LINK_CONFIG = {
'exts': ['zip', 'rar', 'tar', 'gz', '7z', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt']
}
FILE_UPLOAD_IMG_CONFIG = {
'exts': ['jpg', 'jpeg', 'gif', 'png']
}
FILE_UPLOAD_FLASH_CONFIG = {
'exts': ['swf']
}
FILE_UPLOAD_MEDIA_CONFIG = {
'exts': ['avi']
}
#'mysql_conn':'mysql+pymysql://miles:aspect@192.168.100.30/test',
# the sql database settings
DATABASE = {
'dev': {
'driven': 'mysql+pymysql',
'host': '192.168.100.30',
'user': 'miles',
'password': 'aspect',
'port': '3306',
'database': 'utf-8',
},
'prod': {
'driven': 'mysql+pymysql',
'host': '10.10.3.5',
'user': 'miles',
'password': 'aspect',
'port': '3306',
'database': 'utf-8',
}
}
# TODO: the reids database settings
REDIS = {
}
# TODO: the log settings
# TODO: memcahce usage

19
dev_settings.py Normal file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env python
# encoding: utf-8
from os import path
from urls import urls_pattern as url_handlers
DEBUG = True
# the application settings
settings = {
'debug': DEBUG,
'cookie_secret': 'test', # TODO: get the real secret
'login_url': '/admin/login',
'xsrf_cookies': True,
'static_path': path.join(path.dirname(__file__), 'static_v1'),
'template_path': path.join(path.dirname(__file__), 'templates'),
#'ui_modules': '' # TODO: the ui modules file
}

0
handlers/__init__.py Normal file
View File

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import RequestHandler
from model.admin import Admin as User
class BaseHandler(RequestHandler):
@property
def db(self):
return self.application.db
@property
def cache(self):
return self.application.cache
def get_current_user(self):
user_id = self.get_secure_cookie('admin_user_id')
if not user_id:
return None
return self.db.query(User).get(user_id)

10
handlers/admin/index.py Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import tornado.web
from handlers.admin import BaseHandler
class AdminIndexHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
self.render('admin/index.html', user=self.current_user)

50
handlers/admin/login.py Normal file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import tornado.web
from tornado.escape import json_encode
from utils import encrypt
from handlers.admin import BaseHandler
from model.admin import Admin as User
class AdminLoginHandler(BaseHandler):
error_message = {
'110': '填写信息不完整',
'121': '该用户不存在',
'122': '密码错误'
}
def get(self):
self.render('admin/login.html', error=None, email='')
def post(self):
username = self.get_argument('username', '')
password = self.get_argument('password', '')
self.set_header("Content-Type", "application/json")
if not (username and password):
ret = {'error': 110, 'msg': self.error_message['110'], 'url': '/admin/index'}
return self.write(json_encode(ret))
user = User.get_by_username(username)
if not user:
ret = {'error': 121, 'msg': self.error_message['121']}
return self.write(json_encode(ret))
if user.get_password() != encrypt(password):
ret = {'error': 122, 'msg': self.error_message['122']}
return self.write(json_encode(ret))
self.set_secure_cookie("admin_user_id", str(user.user_id), expires_days=7)
ret = {'error': 0, 'msg': '登录成功', 'url': '/admin/index'}
return self.write(json_encode(ret))
class AdminLogoutHandler(tornado.web.RequestHandler):
def get(self):
self.clear_cookie('admin_user_id')
self.redirect('/admin/login')

223
handlers/admin/news.py Normal file
View File

@ -0,0 +1,223 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
from tornado.escape import json_encode
from handlers.admin import BaseHandler
from model.news import News, NewsCategory
from utils import date_encode, obj2dict
class NewsListHandler(BaseHandler):
url = 'admin/news/news_list.html'
def get(self):
self.render(self.url)
class NewsListDatagridHandler(BaseHandler):
def get(self):
page = self.get_argument('page', 1)
rows = self.get_argument('rows', 20)
title = self.get_argument('title', '')
begin = self.get_argument('begin', '')
end = self.get_argument('end', '')
query = {}
if title:
query['title'] = title
if begin:
query['begin'] = begin
if end:
query['end'] = end
offset = (int(page) - 1) * int(rows)
limit = rows
rows = News.gets(offset, limit, **query)
rows = [obj2dict(r) for r in rows]
total = News.get_count()
response = {'total': total, 'rows': rows}
return self.write(date_encode(response))
class NewsEditHandler(BaseHandler):
error_message = {
'110': '请选择分类',
'111': '请填写标题',
'112': '请填写内容',
'113': '请填写发布者',
'114': '添加失败'
}
url = 'admin/news/news_edit.html'
def get(self):
news_id = self.get_argument('id', '')
news = News.get(news_id)
category = NewsCategory.gets()
self.render(self.url, info=news, categorys=category)
def post(self):
news_id = int(self.get_argument('id', 0))
category_id = int(self.get_argument('category_id', 0))
title = self.get_argument('title', '')
content = self.get_argument('content', '')
create_uid = self.get_secure_cookie('admin_user_id')
status = int(self.get_argument('status', 0))
if not category_id:
response = {'code': 110, 'msg': self.error_message['110']}
return self.write(json_encode(response))
if not title:
response = {'code': 111, 'msg': self.error_message['111']}
return self.write(json_encode(response))
if not content:
response = {'code': 112, 'msg': self.error_message['112']}
return self.write(json_encode(response))
if not create_uid:
response = {'code': 113, 'msg': self.error_message['113']}
return self.write(json_encode(response))
result = News.update(news_id, category_id, title, content, create_uid, status)
if result:
response = {'code': 0, 'msg': '添加成功'}
return self.write(json_encode(response))
else:
response = {'code': 114, 'msg': self.error_message['114']}
return self.write(json_encode(response))
class NewsAddHandler(BaseHandler):
error_message = {
'110': '请选择分类',
'111': '请填写标题',
'112': '请填写内容',
'113': '请填写发布者',
'114': '添加失败'
}
url = 'admin/news/news_add.html'
def get(self):
self.render(self.url)
def post(self):
category_id = int(self.get_argument('category_id', 0))
title = self.get_argument('title', '')
content = self.get_argument('content', '')
create_uid = self.get_secure_cookie('admin_user_id')
status = int(self.get_argument('status', 0))
if not category_id:
response = {'code': 110, 'msg': self.error_message['110']}
return self.write(json_encode(response))
if not title:
response = {'code': 111, 'msg': self.error_message['111']}
return self.write(json_encode(response))
if not content:
response = {'code': 112, 'msg': self.error_message['112']}
return self.write(json_encode(response))
result = News.new(category_id, title, content, create_uid, status)
if result:
response = {'code': 0, 'msg': '添加成功'}
return self.write(json_encode(response))
else:
response = {'code': 114, 'msg': self.error_message['114']}
return self.write(json_encode(response))
class NewsCategoryListHandler(BaseHandler):
url = 'admin/news/news_category_list.html'
def get(self):
self.render(self.url)
class NewsCategoryListDatagridHandler(BaseHandler):
def get(self):
page = self.get_argument('page', 1)
rows = self.get_argument('rows', 20)
start = (int(page) - 1) * int(rows)
limit = rows
rows = NewsCategory.gets(start, limit)
rows = [obj2dict(r) for r in rows]
total = NewsCategory.get_count()
response = {'total': total, 'rows': rows}
return self.write(date_encode(response))
class NewsCategoryEditHandler(BaseHandler):
error_message = {
'110': '请选择分类',
'111': '分类名为空',
'112': '更新失败'
}
url = 'admin/news/news_category_edit.html'
def get(self):
category_id = self.get_argument('id', '')
news_category = NewsCategory.get(category_id)
self.render(self.url, info=news_category)
def post(self):
category_id = int(self.get_argument('id', 0))
category_name = self.get_argument('category_name', '')
if not category_id:
response = {'code': 110, 'msg': self.error_message['110']}
return self.write(json_encode(response))
if not category_name:
response = {'code': 111, 'msg': self.error_message['111']}
return self.write(json_encode(response))
result = NewsCategory.update(category_id, category_name)
if result:
response = {'code': 0, 'msg': '更新成功'}
return self.write(json_encode(response))
else:
response = {'code': 112, 'msg': self.error_message['112']}
return self.write(json_encode(response))
class NewsCategoryAddHandler(BaseHandler):
error_message = {
'110': '请填写分类',
'111': '分类名为空',
'112': '添加失败'
}
url = 'admin/news/news_category_add.html'
def get(self):
self.render(self.url)
def post(self):
category_name = self.get_argument('category_name', '')
if not category_name:
response = {'code': 110, 'msg': self.error_message['110']}
return self.write(json_encode(response))
result = NewsCategory.new(category_name)
if result:
response = {'code': 0, 'msg': '添加成功'}
return self.write(json_encode(response))
else:
response = {'code': 112, 'msg': self.error_message['112']}
return self.write(json_encode(response))

40
handlers/admin/setting.py Normal file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
from tornado.escape import json_encode
from handlers.admin import BaseHandler
from model.setting import Setting
class AdminSettingSiteHandler(BaseHandler):
url = 'admin/setting/site.html'
def get(self):
self.render(self.url)
def post(self):
#fields = self.request.arguments
fields = self.get_body_arguments('data[]')
for field in fields:
v = field.split('#')
key = v[0]
value = v[1]
Setting.update(key, value)
response = {
'code': 0,
'msg': '更新成功'
}
return self.write(json_encode(response))
class AdminSettingPropertyGridHandler(BaseHandler):
url = 'admin/setting/site.html'
def get(self):
response = Setting.gets()
return self.write(json_encode(response))

377
handlers/admin/user.py Normal file
View File

@ -0,0 +1,377 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import tornado.web
from tornado.escape import json_encode
from handlers.admin import BaseHandler
from model.admin import AdminRole, Admin as User
from utils import encrypt, obj2dict, date_encode
import re
class AdminEditInfoHandler(BaseHandler):
error_message = {
'110': '信息填写不完整',
'111': '该用户不存在',
'112': '更新失败'
}
url = 'admin/user/edit_info.html'
@tornado.web.authenticated
def get(self):
self.render(self.url, user=self.current_user)
def post(self):
realname = self.get_argument('realname', '')
email = self.get_argument('email', '')
self.set_header("Content-Type", "application/json")
if not (realname and email):
ret = {'code': 110, 'msg': self.error_message['110'], 'url': self.url}
return self.write(json_encode(ret))
user = User.get_by_uid(self.get_secure_cookie("admin_user_id"))
if not user:
ret = {'code': 111, 'msg': self.error_message['111']}
return self.write(json_encode(ret))
user.update('', email, realname)
ret = {'code': 0, 'msg': '更新成功', 'url': self.url}
return self.write(json_encode(ret))
class AdminCheckUsernameHandler(BaseHandler):
def post(self):
username = self.get_argument('username', '')
self.set_header("Content-Type", "application/json")
user = User.get_by_username(username)
if not user:
ret = {'code': 0}
else:
ret = {'code': 1}
return self.write(json_encode(ret))
class AdminCheckEmailHandler(BaseHandler):
def post(self):
email = self.get_argument('email', '')
self.set_header("Content-Type", "application/json")
user = User.get_by_email(email)
if not user:
ret = {'code': 0}
else:
ret = {'code': 1}
return self.write(json_encode(ret))
class AdminEditPasswordHandler(BaseHandler):
error_message = {
'210': '信息填写不完整',
'211': '该用户不存在',
'212': '更新失败'
}
url = 'admin/user/edit_password.html'
@tornado.web.authenticated
def get(self):
self.render(self.url, user=self.current_user)
def post(self):
password = self.get_argument('new_password', '')
self.set_header("Content-Type", "application/json")
if not password:
ret = {'code': 210, 'msg': self.error_message['210'], 'url': self.url}
return self.write(json_encode(ret))
user = User.get_by_uid(self.get_secure_cookie("admin_user_id"))
if not user:
ret = {'code': 211, 'msg': self.error_message['211']}
return self.write(json_encode(ret))
user.update_password(password)
ret = {'code': 0, 'msg': '更新成功', 'url': self.url}
return self.write(json_encode(ret))
class AdminCheckPasswordHandler(BaseHandler):
def post(self):
password = self.get_argument('password', '')
self.set_header("Content-Type", "application/json")
user = User.get_by_uid(self.get_secure_cookie("admin_user_id"))
if user.get_password() == encrypt(password):
ret = {'code': 0}
else:
ret = {'code': 1}
return self.write(json_encode(ret))
class AdminMemberListHandler(BaseHandler):
url = 'admin/user/member_list.html'
def get(self):
self.render(self.url)
def post(self):
pass
class AdminMemberListDatagridHandler(BaseHandler):
def get(self):
page = self.get_argument('page', 1)
rows = self.get_argument('rows', 20)
start = (int(page) - 1) * int(rows)
limit = rows
user_list = User.gets(start, limit)
total = User.get_count()
response = {
'total': total,
'rows': user_list
}
return self.write(date_encode(response))
class AdminMemberAddHandler(BaseHandler):
error_message = {
'110': '用户名不能为空',
'111': '密码不能为空',
'112': '邮箱不匹配',
'113': '角色不能为空',
'114': '添加失败'
}
url = 'admin/user/member_add.html'
def get(self):
self.render(self.url)
def post(self):
username = self.get_argument('username', '')
password = self.get_argument('password', '')
email = self.get_argument('email', '')
realname = self.get_argument('realname', '')
role_id = self.get_argument('role_id', 0)
if not username or len(username) > 15:
ret = {'code': 110, 'msg': self.error_message['110']}
return self.write(json_encode(ret))
if not password:
ret = {'code': 111, 'msg': self.error_message['111']}
return self.write(json_encode(ret))
match = re.search(r'[\w.-]+@[\w.-]+', email)
if not match:
ret = {'code': 112, 'msg': self.error_message['112']}
return self.write(json_encode(ret))
if not role_id:
ret = {'code': 113, 'msg': self.error_message['111']}
return self.write(json_encode(ret))
result = User.new(username, email, password, realname, role_id)
if result:
ret = {'code': 0, 'msg': '添加成功'}
return self.write(json_encode(ret))
else:
ret = {'code': 114, 'msg': self.error_message['114']}
return self.write(json_encode(ret))
class AdminMemberEditHandler(BaseHandler):
error_message = {
'110': '用户名不能为空',
'111': '密码不能为空',
'112': '邮箱不匹配',
'113': '角色不能为空',
'114': '更新失败'
}
url = 'admin/user/member_edit.html'
def get(self):
user_id = int(self.get_argument('id', 0))
user = User.get(user_id)
roles = AdminRole.gets()
return self.render(self.url, info=user, roles=roles)
def post(self):
user_id = int(self.get_argument('id', 0))
username = self.get_argument('username', '')
password = self.get_argument('password', '')
email = self.get_argument('email', '')
realname = self.get_argument('realname', '')
role_id = int(self.get_argument('role_id', 0))
match = re.search(r'[\w.-]+@[\w.-]+', email)
if not match:
ret = {'code': 112, 'msg': self.error_message['112']}
return self.write(json_encode(ret))
if not role_id:
ret = {'code': 113, 'msg': self.error_message['113']}
return self.write(json_encode(ret))
result = User.update(user_id, username, email, password, realname, role_id)
if result:
ret = {'code': 0, 'msg': '更新成功'}
return self.write(json_encode(ret))
else:
ret = {'code': 114, 'msg': self.error_message['114']}
return self.write(json_encode(ret))
class AdminCheckRoleHandler(BaseHandler):
def post(self):
role_name = self.get_argument('role_name', '')
self.set_header("Content-Type", "application/json")
role = AdminRole.get_by_rolename(role_name)
if not role:
ret = {'code': 0}
else:
ret = {'code': 1}
return self.write(json_encode(ret))
class AdminRoleListHandler(BaseHandler):
url = 'admin/user/role_list.html'
def get(self):
self.render(self.url)
def post(self):
pass
class AdminRoleListDatagridHandler(BaseHandler):
def get(self):
page = self.get_argument('page', 1)
rows = self.get_argument('rows', 20)
sort = self.get_argument('sort', '')
order = self.get_argument('order', 'ASC')
start = (int(page) - 1) * int(rows)
limit = rows
role_list = AdminRole.gets(start, limit)
total = AdminRole.get_count()
response = {
'total': total,
'rows': [obj2dict(role) for role in role_list]
}
return self.write(json_encode(response))
class AdminRoleAddHandler(BaseHandler):
error_message = {
'310': '角色名称不能为空',
'311': '添加失败'
}
url = 'admin/user/role_add.html'
def get(self):
self.render(self.url)
def post(self):
role_name = self.get_argument('role_name', '')
description = self.get_argument('description', '')
list_order = int(self.get_argument('list_order', 0))
status = int(self.get_argument('status', 0))
if not role_name:
ret = {'code': 310, 'msg': self.error_message['310']}
return self.write(json_encode(ret))
result = AdminRole.new(role_name, description, list_order, status)
if result:
ret = {'code': 0, 'msg': '添加成功'}
return self.write(json_encode(ret))
else:
ret = {'code': 311, 'msg': self.error_message['311']}
return self.write(json_encode(ret))
class AdminRoleEditHandler(BaseHandler):
error_message = {
'310': '角色名称不能为空',
'311': '更新失败'
}
url = 'admin/user/role_edit.html'
def get(self):
role_id = int(self.get_argument('id', 0))
role = AdminRole.get(role_id)
return self.render(self.url, info=role)
def post(self):
role_id = int(self.get_argument('id', 0))
role_name = self.get_argument('role_name', '')
description = self.get_argument('description', '')
list_order = int(self.get_argument('list_order', 0))
status = int(self.get_argument('status', 0))
if not role_name:
ret = {'code': 310, 'msg': self.error_message['310']}
return self.write(json_encode(ret))
result = AdminRole.update(role_id, role_name, description, list_order, status)
if result:
ret = {'code': 0, 'msg': '更新成功'}
return self.write(json_encode(ret))
else:
ret = {'code': 311, 'msg': self.error_message['311']}
return self.write(json_encode(ret))
class AdminRoleOrderHandler(BaseHandler):
def get(self):
order_role = self.request.arguments
for key, list_order in enumerate(order_role):
role_id = list_order[:]
list_order = order_role[list_order][0]
AdminRole.update(role_id, '', '', list_order, 0)
ret = {'code': 0, 'msg': '更新成功'}
return self.write(json_encode(ret))

22
handlers/home/__init__.py Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import RequestHandler
from model.user import User
class BaseHandler(RequestHandler):
@property
def db(self):
return self.application.db
@property
def cache(self):
return self.application.cache
def get_current_user(self):
user_id = self.get_secure_cookie('user_id')
if not user_id:
return None
return User.get(user_id)

29
handlers/home/help.py Normal file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import tornado.web
from handlers.home import BaseHandler
class AboutUsHandler(BaseHandler):
def get(self):
pass
class ContactUsHandler(BaseHandler):
def get(self):
pass
class JoinUsHandler(BaseHandler):
def get(self):
pass
class OfficialNewsHandler(BaseHandler):
def get(self):
pass

10
handlers/home/index.py Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import tornado.web
from handlers.home import BaseHandler
class IndexHandler(BaseHandler):
def get(self):
self.render('home/index.html', error=None, email='')

112
handlers/home/login.py Normal file
View File

@ -0,0 +1,112 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import re
import tornado.web
from utils import encrypt
#import libs.captcha
from model.user import User
from handlers.home import BaseHandler
class RegisterHandler(BaseHandler):
error_message = {
'110': '填写信息不完整',
'111': '用户名最多15个字符',
'112': '用户名已经被使用',
'113': 'Email不正确',
'114': 'Email已经被使用',
'115': '注册失败,请稍后再试'
}
def get(self):
self.render('home/register.html', error=None, username='', email='')
def post(self):
username = self.get_argument('username', '')
email = self.get_argument('email', '')
password = self.get_argument('password', '')
if not username or len(username) > 15:
self.render('home/register.html', error=111, username=username, email=email)
return
match = re.search(r'[\w.-]+@[\w.-]+', email)
if not match:
self.render('home/register.html', error=113, username=username, email=email)
return
if not password:
self.render('home/register.html', error=110, username=username, email=email)
return
user = User.get_by_username(username)
if user:
self.render('home/register.html', error=112, username=username, email=email)
return
user = User.get_by_email(email)
if user:
self.render('home/register.html', error=114, username=username, email=email)
return
#走代理获取ip方式
reg_ip = self.request.headers['X-Real-Ip']
user = User.new(username, email, password, reg_ip)
if user:
self.set_secure_cookie('user_id', str(user.user_id), expires_days=30)
self.redirect(self.get_argument('next', '/'))
else:
self.render('home/register.html', error=115)
class LoginHandler(BaseHandler):
error_message = {
'100': '信息填写不完整',
'101': '该用户不存在',
'102': '密码错误',
'103': '验证码错误'
}
def get(self):
self.render('home/login.html', error=None, email='')
def post(self):
email = self.get_argument('email', '')
password = self.get_argument('password', '')
if not (email and password):
self.render('home/login.html', error=100, email=email)
user = User.get_by_email(email)
if not user:
self.render('home/login.html', error=101, email=email)
if user.get_password() == encrypt(password):
last_login_ip = self.request.headers['X-Real-Ip']
user.update_login_info(last_login_ip)
self.set_secure_cookie("user_id", str(user.user_id), expires_days=7)
self.redirect(self.get_argument('next', '/'))
else:
self.render('home/login.html', error=102, email=email)
#
# #获取远程ip
# remote_ip = self.request.headers['X-Real-Ip']
# challenge = self.get_argument('recaptcha_challenge_field', None)
# response = self.get_argument('recaptcha_response_field', None)
# rsp = captcha.check_google_captcha(self,remote_ip, challenge, response)
# if not rsp.is_valid:
# self.render('home/login.html', error=103, email=email)
#
# self.render('home/login.html', error=100, email=email)
class LogoutHandler(tornado.web.RequestHandler):
def get(self):
self.clear_cookie('user_id')
self.redirect('/bye')
class ByeHandler(tornado.web.RequestHandler):
def get(self):
self.render('home/bye.html')

View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import tornado.web
class RegisterHandler(tornado.web.RequestHandler):
def get(self):
self.render('home/register.html', error=None)

96
handlers/home/settings.py Normal file
View File

@ -0,0 +1,96 @@
#!/usr/bin/env python
#-*- coding:utf8 -*-
import tornado.web
from utils import encrypt
from handlers.home import BaseHandler
from model.user import User, UserInfo
class ProfileHandler(BaseHandler):
error_message = {
'130': 'update success!',
'131': 'realname empty',
'132': 'username empty',
'133': 'email empty',
}
@tornado.web.authenticated
def get(self):
user = self.current_user
user_info = UserInfo.get_info_by_uid(user.user_id)
self.render('home/settings_profile.html', error=None, user=user, user_info=user_info)
def post(self):
user = self.current_user
realname = self.get_argument('realname', '')
username = self.get_argument('username', '')
email = self.get_argument('email', '')
about_me = self.get_argument('about_me', '')
avatar_src = self.get_argument('avatar_src', '')
if not realname:
self.render('home/settings_profile.html', error=131)
return
if not username:
self.render('home/settings_profile.html', error=132)
return
if not email:
self.render('home/settings_profile.html', error=133)
return
user.update(username, email, realname, about_me, avatar_src)
self.redirect('/settings/profile')
class PasswordHandler(BaseHandler):
error_message = {
'140': '密码修改成功',
'141': '原始密码填写错误',
'142': '新密码不能为空',
'143': '校验密码不能为空',
'144': '两次密码不一致',
'145': '密码修改出错,请稍后再试'
}
@tornado.web.authenticated
def get(self):
self.render('home/settings_password.html', error=None)
@tornado.web.authenticated
def post(self):
user = self.current_user
password = self.get_argument('password', '')
new_password = self.get_argument('new_password', '')
verify_password = self.get_argument('verify_password', '')
if user.get_password() != encrypt(password):
self.render('home/settings_password.html', error=141)
return
if new_password == '':
self.render('home/settings_password.html', error=142)
return
if verify_password == '':
self.render('home/settings_password.html', error=143)
return
if new_password != verify_password:
self.render('home/settings_password.html', error=144)
return
result = user.update_password(new_password)
if not result:
self.render('home/settings_password.html', error=145)
return
self.render('home/settings_password.html', error=140)
class NotificationsHandler(BaseHandler):
def get(self):
self.render('home/settings_notifications.html')
def post(self):
pass

54
handlers/home/upload.py Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
#-*- coding:utf8 -*-
# see: http://www.afewords.com/book/502e5cff3725176a91000004/catalog/16
import tornado.web
import tempfile
import Image
#import time
import logging
class UploadHandler(tornado.web.RequestHandler):
def get(self):
self.render('home/upload.html')
def post(slef):
if self.request.files == {} or 'mypicutre' not in self.request.files:
""" 看是否有文件且name为picture跟HTML代码对应 """
self.write('<script>alert("请选择图片")</script>')
return
image_type_list = ['image/gif', 'image/jpeg', 'image/pjpeg', 'image/bmp', 'image/png', 'image/x-png']
send_file = self.request.files['mypicutre'][0]
if send_file['content_type'] not in image_type_list:
self.write('<script>alert("仅支持jpg,jpeg,bmp,gif,png格式的图片")</script>')
return
if len(send_file['body']) > 4*1024*1024:
self.write('<script>alert("请上传4M以下的图片");</script>')
return
tmp_file = tempfile.NamedTemporaryFile(delete=True)
tmp_file.write(send_file['body'])
tmp_file.seek(0)
try:
image_one = Image.open(tmp_file.name)
except IOError, error:
logging.info(error)
logging.info('+'*30 + '\n')
tmp_file.close()
self.write('<script>alert("图片不合法!")</script>')
return
image_path = "./static/picture/"
image_format = send_file['filename'].split('.').pop().lower()
tmp_name = image_path + str(int(time.time())) + image_format
image_one.save(tmp_name)
tmp_file.close()
self.write('<script>alert("文件上传成功,路径为:" + image_path[1:])</script>')
return

6
libs/__init__.py Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from libs.db.database import engine, Base, create_all, drop_all, db_session
__all__ = ['engine', 'Base', 'create_all', 'drop_all', 'db_session']

46
libs/captcha.py Normal file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import urllib,urllib2
import os
GOOGLE_CAPTCHA_API = 'http://www.google.com/recaptcha/api/verify'
class RecaptchaResponse:
def __init__(self, is_valid, error_code=None):
self.is_valid = is_valid
self.error_code = error_code
def check_google_captcha(request,remote_ip,recaptcha_challenge_field,recaptcha_response_field):
if not (recaptcha_challenge_field and recaptcha_response_field):
return RecaptchaResponse (is_valid = False, error_code = 'incorrect-captcha-sol')
def encode_if_necessary(s):
if isinstance(s, unicode):
return s.encode('utf-8')
return s
params = urllib.urlencode ({
'privatekey': encode_if_necessary('6Ld58vISAAAAANJmi5SeL_3JrVooeLGw2kmo7kK5'), #这里是我的私钥
'remoteip' : encode_if_necessary(remote_ip), #远程主机ip
'challenge': encode_if_necessary(recaptcha_challenge_field),
'response' : encode_if_necessary(recaptcha_response_field), #填入的数据
})
request = urllib2.Request(
url = GOOGLE_CAPTCHA_API,
data = params,
headers = {
"Content-type": "application/x-www-form-urlencoded",
"User-agent": "reCAPTCHA Python"
}
)
httpresp = urllib2.urlopen(request)
return_values = httpresp.read().splitlines();
httpresp.close();
return_code = return_values[0]
if (return_code == "true"):
return RecaptchaResponse(is_valid=True)
else:
return RecaptchaResponse(is_valid=False, error_code = return_values[1])

0
libs/db/__init__.py Normal file
View File

38
libs/db/database.py Normal file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from config import DATABASE
import os
"""
Problem:
1.StatementError: Can't reconnect until invalid transaction is rolled back
2.MYSQL has gone away
See: http://mofanim.wordpress.com/2013/01/02/sqlalchemy-mysql-has-gone-away/
Solution: http://docs.sqlalchemy.org/en/rel_0_7/core/pooling.html#setting-pool-recycle
"""
# engine = create_engine('mysql+pymysql://miles:aspect@192.168.100.30/test', pool_recycle=3600, encoding="utf-8", echo=True) #会乱码
env = os.getenv("ENV") or "dev"
print(f"env={env}")
mysql_conn = "{driven}://{user}:{pswd}@{host}/test".format(driven = DATABASE[env]['driven'],
user = DATABASE[env]['user'],
pswd = DATABASE[env]['password'],
host = DATABASE[env]['host'])
print(f"mysql_conn={mysql_conn}")
engine = create_engine(mysql_conn, pool_recycle = 60, connect_args = {"charset": "utf8"}, echo = True)
Base = declarative_base()
db_session = scoped_session(sessionmaker(bind = engine))
def create_all():
Base.metadata.create_all(bind = engine)
def drop_all():
Base.metadata.drop_all(bind=engine)

7
libs/db/torndb.py Normal file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# database.py
import torndb
db_session = torndb.Connection("localhost", "test", user="", password="")

8
libs/memcache.py Normal file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import memcache
#cache = memcache.Client(['127.0.0.1:11211'], debug=1)
#cache.set('foo', 'bar')
#cache.get('foo')

45
libs/paginator.py Normal file
View File

@ -0,0 +1,45 @@
from __future__ import division
import math
import urlparse
import urllib
import tornado.web
def update_querystring(url, **kwargs):
base_url = urlparse.urlsplit(url)
query_args = urlparse.parse_qs(base_url.query)
query_args.update(kwargs)
for arg_name, arg_value in kwargs.iteritems():
if arg_value is None:
if query_args.has_key(arg_name):
del query_args[arg_name]
query_string = urllib.urlencode(query_args, True)
return urlparse.urlunsplit((base_url.scheme, base_url.netloc,
base_url.path, query_string, base_url.fragment))
class Paginator(tornado.web.UIModule):
"""Pagination links display.
{% for result in results %}
<p>{{ result }}</p>
{% end %}
{% module Paginator(page, page_size, results_count) %}
more see: http://stackoverflow.com/questions/15981257/can-tornado-handle-pagination
"""
def render(self, page, page_size, results_count):
pages = int(math.ceil(results_count / page_size)) if results_count else 0
def get_page_url(page):
# don't allow ?page=1
if page <= 1:
page = None
return update_querystring(self.request.uri, page=page)
next = page + 1 if page < pages else None
previous = page - 1 if page > 1 else None
return self.render_string('uimodules/pagination.html', page=page, pages=pages, next=next,
previous=previous, get_page_url=get_page_url)

View File

@ -0,0 +1,11 @@
{% if pages > 1 %}
<div class="pagination pagination-centered">
<ul>
<li{% if previous %}><a href="{{ get_page_url(previous) }}">&laquo;</a>{% else %} class="disabled"><span>&laquo;</span></li>{% end %}
{% for page_num in xrange(1, pages + 1) %}{# 1-index range #}
<li{% if page_num != page %}><a href="{{ get_page_url(page_num) }}">{{ page_num }}</a>{% else %} class="active"><span>{{ page_num }}</span></li>{% end %}
{% end %}
<li{% if next %}><a href="{{ get_page_url(next) }}">&raquo;</a>{% else %} class="disabled"><span>&raquo;</span></li>{% end %}
</ul>
</div>
{% end %}

0
model/__init__.py Normal file
View File

265
model/admin.py Normal file
View File

@ -0,0 +1,265 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from datetime import datetime
from libs import Base, db_session
from utils import encrypt, obj2dict
from sqlalchemy import Column, Integer, String, DateTime, Boolean, desc, asc
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref
class Admin(Base):
__tablename__ = 'wmh_admin'
user_id = Column(Integer, primary_key=True)
realname = Column(String(16))
username = Column(String(16))
email = Column(String(32))
password = Column(String(32))
last_login_time = Column(DateTime)
last_login_ip = Column(String(16))
login_times = Column(Integer)
update_time = Column(DateTime)
status = Column(Integer)
role_id = Column(Integer)
#user_info = relationship('UserInfo', backref='wmh_user')
def __init__(self, user_id, username, email, realname, role_id=0):
self.user_id = user_id
self.username = username
self.email = email
self.realname = realname
self.role_id = role_id
def __repr__(self):
return "<Admin('%s')>" % (self.username)
@classmethod
def new(cls, username, email, password, realname, role_id):
"""
add new user
"""
user = Admin(None, username, email, realname, role_id)
user.password = encrypt(password) if password else ''
user.status = 1
#TODO optimize
user.last_login_time = '0000-00-00 00:00:00'
user.last_login_ip = ''
user.login_times = 0
user.update_time = '0000-00-00 00:00:00'
db_session.add(user)
#只有提交事务了,才可以获取(user.user_id)数据的ID值
try:
db_session.commit()
except:
db_session.rollback()
if user.user_id:
return cls.get(user.user_id)
return None
def update_password(self, password):
password = encrypt(password)
current_time = datetime.now()
update_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update = {
Admin.password: password,
Admin.update_time: update_time
}
db_session.query(Admin).filter(Admin.user_id == self.user_id).update(update)
try:
return db_session.commit()
except:
db_session.rollback()
def get_password(self):
return db_session.query(Admin).filter(Admin.user_id == self.user_id).first().password
def update_login_info(self, last_login_ip):
current_time = datetime.now()
last_login_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update = {
Admin.last_login_time: last_login_time,
Admin.last_login_ip: last_login_ip,
Admin.login_times: Admin.login_times + 1
}
db_session.query(Admin).filter(Admin.user_id == self.user_id).update(update)
try:
return db_session.commit()
except:
db_session.rollback()
def update_email(self, email):
current_time = datetime.now()
update_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update = {
Admin.email: email,
Admin.update_time: update_time
}
db_session.query(Admin).filter(Admin.user_id == self.user_id).update(update)
try:
return db_session.commit()
except:
db_session.rollback()
@classmethod
def initialize(cls, item):
if not item:
return None
user_id = item.user_id
username = item.username
email = item.email
realname = item.realname
if not user_id:
return None
return cls(user_id, username, email, realname)
@classmethod
def get(cls, user_id):
item = db_session.query(Admin.user_id, Admin.realname, Admin.email, Admin.username, Admin.last_login_ip,
Admin.login_times, Admin.status).filter(Admin.user_id == user_id).first()
return item and cls.initialize(item)
@classmethod
def get_by_uid(cls, user_id):
item = db_session.query(Admin).filter(Admin.user_id == user_id).first()
return item and cls.initialize(item)
@classmethod
def get_by_email(cls, email):
item = db_session.query(Admin).filter(Admin.email == email).first()
return item and cls.initialize(item)
@classmethod
def get_by_username(cls, username):
item = db_session.query(Admin).filter(Admin.username == username).first()
return item and cls.initialize(item)
@classmethod
def update(cls, user_id=0, username='', email='', password='', realname='', role_id=0):
update = {}
if username:
update['username'] = username
if email:
update['email'] = email
if password:
update['password'] = password
if realname:
update['realname'] = realname
if role_id:
update['role_id'] = role_id
current_time = datetime.now()
update_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update['update_time'] = update_time
try:
db_session.query(Admin).filter(Admin.user_id == user_id).update(update)
db_session.commit()
return True
except:
db_session.rollback()
raise
@classmethod
def gets(cls, start=0, limit=20):
rs = db_session.query(Admin.user_id, Admin.realname, Admin.email, Admin.username, Admin.last_login_ip,
Admin.last_login_time, Admin.login_times, Admin.status).offset(start).limit(limit)
return [obj2dict(r) for r in rs.all()]
@classmethod
def get_count(cls):
return db_session.query(Admin).count()
class AdminRole(Base):
__tablename__ = 'wmh_admin_role'
role_id = Column(Integer, primary_key=True)
role_name = Column(String(50))
description = Column(String)
list_order = Column(Integer)
status = Column(Integer)
def __init__(self, role_id, role_name, description, list_order, status):
self.role_id = role_id
self.role_name = role_name
self.description = description
self.list_order = list_order
self.status = status
def __repr__(self):
return "<AdminRole('%s')>" % self.role_name
@classmethod
def initialize(cls, item):
if not item:
return None
role_id = item.role_id
role_name = item.role_name
description = item.description
list_order = item.list_order
status = item.status
if not role_id:
return None
return cls(role_id, role_name, description, list_order, status)
@classmethod
def new(cls, role_name, description, list_order, status):
"""
add new role
"""
role = AdminRole(None, role_name, description, list_order, status)
db_session.add(role)
try:
db_session.commit()
return True
except:
db_session.rollback()
return None
@classmethod
def update(cls, role_id, role_name, description, list_order, status):
update = {}
if role_name:
update['role_name'] = role_name
if description:
update['description'] = description
if list_order:
update['list_order'] = list_order
if status:
update['status'] = status
try:
db_session.query(AdminRole).filter(AdminRole.role_id == role_id).update(update)
db_session.commit()
return True
except:
db_session.rollback()
raise
@classmethod
def get(cls, role_id):
item = db_session.query(AdminRole.role_id, AdminRole.role_name, AdminRole.description, AdminRole.list_order,
AdminRole.status).filter(AdminRole.role_id == role_id).first()
return item and cls.initialize(item)
@classmethod
def gets(cls, start=0, limit=20, sort='id', order='asc'):
return db_session.query(AdminRole.role_id, AdminRole.role_name, AdminRole.description, AdminRole.list_order,
AdminRole.status).offset(start).limit(limit).all()
@classmethod
def get_count(cls):
return db_session.query(AdminRole).count()
@classmethod
def get_by_rolename(cls, role_name):
item = db_session.query(AdminRole).filter(AdminRole.role_name == role_name).first()
return item and cls.initialize(item)

190
model/news.py Normal file
View File

@ -0,0 +1,190 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from datetime import datetime
from libs import Base, db_session
from utils import obj2dict
from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey
from sqlalchemy.orm import relationship, backref
from model.admin import Admin
class News(Base):
__tablename__ = 'wmh_news'
news_id = Column(Integer, primary_key=True)
title = Column(String(50))
content = Column(Text)
create_time = Column(DateTime)
update_time = Column(DateTime)
status = Column(Integer)
create_uid = Column(Integer, ForeignKey('wmh_admin.user_id'))
category_id = Column(Integer, ForeignKey('wmh_news_category.category_id'))
#category = relationship('NewsCategory', backref='News')
def __init__(self, news_id, category_id, title, content, create_uid=0, status=0, create_time='', update_time=''):
self.news_id = news_id
self.category_id = category_id
self.title = title
self.content = content
if create_time:
self.create_time = create_time
if update_time:
self.update_time = update_time
if create_uid:
self.create_uid = create_uid
self.status = status
def __repr__(self):
return "<News('%s')>" % self.title
@classmethod
def initialize(cls, item):
if not item:
return None
news_id = item.news_id
category_id = item.category_id
title = item.title
content = item.content
create_time = item.create_time
update_time = item.update_time
create_uid = item.create_uid
status = item.status
if not news_id:
return None
return cls(news_id, category_id, title, content, create_uid, status, create_time, update_time)
@classmethod
def new(cls, category_id, title, content, create_uid, status):
"""
add new news
"""
news = News(None, category_id, title, content, create_uid, status)
#TODO optimize
news.update_time = '0000-00-00 00:00:00'
db_session.add(news)
try:
db_session.commit()
except:
db_session.rollback()
if news.news_id:
return cls.get(news.news_id)
return None
@classmethod
def update(cls, news_id, category_id, title, content, create_uid, status):
update = {}
if category_id:
update['category_id'] = category_id
if title:
update['title'] = title
if content:
update['content'] = content
if create_uid:
update['create_uid'] = create_uid
if status:
update['status'] = status
current_time = datetime.now()
update_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update['update_time'] = update_time
try:
db_session.query(News).filter(News.news_id == news_id).update(update)
db_session.commit()
return True
except:
db_session.rollback()
raise
@classmethod
def get(cls, news_id):
item = db_session.query(News.news_id, News.category_id, News.title, News.content, News.create_uid, News.status,
News.create_time, News.update_time).filter(News.news_id == news_id).first()
return item and cls.initialize(item)
@classmethod
def gets(cls, offset=0, limit=20, title='', begin=0, end=0):
end = end if end else '2039-12-12'
rs = db_session.query(News.news_id, News.news_id.label('operate_id'), News.category_id, News.title,
News.create_time, News.create_uid, News.update_time, News.status,
NewsCategory.category_name, Admin.username).\
join(NewsCategory, Admin).\
filter(News.category_id == NewsCategory.category_id, News.create_uid == Admin.user_id).\
filter(News.title.like('%'+title+'%')).\
filter(News.create_time >= begin, News.create_time <= end).\
offset(offset).limit(limit)
return rs.all()
@classmethod
def get_count(cls):
return db_session.query(News).count()
class NewsCategory(Base):
__tablename__ = 'wmh_news_category'
category_id = Column(Integer, primary_key=True)
category_name = Column(String(50))
def __init__(self, category_id, category_name):
self.category_id = category_id
self.category_name = category_name
def __repr__(self):
return "<NewsCategory('%s')>" % self.category_name
@classmethod
def new(cls, category_name):
"""
add new news
"""
newsCategory = NewsCategory(None, category_name)
db_session.add(newsCategory)
try:
db_session.commit()
except:
db_session.rollback()
if newsCategory.category_id:
return cls.get(newsCategory.category_id)
return None
@classmethod
def update(cls, category_id, category_name):
update = {}
if category_name:
update['category_name'] = category_name
try:
db_session.query(NewsCategory).filter(NewsCategory.category_id == category_id).update(update)
db_session.commit()
return True
except:
db_session.rollback()
raise
@classmethod
def get(cls, category_id):
item = db_session.query(NewsCategory.category_id, NewsCategory.category_name)\
.filter(NewsCategory.category_id == category_id).first()
return item
@classmethod
def gets(cls, start=0, limit=20):
rs = db_session.query(NewsCategory.category_id, NewsCategory.category_name).offset(start).limit(limit)
return rs.all()
@classmethod
def get_count(cls):
return db_session.query(NewsCategory).count()

61
model/setting.py Normal file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from libs import Base, db_session
from utils import obj2dict
from sqlalchemy import Column, String
from utils.dict_setting import setting
class Setting(Base):
__tablename__ = 'wmh_setting'
key = Column(String(100), primary_key=True)
value = Column(String(255))
def __init__(self, key, value):
self.key = key
self.value = value
def __repr__(self):
return "<Settings('%s')>" % (self.key)
@classmethod
def update(cls, key, value):
update = {}
if key and value:
update['value'] = value
try:
db_session.query(Setting).filter(Setting.key == key).update(update)
db_session.commit()
return True
except:
db_session.rollback()
raise
@classmethod
def gets(cls):
_dict = {}
#get current config list
fields = setting
settingFields = setting.keys()
#get data from db
data = db_session.query(Setting.key, Setting.value).filter(Setting.key.in_(settingFields))
_dict['total'] = data.count()
data = data.all()
_data_dict = dict(data)
for key in fields:
#如果数据库不存在该设置项则从默认值中获取
fields[key]['value'] = _data_dict[key] if _data_dict[key] else fields[key]['default']
fields[key]['key'] = key
_dict['rows'] = fields.values()
return _dict

198
model/user.py Normal file
View File

@ -0,0 +1,198 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from datetime import datetime
from libs import Base, db_session
from utils import encrypt
from sqlalchemy import Column, Integer, String, DateTime, Boolean
from sqlalchemy import ForeignKey
class User(Base):
__tablename__ = 'wmh_user'
user_id = Column(Integer, primary_key=True)
realname = Column(String(16))
username = Column(String(16), unique=True)
email = Column(String(32), unique=True, index=True)
password = Column(String(32))
reg_ip = Column(String(16))
last_login_time = Column(DateTime)
last_login_ip = Column(String(16))
login_times = Column(Integer)
update_time = Column(DateTime)
def __init__(self, user_id, username, email, realname):
self.user_id = user_id
self.username = username
self.email = email
self.realname = realname
def __repr__(self):
return "<User('%s')>" % (self.username)
@classmethod
def new(cls, username, email, password, reg_ip):
"""
add new user
"""
user = User(None, username, email)
user.password = encrypt(password) if password else ''
user.reg_ip = reg_ip
db_session.add(user)
#只有提交事务了,才可以获取(user.user_id)数据的ID值
try:
db_session.commit()
except:
db_session.rollback()
db_session.close()
if user.user_id:
return cls.get(user.user_id)
return None
def update_password(self, password):
password = encrypt(password)
current_time = datetime.now()
update_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update = {
User.password: password,
User.update_time: update_time
}
db_session.query(User).filter(User.user_id == self.user_id).update(update)
try:
db_session.commit()
return True
except:
db_session.rollback()
def get_password(self):
return db_session.query(User).filter(User.user_id == self.user_id).first().password
def update_login_info(self, last_login_ip):
current_time = datetime.now()
last_login_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update = {
User.last_login_time: last_login_time,
User.last_login_ip: last_login_ip,
User.login_times: User.login_times + 1
}
db_session.query(User).filter(User.user_id == self.user_id).update(update)
try:
db_session.commit()
return True
except:
db_session.rollback()
def update_email(self, email):
current_time = datetime.now()
update_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update = {
User.email: email,
User.update_time: update_time
}
db_session.query(User).filter(User.user_id == self.user_id).update(update)
try:
return db_session.commit()
except:
db_session.rollback()
@classmethod
def initialize(cls, item):
if not item:
return None
user_id = item.user_id
username = item.username
email = item.email
realname = item.realname
if not user_id:
return None
return cls(user_id, username, email, realname)
@classmethod
def get(cls, user_id):
item = db_session.query(User).filter(User.user_id == user_id).first()
return item and cls.initialize(item)
@classmethod
def get_by_uid(cls, user_id):
item = db_session.query(User).filter(User.user_id == user_id).first()
return item and cls.initialize(item)
@classmethod
def get_by_email(cls, email):
item = db_session.query(User).filter(User.email == email).first()
return item and cls.initialize(item)
@classmethod
def get_by_username(cls, username):
item = db_session.query(User).filter(User.username == username).first()
return item and cls.initialize(item)
def update(self, username='', email='', realname='', about_me='', avatar_src=''):
update = {}
update_info = {}
if username:
update['username'] = username
if email:
update['email'] = email
if realname:
update['realname'] = realname
current_time = datetime.now()
update_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
update['update_time'] = update_time
if about_me:
update_info['about_me'] = about_me
if avatar_src:
update_info['avatar_src'] = avatar_src
try:
db_session.query(User).filter(User.user_id == self.user_id).update(update)
#if update_info['about_me'] or update_info['avatar_src']:
# db_session.query(UserInfo).filter(UserInfo.user_id == self.user_id).update(update_info)
db_session.commit()
except:
db_session.rollback()
raise
class UserInfo(Base):
__tablename__ = 'wmh_user_extinfo'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('wmh_user.user_id'))
gender = Column(Integer)
birthday = Column(DateTime)
about_me = Column(String(500))
#avatar_src = Column(String(255))
def __init__(self, user_id, gender, birthday, about_me):
self.user_id = user_id
self.gender = gender
self.birthday = birthday
self.about_me = about_me
#self.avatar_src = avatar_src
def __repr__(self):
return "<UserInfo(gender=%s, birthday=%s)>" % (self.gender, self.birthday)
@classmethod
def initialize(cls, item):
if not item:
return None
user_id = item.user_id
gender = item.gender
birthday = item.birthday
about_me = item.about_me
#avatar_src = item.avatar_src
if not user_id:
return None
return cls(user_id, gender, birthday, about_me )
@classmethod
def get_info_by_uid(cls, user_id):
item = db_session.query(UserInfo).filter(UserInfo.user_id == user_id).first()
return item and cls.initialize(item)

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
tornado
sqlalchemy
wtforms
#python-memcached==1.53
#MySQL-python
pymysql

1
robots.txt Normal file
View File

@ -0,0 +1 @@
Disallow: /

19
settings.py Normal file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env python
# encoding: utf-8
from os import path
from urls import urls_pattern as url_handlers
DEBUG = False
# the application settings
settings = {
'debug': DEBUG,
'cookie_secret': 'test', # TODO: get the real secret
'login_url': '/admin/login',
'xsrf_cookies': True,
'static_path': path.join(path.dirname(__file__), 'static_v1'),
'template_path': path.join(path.dirname(__file__), 'templates'),
#'ui_modules': '' # TODO: the ui modules file
}

27
static/css/admin/bootstrap.css vendored Normal file
View File

@ -0,0 +1,27 @@
@CHARSET "UTF-8";
body{margin:0; padding:0;background:none;font-size:13px;font-family:sans-serif;}
a{color:#08c;text-decoration:none}
a:hover,a:focus{color:#005580;text-decoration:underline}
a:focus,a:hover,a:active {outline: 0;}
.word-wrap{word-wrap: break-word;word-break: break-all;}
.messager-body{word-wrap: break-word;word-break: break-all;}
.datagrid-row-selected a{color:#fff} /* 解决列表选中部分a标签看不见问题 */
.panel input,.panel form{font-size:12px} /* 表单中字体问题 */
/* 表单样式 */
form{font-size:13px}
textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{background-color: #ffffff;border: 1px solid #cccccc;-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;-moz-transition: border linear 0.2s, box-shadow linear 0.2s;-o-transition: border linear 0.2s, box-shadow linear 0.2s;transition: border linear 0.2s, box-shadow linear 0.2s;}
textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{border-color: rgba(82, 168, 236, 0.8);outline: 0;outline: thin dotted \9;-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);}
input[type="radio"],input[type="checkbox"] {margin: 4px 0 0;margin-top: 1px \9;*margin-top: 0;line-height: normal;}
input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"] {width: auto;}
/* 首页样式 */
#toparea{background-color:#EEEEEE;background-image:linear-gradient(to bottom, #FFFFFF, #EEEEEE);background-repeat: repeat-x;border:0;padding:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF',endColorstr='#FFEEEEEE',GradientType=0)}
#topmenu{display:inline;background:none;}
#topmenu .logo{color:#777777;display:block;float:left;font-size:20px;text-decoration:none;cursor:default;font-weight: 200;padding:7px 17px 5px;text-shadow: 0 1px 0 #FFFFFF;}
#topmenu .nav{float:left;margin:0;padding:0;list-style:none}
#topmenu .nav li{float:left;margin:0;padding:0}
#topmenu .nav li a{color:#777777;padding:13px 15px;text-decoration:none;text-shadow:0 1px 0 #FFFFFF;display: block;}
#topmenu .nav li a.focus{background-color:#E5E5E5;box-shadow:0 3px 8px rgba(0, 0, 0, 0.125) inset;color:#555555;text-decoration:none;}
#topmenu .nav-right{margin:8px 5px 0;padding:0;list-style:none;float:right}
#topmenu .nav-right li{float:left;margin:0 5px;padding:0;}

View File

@ -0,0 +1,26 @@
@CHARSET "UTF-8";
body{margin:0; padding:0;background:none;font-size:13px;font-family:sans-serif;}
a{color:#08c;text-decoration:none}
a:hover,a:focus{color:#005580;text-decoration:underline}
a:focus,a:hover,a:active {outline: 0;}
.word-wrap{word-wrap: break-word;word-break: break-all;}
.messager-body{word-wrap: break-word;word-break: break-all;}
.datagrid-row-selected a{color:#000} /* 解决列表选中部分a标签看不见问题 */
.panel input,.panel form{font-size:12px} /* 表单中字体问题 */
/* 表单样式 */
form{font-size:13px}
textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{background-color: #ffffff;border: 1px solid #cccccc;-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;-moz-transition: border linear 0.2s, box-shadow linear 0.2s;-o-transition: border linear 0.2s, box-shadow linear 0.2s;transition: border linear 0.2s, box-shadow linear 0.2s;}
input[type="radio"],input[type="checkbox"] {margin: 4px 0 0;margin-top: 1px \9;*margin-top: 0;line-height: normal;}
input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"] {width: auto;}
/* 首页样式 */
#toparea{background-color:#E4EEFF;background-image:linear-gradient(to bottom, #F2F5FF, #E4EEFF);background-repeat: repeat-x;border:0;padding:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFF2F5FF',endColorstr='#FFE4EEFF',GradientType=0)}
#topmenu{display:inline;background:none;}
#topmenu .logo{color:#777777;display:block;float:left;font-size:20px;text-decoration:none;cursor:default;font-weight: 200;padding:7px 17px 5px;text-shadow: 0 1px 0 #FFFFFF;}
#topmenu .nav{float:left;margin:0;padding:0;list-style:none}
#topmenu .nav li{float:left;margin:0;padding:0}
#topmenu .nav li a{color:#777777;padding:13px 15px;text-decoration:none;text-shadow:0 1px 0 #FFFFFF;display: block;}
#topmenu .nav li a.focus{background-color:#D0DCE2;box-shadow:0 3px 8px rgba(0, 0, 0, 0.125) inset;color:#555555;text-decoration:none;}
#topmenu .nav-right{margin:8px 5px 0;padding:0;list-style:none;float:right}
#topmenu .nav-right li{float:left;margin:0 5px;padding:0;}

27
static/css/admin/gray.css Normal file
View File

@ -0,0 +1,27 @@
@CHARSET "UTF-8";
body{margin:0; padding:0;background:none;font-size:13px;font-family:sans-serif;}
a{color:#08c;text-decoration:none}
a:hover,a:focus{color:#005580;text-decoration:underline}
a:focus,a:hover,a:active {outline: 0;}
.word-wrap{word-wrap: break-word;word-break: break-all;}
.messager-body{word-wrap: break-word;word-break: break-all;}
.datagrid-row-selected a{color:#fff} /* 解决列表选中部分a标签看不见问题 */
.panel input,.panel form{font-size:12px} /* 表单中字体问题 */
/* 表单样式 */
form{font-size:13px}
textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{background-color: #ffffff;border: 1px solid #cccccc;-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;-moz-transition: border linear 0.2s, box-shadow linear 0.2s;-o-transition: border linear 0.2s, box-shadow linear 0.2s;transition: border linear 0.2s, box-shadow linear 0.2s;}
textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{border-color: rgba(82, 168, 236, 0.8);outline: 0;outline: thin dotted \9;-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);}
input[type="radio"],input[type="checkbox"] {margin: 4px 0 0;margin-top: 1px \9;*margin-top: 0;line-height: normal;}
input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"] {width: auto;}
/* 首页样式 */
#toparea{background-color:#EFEFEF;background-image:linear-gradient(to bottom, #FFFFFF, #EFEFEF);background-repeat: repeat-x;border:0;padding:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF',endColorstr='#FFEFEFEF',GradientType=0)}
#topmenu{display:inline;background:none;}
#topmenu .logo{color:#777777;display:block;float:left;font-size:20px;text-decoration:none;cursor:default;font-weight: 200;padding:7px 17px 5px;text-shadow: 0 1px 0 #FFFFFF;}
#topmenu .nav{float:left;margin:0;padding:0;list-style:none}
#topmenu .nav li{float:left;margin:0;padding:0}
#topmenu .nav li a{color:#777777;padding:13px 15px;text-decoration:none;text-shadow:0 1px 0 #FFFFFF;display: block;}
#topmenu .nav li a.focus{background-color:#E5E5E5;box-shadow:0 3px 8px rgba(0, 0, 0, 0.125) inset;color:#555555;text-decoration:none;}
#topmenu .nav-right{margin:8px 5px 0;padding:0;list-style:none;float:right}
#topmenu .nav-right li{float:left;margin:0 5px;padding:0;}

View File

@ -0,0 +1,26 @@
@CHARSET "UTF-8";
body{margin:0; padding:0;background:none;font-size:13px;font-family:sans-serif;}
a{color:#08c;text-decoration:none}
a:hover,a:focus{color:#005580;text-decoration:underline}
a:focus,a:hover,a:active {outline: 0;}
.word-wrap{word-wrap: break-word;word-break: break-all;}
.messager-body{word-wrap: break-word;word-break: break-all;}
.datagrid-row-selected a{color:#000} /* 解决列表选中部分a标签看不见问题 */
.panel input,.panel form{font-size:12px} /* 表单中字体问题 */
/* 表单样式 */
form{font-size:13px}
textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{background-color: #ffffff;border: 1px solid #dddddd;}
input[type="radio"],input[type="checkbox"] {margin: 4px 0 0;margin-top: 1px \9;*margin-top: 0;line-height: normal;}
input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"] {width: auto;}
/* 首页样式 */
#toparea{background-color:#FFFFFF;}
#topmenu{display:inline;background:none;}
#topmenu .logo{color:#777777;display:block;float:left;font-size:20px;text-decoration:none;cursor:default;font-weight: 200;padding:7px 17px 5px;text-shadow: 0 1px 0 #FFFFFF;}
#topmenu .nav{float:left;margin:0;padding:0;list-style:none}
#topmenu .nav li{float:left;margin:0;padding:0}
#topmenu .nav li a{color:#777777;padding:13px 15px;text-decoration:none;text-shadow:0 1px 0 #FFFFFF;display: block;}
#topmenu .nav li a.focus{background-color:#E5EFF2;color:#555555;text-decoration:none;}
#topmenu .nav-right{margin:8px 5px 0;padding:0;list-style:none;float:right}
#topmenu .nav-right li{float:left;margin:0 5px;padding:0;}

4
static/css/bootstrap.amethyst.min.css vendored Normal file

File diff suppressed because one or more lines are too long

4
static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1078
static/css/icons.css Normal file

File diff suppressed because it is too large Load Diff

14
static/css/plan.amethyst.min.css vendored Normal file

File diff suppressed because one or more lines are too long

14
static/css/plan.min.css vendored Normal file

File diff suppressed because one or more lines are too long

58
static/css/style.css Normal file
View File

@ -0,0 +1,58 @@
html,body {
margin: 0;
padding: 0;
height: 100%;
font-family:sans-serif;
}
#wrap {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0px auto -60px;
padding: 0;
}
#push{
height:60px;
}
footer {
height: 60px;
background-color: white;
border-top: 1px solid #ddd;
padding: 18px 0;
margin: 0;
}
.row > [class*="col-"] {
margin-bottom: 30px;
}
.navbar-container {
position: relative;
min-height: 100px;
}
.navbar-container .navbar.navbar-fixed-top,
.navbar-container .navbar.navbar-fixed-bottom {
position: absolute;
top: 50px;
z-index: 0;
}
.navbar-container .navbar.navbar-fixed-top .container,
.navbar-container .navbar.navbar-fixed-bottom .container {
max-width: 90%;
}
.btn-group {
margin-bottom: 10px
}
.form-inline select,
.form-inline input[type="text"],
.form-inline input[type="password"] {
width: 180px;
}
.input-group {
margin-bottom: 10px;
}
.pagination { margin-top :0;}
.navbar-inverse {margin: 110px 0}
.word-wrap{
word-wrap: break-word;
word-break: break-all;
}

View File

@ -0,0 +1,137 @@
[
{
"name": "系统设置",
"son": [
{
"text": "系统设置",
"id": "9",
"url": "/admin/setting/site"
}
]
},
{
"name": "管理员设置",
"son": [
{
"text": "管理员管理",
"id": "9",
"url": "/admin/user/member_list"
},
{
"text": "角色管理",
"id": "11",
"url": "/admin/user/role_list"
}
]
},
{
"name": "新闻管理",
"son": [
{
"text": "新闻管理",
"id": "34",
"url": "/admin/news/news_list"
},
{
"text": "分类管理",
"id": "34",
"url": "/admin/news/news_category_list"
}
]
},
{
"name": "图片管理",
"son": [
{
"text": "图片管理",
"id": "34",
"url": "/admin/image"
},
{
"text": "分类管理",
"id": "34",
"url": "/admin/image_category"
},
{
"text": "评论管理",
"id": "34",
"url": "/admin/image_comment"
}
]
},
{
"name": "用户管理",
"son": [
{
"text": "用户管理",
"id": "14",
"url": "/admin/user"
},
{
"text": "黑名单",
"id": "14",
"url": "/admin/user_black"
}
]
},
{
"name": "关于我们",
"son": [
{
"text": "关于我们",
"id": "9",
"url": "/admin/about_us"
},
{
"text": "联系我们",
"id": "9",
"url": "/admin/contact_us"
},
{
"text": "招贤纳士",
"id": "9",
"url": "/admin/join_us"
},
{
"text": "公司新闻",
"id": "9",
"url": "/admin/official_news"
}
]
},
{
"name": "友情链接",
"son": [
{
"text": "友情管理",
"id": "9",
"url": "/admin/friend_link"
}
]
},
{
"name": "后台管理",
"son": [
{
"text": "操作日志",
"id": "14",
"url": "/admin/op_log"
}
]
},
{
"name": "我的面板",
"son": [
{
"text": "修改个人信息",
"id": "6",
"url": "/admin/user/edit_info"
},
{
"text": "修改密码",
"id": "6",
"url": "/admin/user/edit_password"
}
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Some files were not shown because too many files have changed in this diff Show More