add first
217
.gitignore
vendored
Normal 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
|
||||
|
41
app.py
Normal 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
@ -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
@ -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
23
handlers/admin/__init__.py
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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')
|
9
handlers/home/register.py
Normal 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
@ -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
@ -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
@ -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
@ -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
38
libs/db/database.py
Normal 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
@ -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
@ -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
@ -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)
|
11
libs/uimodules/pagination.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% if pages > 1 %}
|
||||
<div class="pagination pagination-centered">
|
||||
<ul>
|
||||
<li{% if previous %}><a href="{{ get_page_url(previous) }}">«</a>{% else %} class="disabled"><span>«</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) }}">»</a>{% else %} class="disabled"><span>»</span></li>{% end %}
|
||||
</ul>
|
||||
</div>
|
||||
{% end %}
|
0
model/__init__.py
Normal file
265
model/admin.py
Normal 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
@ -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
@ -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
@ -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
@ -0,0 +1,6 @@
|
||||
tornado
|
||||
sqlalchemy
|
||||
wtforms
|
||||
#python-memcached==1.53
|
||||
#MySQL-python
|
||||
pymysql
|
1
robots.txt
Normal file
@ -0,0 +1 @@
|
||||
Disallow: /
|
19
settings.py
Normal 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
@ -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;}
|
26
static/css/admin/default.css
Normal 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
@ -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;}
|
26
static/css/admin/metro.css
Normal 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
4
static/css/bootstrap.min.css
vendored
Normal file
1078
static/css/icons.css
Normal file
14
static/css/plan.amethyst.min.css
vendored
Normal file
14
static/css/plan.min.css
vendored
Normal file
58
static/css/style.css
Normal 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;
|
||||
}
|
137
static/data/admin_leftmenu.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
static/img/icons/Date/date.png
Normal file
After Width: | Height: | Size: 626 B |
BIN
static/img/icons/Date/date_add.png
Normal file
After Width: | Height: | Size: 703 B |
BIN
static/img/icons/Date/date_delete.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
static/img/icons/Date/date_edit.png
Normal file
After Width: | Height: | Size: 799 B |
BIN
static/img/icons/Date/date_error.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
static/img/icons/Date/date_go.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
static/img/icons/Date/date_link.png
Normal file
After Width: | Height: | Size: 764 B |
BIN
static/img/icons/Date/date_magnify.png
Normal file
After Width: | Height: | Size: 711 B |
BIN
static/img/icons/Date/date_next.png
Normal file
After Width: | Height: | Size: 688 B |
BIN
static/img/icons/Date/date_previous.png
Normal file
After Width: | Height: | Size: 720 B |
BIN
static/img/icons/application/application.png
Normal file
After Width: | Height: | Size: 464 B |
BIN
static/img/icons/application/application_add.png
Normal file
After Width: | Height: | Size: 619 B |
BIN
static/img/icons/application/application_cascade.png
Normal file
After Width: | Height: | Size: 524 B |
BIN
static/img/icons/application/application_delete.png
Normal file
After Width: | Height: | Size: 610 B |
BIN
static/img/icons/application/application_double.png
Normal file
After Width: | Height: | Size: 533 B |
BIN
static/img/icons/application/application_edit.png
Normal file
After Width: | Height: | Size: 703 B |
BIN
static/img/icons/application/application_error.png
Normal file
After Width: | Height: | Size: 656 B |
BIN
static/img/icons/application/application_form.png
Normal file
After Width: | Height: | Size: 467 B |
BIN
static/img/icons/application/application_form_add.png
Normal file
After Width: | Height: | Size: 592 B |
BIN
static/img/icons/application/application_form_delete.png
Normal file
After Width: | Height: | Size: 605 B |
BIN
static/img/icons/application/application_form_edit.png
Normal file
After Width: | Height: | Size: 714 B |
BIN
static/img/icons/application/application_form_magnify.png
Normal file
After Width: | Height: | Size: 612 B |
BIN
static/img/icons/application/application_get.png
Normal file
After Width: | Height: | Size: 581 B |
BIN
static/img/icons/application/application_go.png
Normal file
After Width: | Height: | Size: 634 B |
BIN
static/img/icons/application/application_home.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
static/img/icons/application/application_key.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
static/img/icons/application/application_lightning.png
Normal file
After Width: | Height: | Size: 656 B |
BIN
static/img/icons/application/application_link.png
Normal file
After Width: | Height: | Size: 701 B |
BIN
static/img/icons/application/application_osx.png
Normal file
After Width: | Height: | Size: 487 B |
BIN
static/img/icons/application/application_osx_terminal.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
static/img/icons/application/application_put.png
Normal file
After Width: | Height: | Size: 585 B |
BIN
static/img/icons/application/application_side_boxes.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
static/img/icons/application/application_side_contract.png
Normal file
After Width: | Height: | Size: 547 B |
BIN
static/img/icons/application/application_side_expand.png
Normal file
After Width: | Height: | Size: 581 B |
BIN
static/img/icons/application/application_side_list.png
Normal file
After Width: | Height: | Size: 510 B |
BIN
static/img/icons/application/application_side_tree.png
Normal file
After Width: | Height: | Size: 483 B |
BIN
static/img/icons/application/application_split.png
Normal file
After Width: | Height: | Size: 520 B |
BIN
static/img/icons/application/application_tile_horizontal.png
Normal file
After Width: | Height: | Size: 432 B |
BIN
static/img/icons/application/application_tile_vertical.png
Normal file
After Width: | Height: | Size: 492 B |
BIN
static/img/icons/application/application_view_columns.png
Normal file
After Width: | Height: | Size: 493 B |
BIN
static/img/icons/application/application_view_detail.png
Normal file
After Width: | Height: | Size: 576 B |
BIN
static/img/icons/application/application_view_gallery.png
Normal file
After Width: | Height: | Size: 555 B |
BIN
static/img/icons/application/application_view_icons.png
Normal file
After Width: | Height: | Size: 476 B |
BIN
static/img/icons/application/application_view_list.png
Normal file
After Width: | Height: | Size: 473 B |
BIN
static/img/icons/application/application_view_tile.png
Normal file
After Width: | Height: | Size: 465 B |
BIN
static/img/icons/application/application_xp.png
Normal file
After Width: | Height: | Size: 426 B |
BIN
static/img/icons/application/application_xp_terminal.png
Normal file
After Width: | Height: | Size: 507 B |
BIN
static/img/icons/arrow/accept.png
Normal file
After Width: | Height: | Size: 781 B |
BIN
static/img/icons/arrow/add.png
Normal file
After Width: | Height: | Size: 733 B |
BIN
static/img/icons/arrow/arrow_branch.png
Normal file
After Width: | Height: | Size: 582 B |
BIN
static/img/icons/arrow/arrow_divide.png
Normal file
After Width: | Height: | Size: 677 B |
BIN
static/img/icons/arrow/arrow_down.png
Normal file
After Width: | Height: | Size: 379 B |
BIN
static/img/icons/arrow/arrow_in.png
Normal file
After Width: | Height: | Size: 600 B |
BIN
static/img/icons/arrow/arrow_inout.png
Normal file
After Width: | Height: | Size: 551 B |