├── .gitattributes ├── .gitignore ├── README.rst ├── conf └── app.conf ├── examples └── wsgi.py ├── requirements.txt ├── schema └── mysql.sql ├── setup.py ├── t └── paginatort.py ├── zephyr ├── __init__.py ├── app.py ├── asset │ ├── css │ │ ├── admin.css │ │ ├── forms.css │ │ ├── login.css │ │ ├── notifications.css │ │ ├── reset.css │ │ └── small.css │ ├── img │ │ ├── cloud.png │ │ ├── cross.gif │ │ ├── favicon.ico │ │ ├── icons.png │ │ ├── logo.png │ │ ├── piggy.gif │ │ ├── statuses.png │ │ ├── tick.gif │ │ └── tick.png │ ├── js │ │ ├── custom-fields.js │ │ ├── dragdrop.js │ │ ├── editor.js │ │ ├── focus-mode.js │ │ ├── page-name.js │ │ ├── redirect.js │ │ ├── slug.js │ │ ├── sortable.js │ │ ├── text-resize.js │ │ ├── upload-fields.js │ │ └── zepto.js │ └── theme │ │ └── default │ │ ├── css │ │ ├── reset.css │ │ ├── small.css │ │ └── style.css │ │ ├── img │ │ ├── categories.png │ │ ├── favicon.png │ │ ├── og_image.gif │ │ └── search.png │ │ └── js │ │ └── main.js ├── autoload.py ├── boot │ ├── __init__.py │ ├── asset.py │ ├── database.py │ ├── jinja2t.py │ ├── pedis.py │ └── site.py ├── breeze │ ├── __init__.py │ ├── app.py │ ├── core.py │ ├── flash.py │ ├── hook.py │ └── patch.py ├── cmd.py ├── config │ ├── __init__.py │ ├── _config.py │ ├── errors.py │ └── hocon.py ├── feed.py ├── helper.py ├── hooks.py ├── jinja2t.py ├── lang │ ├── __init__.py │ ├── en_GB │ │ ├── __init__.py │ │ ├── category.py │ │ ├── comment.py │ │ ├── extend.py │ │ ├── global.py │ │ ├── menu.py │ │ ├── metadata.py │ │ ├── page.py │ │ ├── post.py │ │ └── user.py │ ├── zh_CN │ │ ├── __init__.py │ │ ├── category.py │ │ ├── comment.py │ │ ├── extend.py │ │ ├── global.py │ │ ├── menu.py │ │ ├── metadata.py │ │ ├── page.py │ │ ├── post.py │ │ └── user.py │ └── zh_TW │ │ ├── __init__.py │ │ ├── category.py │ │ ├── comment.py │ │ ├── extend.py │ │ ├── global.py │ │ ├── menu.py │ │ ├── metadata.py │ │ ├── page.py │ │ ├── post.py │ │ └── user.py ├── lib │ ├── __init__.py │ ├── image.py │ ├── memoize.py │ ├── paginator.py │ └── validator.py ├── log.py ├── module │ ├── __init__.py │ ├── category │ │ ├── __init__.py │ │ ├── mapper.py │ │ ├── model.py │ │ ├── thing.py │ │ └── view.py │ ├── comment │ │ ├── __init__.py │ │ ├── mapper.py │ │ ├── model.py │ │ ├── thing.py │ │ └── view.py │ ├── extend │ │ ├── __init__.py │ │ ├── mapper.py │ │ ├── model.py │ │ ├── thing.py │ │ └── view │ │ │ ├── __init__.py │ │ │ └── field.py │ ├── front │ │ ├── __init__.py │ │ ├── mixin.py │ │ └── view.py │ ├── menu │ │ ├── __init__.py │ │ ├── thing.py │ │ └── view.py │ ├── page │ │ ├── __init__.py │ │ ├── mapper.py │ │ ├── model.py │ │ ├── thing.py │ │ └── view.py │ ├── post │ │ ├── __init__.py │ │ ├── mapper.py │ │ ├── model.py │ │ ├── thing.py │ │ └── view.py │ ├── storage │ │ ├── __init__.py │ │ ├── mapper.py │ │ ├── model.py │ │ └── thing.py │ └── user │ │ ├── __init__.py │ │ ├── mapper.py │ │ ├── model.py │ │ ├── thing.py │ │ └── view.py ├── options.py ├── orm.py ├── pedis.py ├── session.py ├── template │ ├── admin │ │ ├── 403.html │ │ ├── category │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ └── index.html │ │ ├── comment │ │ │ ├── edit.html │ │ │ └── index.html │ │ ├── extend │ │ │ ├── field │ │ │ │ ├── add.html │ │ │ │ ├── edit.html │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── metadata │ │ │ │ └── edit.html │ │ │ └── plugin │ │ │ │ └── index.html │ │ ├── layout │ │ │ ├── edit.html │ │ │ ├── footer.html │ │ │ └── header.html │ │ ├── menu │ │ │ └── index.html │ │ ├── page │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ └── index.html │ │ ├── post │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ └── index.html │ │ └── user │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ ├── index.html │ │ │ └── login.html │ ├── component │ │ └── views.html │ └── theme │ │ └── default │ │ ├── 403.html │ │ ├── 404.html │ │ ├── article.html │ │ ├── body.html │ │ ├── footer.html │ │ ├── header.html │ │ ├── layout.html │ │ ├── page.html │ │ ├── posts.html │ │ └── search.html └── util.py └── zephyrd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | conf/test.conf 6 | *.pydevproject 7 | .project 8 | .metadata 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Oo]bj/ 51 | 52 | # MSTest test Results 53 | [Tt]est[Rr]esult*/ 54 | [Bb]uild[Ll]og.* 55 | 56 | *_i.c 57 | *_p.c 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.log 78 | *.scc 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | *.ncrunch* 108 | .*crunch*.local.xml 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.Publish.xml 128 | *.pubxml 129 | 130 | # NuGet Packages Directory 131 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 132 | #packages/ 133 | 134 | # Windows Azure Build Output 135 | csx 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.[Pp]ublish.xml 150 | *.pfx 151 | *.publishsettings 152 | 153 | # RIA/Silverlight projects 154 | Generated_Code/ 155 | 156 | # Backup & report files from converting an old project file to a newer 157 | # Visual Studio version. Backup files are not needed, because we have git ;-) 158 | _UpgradeReport_Files/ 159 | Backup*/ 160 | UpgradeLog*.XML 161 | UpgradeLog*.htm 162 | 163 | # SQL Server files 164 | App_Data/*.mdf 165 | App_Data/*.ldf 166 | 167 | ############# 168 | ## Windows detritus 169 | ############# 170 | 171 | # Windows image file caches 172 | Thumbs.db 173 | ehthumbs.db 174 | 175 | # Folder config file 176 | Desktop.ini 177 | 178 | # Recycle Bin used on file shares 179 | $RECYCLE.BIN/ 180 | 181 | # Mac crap 182 | .DS_Store 183 | 184 | 185 | ############# 186 | ## Python 187 | ############# 188 | 189 | *.py[co] 190 | 191 | # Packages 192 | *.egg 193 | *.egg-info 194 | dist/ 195 | build/ 196 | eggs/ 197 | parts/ 198 | var/ 199 | sdist/ 200 | develop-eggs/ 201 | .installed.cfg 202 | 203 | # Installer logs 204 | pip-log.txt 205 | 206 | # Unit test / coverage reports 207 | .coverage 208 | .tox 209 | 210 | #Translations 211 | *.mo 212 | 213 | #Mr Developer 214 | .mr.developer.cfg 215 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | # Zehpyr config 2 | 3 | 4 | tornado { 5 | host = "localhost" 6 | port = 8888 7 | } 8 | 9 | # theme = "default" 10 | # languge = "en_GB" 11 | 12 | content_path = "$upload_path" # required to store uploaded assets 13 | 14 | secert_key = "7oGwHH8NQDKn9hL12Gak9G/MEjZZYk4PsAxqKU4cJoY=" 15 | 16 | debug = off 17 | 18 | db { 19 | passwd = "zephyr" 20 | user = "zephyr" 21 | host = "localhost" 22 | db = "zephyr" 23 | } 24 | 25 | 26 | redis { 27 | host = "localhost" 28 | port = 6379 29 | } 30 | 31 | //asset { 32 | // url_prefix = "/assets/" // asset url path prefix 33 | // path = "./nodejs/dist/assets" # static files path 34 | //} 35 | 36 | 37 | jinja2 { 38 | cache_path = "./cache" # jinja2 module cache path, comments it if wanna disable 39 | auto_reload = on 40 | } -------------------------------------------------------------------------------- /examples/wsgi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import tornado.wsgi 18 | 19 | from cherrypy.wsgiserver import CherryPyWSGIServer 20 | 21 | from zephyr.app import ZephyrApp 22 | from zephyr.config import ConfigFactory 23 | 24 | if __name__ == "__main__": 25 | config = ConfigFactory.parseFile('$your_conf', pystyle=True) # or use SelectConfig 26 | app = ZephyrApp(config) 27 | wsgi_app = tornado.wsgi.WSGIAdapter(app) 28 | server = CherryPyWSGIServer( 29 | (config.get('cherry.host', 'localhost'), config.get('cherry.port', 8888)), 30 | wsgi_app, 31 | server_name='Zephyr', 32 | numthreads=30) 33 | try: 34 | server.start() 35 | except KeyboardInterrupt: 36 | server.stop() 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dbpy=>0.1.1 2 | Jinja2=>2.7.3 3 | Markdown=>2.6.1 4 | MarkupSafe=>0.23 5 | MySQL-python=>1.2.5 6 | Pillow==2.7.0 7 | tornado>=3.0 8 | redis>=2.4.9 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import sys 3 | from zephyr import __version__ 4 | import os 5 | 6 | def include_path(datas, path): 7 | for root, dirs, files in os.walk(path): 8 | base, ex = path.split(os.path.sep, 1) 9 | for f in files: 10 | name, ext = f.split('.') 11 | if ext in ('css', 'js', 'html', 'ico', 'png', 'gif'): 12 | datas.append(os.path.join(ex, f)) 13 | for d in dirs: 14 | include_path(datas, os.path.join(path, d)) 15 | 16 | 17 | def _package_data(): 18 | datas = [] 19 | path = os.path.join(os.path.dirname(__file__), 'zephyr') 20 | include_path(datas, os.path.join(path, 'asset')) 21 | include_path(datas ,os.path.join(path, 'view')) 22 | return datas 23 | 24 | setup( 25 | name = 'zephyr', 26 | version = __version__, 27 | author = "Thomas", 28 | author_email='lyanghwy@gmail.com', 29 | description = "A Blog Cms backed by Tornado&MySQL in Python", 30 | license = "http://www.apache.org/licenses/LICENSE-2.0", 31 | keywords = "A Blog Cms backed by Tornado&MySQL in Python", 32 | url='https://github.com/whiteclover/Zephyr', 33 | long_description=open('README.rst').read(), 34 | packages=find_packages(exclude=['t', 't.*']), 35 | zip_safe=False, 36 | include_package_data=True, 37 | package_data = { 38 | # Non-.py files to distribute as part of each package 39 | 'zephyr': _package_data() 40 | }, 41 | install_requires = ['setuptools', 'tornado', 'markdown', 'Jinja2', 'dbpy', 'pillow', 'redis'], 42 | test_suite='unittests', 43 | classifiers=( 44 | "Development Status :: Production/Alpha", 45 | "License :: Apache Software License", 46 | "Natural Language :: English", 47 | "Programming Language :: Python", 48 | "Programming Language :: Python :: 2.7", 49 | "Topic :: Blog Cms" 50 | ) 51 | ) 52 | -------------------------------------------------------------------------------- /t/paginatort.py: -------------------------------------------------------------------------------- 1 | from zephyr.lib.paginator import Paginator 2 | import unittest 3 | 4 | 5 | class PaginatorTest(unittest.TestCase): 6 | 7 | def setUp(self): 8 | result = [_ for _ in range(1, 10)] 9 | self.p = Paginator(result, 100, 10, 5, '/test') 10 | 11 | def test_next_link(self): 12 | self.assertEqual(str(self.p.next_link('next')), 'next') 13 | self.p.page = 22 14 | self.assertEqual(str(self.p.next_link('next')), '') 15 | 16 | def test_pre_link(self): 17 | self.assertEqual(str(self.p.pre_link('pre')), 'pre') 18 | self.p.page = 1 19 | self.assertEqual(str(self.p.pre_link('pre')), '') 20 | 21 | def test_iter(self): 22 | for _ in range(1, 9): 23 | next(self.p) 24 | self.assertEqual(next(self.p), 9) 25 | self.assertEqual(self.p._index, 9) 26 | self.assertRaises(StopIteration, lambda: next(self.p)) 27 | 28 | def test_len(self): 29 | self.assertEqual(9, len(self.p)) 30 | 31 | def test_links(self): 32 | 33 | self.assertTrue(self.p.links().startswith( 34 | 'FirstPrevious7')) 35 | self.p.page = 0 36 | self.assertEqual( 37 | self.p.links(), '1234Next Last') 38 | self.p.page = 22 39 | self.assertEqual( 40 | self.p.links(), 'FirstPrevious1920') 41 | 42 | 43 | if __name__ == '__main__': 44 | unittest.main(verbosity=2) -------------------------------------------------------------------------------- /zephyr/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | __version__ = '0.1.0a' -------------------------------------------------------------------------------- /zephyr/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import os 18 | import os.path 19 | 20 | 21 | import db 22 | from zephyr import pedis 23 | from zephyr.autoload import AutoLoader 24 | from zephyr.jinja2t import Jinja2Loader 25 | import zephyr.breeze 26 | 27 | import zephyr.hooks 28 | 29 | class ZephyrApp(zephyr.breeze.Application): 30 | 31 | def __init__(self, config): 32 | self.config = config 33 | template_loader = self.boot_template() 34 | self.boot_database() 35 | 36 | self.autoload = AutoLoader() 37 | self.autoload.autoload() 38 | 39 | settings = dict( 40 | static_path=config.get("asset.path", os.path.join(os.path.dirname(__file__), "asset")), 41 | static_url_prefix=config.get("asset.url_prefix", "/assets/"), 42 | template_loader=template_loader, 43 | debug=self.config.get("debug", False), 44 | cookie_secret=self.config.get("secert_key", None) 45 | ) 46 | zephyr.breeze.Application.__init__( 47 | self, self.autoload.routes, **settings) 48 | 49 | self.hooks.attach("on_start_request", zephyr.hooks.on_load_session) 50 | self.error_page(404, zephyr.hooks.on_not_found) 51 | 52 | def boot_template(self): 53 | template_loader = Jinja2Loader( 54 | os.path.join(os.path.dirname(__file__), "template"), 55 | self.config.get("jinja2.cache_path"), 56 | self.config.get("jinja2.cache_size", -1), 57 | auto_reload=self.config.get("jinja2.auto_reload", False)) 58 | 59 | from zephyr import lang 60 | from zephyr.lang import text 61 | from zephyr.helper import categories, menus, site 62 | from zephyr.util import markdown 63 | 64 | lang.setup(self.config.get('language', 'en_GB')) 65 | 66 | template_loader.env.globals.update(__=text) 67 | template_loader.env.globals.update(site_categories=categories, menus=menus) 68 | template_loader.env.globals.update(site=site, enumerate=enumerate) 69 | 70 | template_loader.env.filters['markdown'] = markdown 71 | return template_loader 72 | 73 | def boot_database(self): 74 | db.setup(self.config.get('db')) 75 | pedis.setup(**self.config.get('redis')) 76 | 77 | 78 | import logging 79 | import tornado.ioloop 80 | from zephyr import cmd 81 | from zephyr.boot import Bootable 82 | from zephyr.log import log_config 83 | 84 | LOG = logging.getLogger('app') 85 | 86 | class ZephyrService(Bootable): 87 | 88 | def __init__(self): 89 | self.config = cmd.parse_cmd("zephyrd") 90 | log_config("zephyr", self.config.get("debug", False)) 91 | self.application = ZephyrApp(self.config) 92 | 93 | def startup(self): 94 | try: 95 | port = self.config.get("tornado.port", 8888) 96 | host = self.config.get("tornado.host", 'localhost') 97 | LOG.info("Starting zephyr on %s:%s", host, port) 98 | self.application.listen(port, host) 99 | tornado.ioloop.IOLoop.instance().start() 100 | except KeyboardInterrupt as e: 101 | self.shutdown() 102 | 103 | def shutdown(self): 104 | tornado.ioloop.IOLoop.instance().stop() 105 | -------------------------------------------------------------------------------- /zephyr/asset/css/login.css: -------------------------------------------------------------------------------- 1 | /* 2 | Login 3 | */ 4 | body.login { 5 | position: absolute; 6 | top: 25%; 7 | left: 50%; 8 | margin-left: -150px; 9 | background: #444f5f; 10 | } 11 | 12 | .login .wrap, .login .content { 13 | width: 300px; 14 | } 15 | .login h1 { 16 | padding-bottom: 25px; 17 | 18 | font-size: 25px; 19 | line-height: 60px; 20 | font-weight: lighter; 21 | color: #fff; 22 | } 23 | 24 | .login label { 25 | display: none; 26 | } 27 | 28 | .login fieldset { 29 | border: none; 30 | } 31 | 32 | .login input { 33 | width: 300px; 34 | margin-bottom: 20px; 35 | 36 | padding: 14px 16px; 37 | } 38 | 39 | .login .buttons a { 40 | float: right; 41 | font-size: 13px; 42 | line-height: 38px; 43 | } 44 | 45 | .login .buttons button { 46 | float: left; 47 | } 48 | 49 | .login a { 50 | color: #8491a5; 51 | } 52 | 53 | .login a:hover { 54 | color: #fff; 55 | } 56 | 57 | .login button { 58 | background: #2f3744; 59 | color: #96a4bb; 60 | font-size: 13px; 61 | font-weight: 500; 62 | } 63 | 64 | .login .notification { 65 | width: 300px; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /zephyr/asset/css/notifications.css: -------------------------------------------------------------------------------- 1 | 2 | .notifications { 3 | margin-bottom: 10px; 4 | } 5 | 6 | .notifications .notice, .notifications .error, .notifications .success { 7 | padding: 10px 18px; 8 | margin-bottom: 20px; 9 | 10 | font-size: 13px; 11 | line-height: 21px; 12 | font-weight: 500; 13 | 14 | border-radius: 5px; 15 | } 16 | 17 | .notifications .notice { 18 | color: #fff; 19 | background: #578cd9; 20 | } 21 | 22 | .notifications .error { 23 | color: #fff; 24 | background: #d34937; 25 | } 26 | 27 | .notifications .success { 28 | color: #fff; 29 | background: #64a524; 30 | } 31 | 32 | 33 | .header .notifications { 34 | position: absolute; 35 | left: 55%; 36 | top: 82px; 37 | z-index: 1200; 38 | width: 320px; 39 | } 40 | .header .page .notifications { 41 | left: 48%; 42 | } 43 | .header .notifications div:after { 44 | content: ''; 45 | position: absolute; 46 | display: block; 47 | top: -6px; 48 | right: 50px; 49 | 50 | border-bottom: 6px solid #64a524; 51 | border-left: 6px solid transparent; 52 | border-right: 6px solid transparent; 53 | } 54 | 55 | .header .notifications .error:after { 56 | border-bottom-color: #d34937; 57 | } -------------------------------------------------------------------------------- /zephyr/asset/css/reset.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | 6 | -webkit-font-smoothing: antialiased; 7 | 8 | -webkit-box-sizing: border-box; 9 | -moz-box-sizing: border-box; 10 | box-sizing: border-box; 11 | } 12 | 13 | ::selection { 14 | background: #606d80; 15 | color: #fff; 16 | } 17 | 18 | ::-webkit-input-placeholder { 19 | color: #bec8d4; 20 | } 21 | 22 | ::-moz-placeholder { 23 | color: #bec8d4; 24 | } 25 | 26 | ::placeholder { 27 | color: #bec8d4; 28 | } 29 | -------------------------------------------------------------------------------- /zephyr/asset/css/small.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | width: 100%; 3 | } 4 | 5 | .wrap { 6 | width: 90%; 7 | } 8 | 9 | .top nav { 10 | float: none; 11 | overflow: hidden; 12 | } 13 | 14 | .top .btn { 15 | display: none; 16 | } 17 | 18 | .list, .list li { 19 | width: 100%; 20 | } 21 | 22 | .list li { 23 | float: none; 24 | margin-right: 0; 25 | } 26 | 27 | .list li p { 28 | display: none; 29 | } 30 | 31 | hgroup h1 { 32 | line-height: 80px; 33 | margin: 0; 34 | float: none; 35 | } 36 | 37 | hgroup nav { 38 | float: none; 39 | line-height: 80px; 40 | } -------------------------------------------------------------------------------- /zephyr/asset/img/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/cloud.png -------------------------------------------------------------------------------- /zephyr/asset/img/cross.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/cross.gif -------------------------------------------------------------------------------- /zephyr/asset/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/favicon.ico -------------------------------------------------------------------------------- /zephyr/asset/img/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/icons.png -------------------------------------------------------------------------------- /zephyr/asset/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/logo.png -------------------------------------------------------------------------------- /zephyr/asset/img/piggy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/piggy.gif -------------------------------------------------------------------------------- /zephyr/asset/img/statuses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/statuses.png -------------------------------------------------------------------------------- /zephyr/asset/img/tick.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/tick.gif -------------------------------------------------------------------------------- /zephyr/asset/img/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/img/tick.png -------------------------------------------------------------------------------- /zephyr/asset/js/custom-fields.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend attribute selection 3 | * 4 | * Show/hide fields depending on type 5 | */ 6 | $(function() { 7 | var select = $('#field'), attrs = $('.hide'); 8 | 9 | var update = function() { 10 | var value = select.val(); 11 | 12 | attrs.hide(); 13 | 14 | if(value == 'image') { 15 | attrs.show(); 16 | } 17 | else if(value == 'file') { 18 | $('.attributes_type').show(); 19 | } 20 | }; 21 | 22 | select.bind('change', update); 23 | 24 | update(); 25 | }); -------------------------------------------------------------------------------- /zephyr/asset/js/dragdrop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Drag and Drop upload 3 | * 4 | * Allows the drag and drop of single files into posts 5 | */ 6 | $(function() { 7 | var zone = $(document), body = $('body'); 8 | var allowed = ['text/css', 'text/javascript', 'application/javascript', 'text/x-markdown']; 9 | 10 | var cancel = function(event) { 11 | event.preventDefault(); 12 | return false; 13 | }; 14 | 15 | var open = function(event) { 16 | event.preventDefault(); 17 | body.addClass('draggy'); 18 | return false; 19 | }; 20 | 21 | var close = function(event) { 22 | event.preventDefault(); 23 | body.removeClass('draggy'); 24 | return false; 25 | }; 26 | 27 | var drop = function(event) { 28 | event.preventDefault(); 29 | 30 | var files = event.target.files || event.dataTransfer.files; 31 | 32 | for(var i = 0; i < files.length; i++) { 33 | var file = files.item(i); 34 | 35 | if(allowed.indexOf(file.type) !== -1) { 36 | transfer(file); 37 | } 38 | } 39 | 40 | body.removeClass('draggy'); 41 | 42 | return false; 43 | }; 44 | 45 | var transfer = function(file) { 46 | var reader = new FileReader(); 47 | reader.file = file; 48 | reader.callback = complete; 49 | reader.onload = reader.callback; 50 | reader.readAsText(file); 51 | }; 52 | 53 | var complete = function() { 54 | if(['text/css'].indexOf(this.file.type) !== -1) { 55 | $('textarea[name=css]').val(this.result).parent().show(); 56 | } 57 | 58 | if(['text/javascript', 'application/javascript'].indexOf(this.file.type) !== -1) { 59 | $('textarea[name=js]').val(this.result).parent().show(); 60 | } 61 | 62 | if(['text/x-markdown'].indexOf(this.file.type) !== -1) { 63 | var textarea = $('textarea[name=html]'), value = textarea.val(); 64 | 65 | textarea.val(this.result).trigger('keydown'); 66 | } 67 | }; 68 | 69 | if(window.FileReader && window.FileList && window.File) { 70 | zone.on('dragover', open); 71 | zone.on('dragenter', cancel); 72 | zone.on('drop', drop); 73 | zone.on('dragleave', cancel); 74 | zone.on('dragexit', close); 75 | 76 | body.append('
Upload your file
'); 77 | 78 | // hide drag/drop inputs until populated 79 | $('textarea[name=css],textarea[name=js]').each(function(index, item) { 80 | var element = $(item); 81 | 82 | if(element.val() == '') { 83 | element.parent().hide(); 84 | } 85 | }); 86 | } 87 | }); -------------------------------------------------------------------------------- /zephyr/asset/js/focus-mode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Focus mode for post and page main textarea 3 | */ 4 | $(function() { 5 | var doc = $(document), html = $('html'), body = html.children('body'); 6 | 7 | var Focus = { 8 | // Our element to focus 9 | target: $('textarea[name=html], textarea[name=content]'), 10 | exitSpan: '#exit-focus', 11 | 12 | enter: function() { 13 | html.addClass('focus'); 14 | 15 | if( ! body.children(Focus.exitSpan).length) { 16 | body.append('Exit focus mode (ESC)'); 17 | } 18 | 19 | body.children(Focus.exitSpan).css('opacity', 0).animate({opacity: 1}, 250); 20 | 21 | // Set titles and placeholders 22 | Focus.target.placeholder = (Focus.target.placeholder || '').split('.')[0] + '.'; 23 | }, 24 | 25 | exit: function() { 26 | body.children(Focus.exitSpan).animate({opacity: 0}, 250); 27 | html.removeClass('focus'); 28 | } 29 | }; 30 | 31 | // Bind textarea events 32 | Focus.target.focus(Focus.enter).blur(Focus.exit); 33 | 34 | // Bind key events 35 | doc.on('keyup', function(event) { 36 | // Pressing the "f" key 37 | if(event.keyCode == 70) { 38 | Focus.enter(); 39 | } 40 | 41 | // Pressing the Escape key 42 | if(event.keyCode == 27) { 43 | Focus.exit(); 44 | } 45 | }); 46 | }); -------------------------------------------------------------------------------- /zephyr/asset/js/page-name.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mirrors the page title into the page name field which is use in the menus 3 | */ 4 | $(function(input, output) { 5 | var input = $('input[name=title]'), output = $('input[name=name]'); 6 | var changed = false; 7 | 8 | output.bind('keyup', function() { 9 | changed = true; 10 | }); 11 | 12 | input.bind('keyup', function() { 13 | if( ! changed) output.val(input.val()); 14 | }); 15 | }); -------------------------------------------------------------------------------- /zephyr/asset/js/redirect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Toggles the redirect field in pages 3 | */ 4 | $(function() { 5 | var fieldset = $('fieldset.redirect'), 6 | input = $('input[name=redirect]'), 7 | btn = $('button.secondary'); 8 | 9 | var toggle = function() { 10 | fieldset.toggleClass('show'); 11 | return false; 12 | }; 13 | 14 | btn.bind('click', toggle); 15 | 16 | // Hide the input if you get rid of the content within. 17 | input.change(function(){ 18 | if(input.val() === '') fieldset.removeClass('show'); 19 | }); 20 | 21 | // Show the redirect field if it isn't empty. 22 | if(input.val() !== '') fieldset.addClass('show'); 23 | }); -------------------------------------------------------------------------------- /zephyr/asset/js/slug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Format title into a slug value after each keypress 3 | * Disabled if the slug is manually changed 4 | */ 5 | $(function() { 6 | var input = $('input[name=title]'), output = $('input[name=slug]'); 7 | var changed = false; 8 | 9 | var slugify = function(str) { 10 | str = str.replace(/^\s+|\s+$/g, '').toLowerCase(); 11 | 12 | // remove accents 13 | var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;", to = "aaaaeeeeiiiioooouuuunc------"; 14 | 15 | for(var i = 0, l = from.length; i < l; i++) { 16 | str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)); 17 | } 18 | 19 | return str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars 20 | .replace(/\s+/g, '-') // collapse whitespace and replace by - 21 | .replace(/-+/g, '-'); // collapse dashes 22 | } 23 | 24 | output.bind('keyup', function() { 25 | changed = true; 26 | }); 27 | 28 | input.bind('keyup', function() { 29 | if( ! changed) output.val(slugify(input.val())); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /zephyr/asset/js/sortable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Zepto sortable plugin using html5 drag and drop api. 3 | */ 4 | ;(function($) { 5 | $.fn.sortable = function(options) { 6 | 7 | var defaults = { 8 | element: 'li', 9 | dropped: function() {} 10 | }; 11 | 12 | var settings = $.extend({}, defaults, options); 13 | var sortables = $(this).find(settings.element); 14 | var dragsrc; 15 | 16 | var dragstart = function(event) { 17 | $(this).addClass('moving'); 18 | 19 | dragsrc = this; 20 | 21 | event.dataTransfer.effectAllowed = 'move'; 22 | event.dataTransfer.setData('text/html', this.innerHTML); 23 | }; 24 | 25 | var dragenter = function() { 26 | $(this).addClass('over'); 27 | } 28 | 29 | var dragleave = function() { 30 | $(this).removeClass('over'); 31 | }; 32 | 33 | var dragover = function(event) { 34 | event.preventDefault(); 35 | event.stopPropagation(); 36 | 37 | event.dataTransfer.dropEffect = 'move'; 38 | }; 39 | 40 | var drop = function(event) { 41 | event.preventDefault(); 42 | event.stopPropagation(); 43 | 44 | if (dragsrc != this) { 45 | dragsrc.innerHTML = this.innerHTML; 46 | 47 | this.innerHTML = event.dataTransfer.getData('text/html'); 48 | } 49 | 50 | settings.dropped(); 51 | }; 52 | 53 | var dragend = function() { 54 | $(this).removeClass('moving'); 55 | sortables.removeClass('over'); 56 | }; 57 | 58 | sortables.on('dragstart', dragstart); 59 | sortables.on('dragenter', dragenter); 60 | sortables.on('dragover', dragover); 61 | sortables.on('dragleave', dragleave); 62 | sortables.on('drop', drop); 63 | sortables.on('dragend', dragend); 64 | }; 65 | }(Zepto)); -------------------------------------------------------------------------------- /zephyr/asset/js/text-resize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Textarea auto resize 3 | */ 4 | $(function() { 5 | var $text = $('textarea').first(); 6 | 7 | function resize(e) { 8 | var bodyScrollPos = $('body').prop('scrollTop'); 9 | $text.height('auto'); 10 | $text.height($text.prop('scrollHeight') + 'px'); 11 | $('body').prop('scrollTop', bodyScrollPos); 12 | } 13 | 14 | /* 0-timeout to get the already changed text */ 15 | function delayedResize (e) { 16 | window.setTimeout(function(){ 17 | resize(e); 18 | }, 0); 19 | } 20 | 21 | $text.on('change', resize); 22 | $text.on('cut paste drop keydown', delayedResize); 23 | 24 | $text.focus(); 25 | $text.select(); 26 | resize(); 27 | }); -------------------------------------------------------------------------------- /zephyr/asset/js/upload-fields.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Populate placeholder when user selects a file to upload 3 | */ 4 | $(function() { 5 | var basename = function(path) { 6 | return path.replace(/\\/g,'/').replace(/.*\//, ''); 7 | }; 8 | 9 | $('input[type=file]').bind('change', function() { 10 | var input = $(this), placeholder = input.parent().parent().find('.current-file'); 11 | 12 | placeholder.html(basename(input.val())); 13 | }); 14 | }); -------------------------------------------------------------------------------- /zephyr/asset/theme/default/css/reset.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Anchor, default reset 3 | */ 4 | * { 5 | margin: 0; 6 | padding: 0; 7 | 8 | -webkit-font-smoothing: antialiased; 9 | 10 | /* Don't count padding and borders towards widths */ 11 | -webkit-box-sizing: border-box; 12 | -moz-box-sizing: border-box; 13 | box-sizing: border-box; 14 | } 15 | 16 | /** 17 | * Typographic reset 18 | */ 19 | body { 20 | /* Use a serif font for nice readability, but not Times */ 21 | font: 17px/26px Skolar, Tisa, "Chaparral Pro", Merriweather, Georgia, serif; 22 | } 23 | 24 | h1, h2, h3, h4, h5, #logo, #top, .slidey b, .slidey label, .counter, input, textarea, button, .pagination { 25 | font-family: "Helvetica Neue", sans-serif; 26 | font-weight: 300; 27 | } 28 | 29 | pre, code, .mono { 30 | font: 12px/19px "Anonymous Pro", Consolas, monospace; 31 | padding: 0 2px; 32 | } 33 | 34 | p { 35 | padding-bottom: 15px; 36 | } 37 | 38 | pre { 39 | padding: 15px 20px; 40 | margin-bottom: 20px; 41 | 42 | border-radius: 5px; 43 | white-space: pre-wrap; 44 | } 45 | 46 | img { 47 | max-width: 100%; 48 | height: auto; 49 | } 50 | 51 | a { 52 | text-decoration: none; 53 | } 54 | a img { 55 | border: none; 56 | } 57 | 58 | /** 59 | * Layout reset 60 | */ 61 | .wrap { 62 | min-width: 280px; 63 | max-width: 750px; 64 | width: 60%; 65 | 66 | margin: 0 auto; 67 | } 68 | 69 | /** 70 | * Default colours 71 | */ 72 | body, .items > li:first-child { 73 | color: #6e7886; 74 | } 75 | a, .items h1 a:hover { 76 | color: #4075c3; 77 | } 78 | a:hover { 79 | color: #1e4d92; 80 | } 81 | 82 | pre, .hilite, mark { 83 | background: #f9f6ea; 84 | color: #8b7c65; 85 | text-shadow: 0 1px 0 rgba(255,255,255,.2); 86 | } 87 | 88 | input, textarea { 89 | color: #697281; 90 | } 91 | ::-webkit-input-placeholder { 92 | color: #b2b9c5; 93 | } 94 | :-moz-placeholder, :placeholder { 95 | color: #b2b9c5; 96 | } 97 | 98 | .error, .success { 99 | padding: 20px 30px; 100 | margin-bottom: 30px; 101 | 102 | background: #e25d47; 103 | color: #fff; 104 | 105 | border-radius: 5px; 106 | } 107 | .success { 108 | background: #88be33; 109 | } 110 | .error p, .success p { 111 | float: none !important; 112 | width: 100%; 113 | padding: 0; 114 | margin: 0 !important; 115 | } 116 | 117 | /** 118 | * Transitions and animations 119 | */ 120 | a, a img { 121 | -webkit-transition: opacity .2s, color .2s; 122 | -moz-transition: opacity .2s, color .2s; 123 | transition: opacity .2s, color .2s; 124 | } -------------------------------------------------------------------------------- /zephyr/asset/theme/default/css/small.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Small device CSS, mostly to fix alignment 3 | */ 4 | 5 | #top { 6 | position: relative; 7 | } 8 | #top .tray { 9 | position: absolute; 10 | right: 10px; 11 | top: 10px; 12 | } 13 | #top #logo, #top ul { 14 | float: none; 15 | } 16 | #top ul li { 17 | padding: 10px 15px 0 0; 18 | } 19 | 20 | .slidey form, .slidey aside { 21 | float: none; 22 | width: 100%; 23 | } 24 | .slidey form { 25 | margin-bottom: 25px; 26 | } 27 | 28 | #comment p, .footnote { 29 | float: none; 30 | width: 100%; 31 | 32 | white-space: normal; 33 | } 34 | 35 | #bottom { 36 | padding: 20px 0; 37 | } 38 | #bottom small { 39 | display: block; 40 | } 41 | #bottom ul { 42 | overflow: hidden; 43 | float: none; 44 | margin-bottom: 15px; 45 | } 46 | #bottom li { 47 | padding: 15px 15px 0 0; 48 | } -------------------------------------------------------------------------------- /zephyr/asset/theme/default/img/categories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/theme/default/img/categories.png -------------------------------------------------------------------------------- /zephyr/asset/theme/default/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/theme/default/img/favicon.png -------------------------------------------------------------------------------- /zephyr/asset/theme/default/img/og_image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/theme/default/img/og_image.gif -------------------------------------------------------------------------------- /zephyr/asset/theme/default/img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/asset/theme/default/img/search.png -------------------------------------------------------------------------------- /zephyr/asset/theme/default/js/main.js: -------------------------------------------------------------------------------- 1 | var White = { 2 | init: function() { 3 | White.slidey = $('.slidey'); 4 | White.keys = []; 5 | 6 | // Uh, bind to the resizing of the window? 7 | $(window).resize(White.bindResize).trigger('resize'); 8 | 9 | // Re-/Set keys 10 | $(window).on('keyup', White.keyup); 11 | $(window).on('keydown', White.keydown); 12 | 13 | // Set up the toggle link 14 | White.linky = $('.linky').on('click', White.toggleSlidey); 15 | 16 | // Hide the thingymabob 17 | setTimeout(function() { 18 | // Set up the slidey panel 19 | White.hideSlidey(); 20 | 21 | $('body').addClass('js-enabled'); 22 | }, 10); 23 | 24 | // Listen for search link 25 | $('a[href="#search"]').click(function() { 26 | if(!White.linky.hasClass('active')) { 27 | return White.toggleSlidey.call(White.linky); 28 | } 29 | }); 30 | }, 31 | 32 | keyup: function(event) { 33 | White.keys[event.keyCode] = false; 34 | }, 35 | 36 | keydown: function(event) { 37 | White.keys[event.keyCode] = true; 38 | 39 | // ctrl + shift + f => show Slidey and/or focus search bar 40 | if(White.keys[17] && White.keys[16] && White.keys[70]) { 41 | event.preventDefault(); 42 | 43 | White.showSlidey.call(White.linky); 44 | $('input[type="search"]').focus(); 45 | } 46 | 47 | // esc => hide Slidey 48 | if(White.keys[27]) { 49 | event.preventDefault(); 50 | 51 | White.hideSlidey(); 52 | $('input[type="search"]').blur(); 53 | } 54 | }, 55 | 56 | hideSlidey: function() { 57 | White.slidey.css('margin-top', this._slideyHeight); 58 | White.linky && White.linky.removeClass('active'); 59 | 60 | return this; 61 | }, 62 | 63 | showSlidey: function() { 64 | White.slidey.css('margin-top', 0); 65 | White.linky && White.linky.addClass('active'); 66 | 67 | return this; 68 | }, 69 | 70 | toggleSlidey: function() { 71 | var self = White; 72 | var me = $(this); 73 | 74 | me.toggleClass('active'); 75 | self.slidey.css('margin-top', me.hasClass('active') ? 0 : self._slideyHeight); 76 | 77 | return false; 78 | }, 79 | 80 | bindResize: function() { 81 | White._slideyHeight = -(White.slidey.height() + 1); 82 | White.hideSlidey(); 83 | } 84 | }; 85 | 86 | // And bind loading 87 | $(White.init); 88 | -------------------------------------------------------------------------------- /zephyr/boot/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import os 18 | 19 | 20 | class BootOptions(object): 21 | 22 | def __init__(self, options): 23 | self.options = options 24 | self.mod_prefix = 'zephyr.boot.' 25 | self.auto_boot() 26 | 27 | def auto_boot(self): 28 | current_path = os.path.dirname(__file__) 29 | for name in os.listdir(current_path): 30 | path = os.path.join(current_path, name) 31 | prefix, ext = name.split(".") 32 | if ext == 'py' and prefix != '__init__': 33 | boot_path = self.mod_prefix + prefix + '.' + prefix.capitalize() + 'Boot' 34 | else: 35 | continue 36 | boot = self._import_boot(boot_path) 37 | if boot: 38 | boot(self.options) 39 | 40 | def _import_boot(self, module2object): 41 | try: 42 | d = module2object.rfind(".") 43 | menu_func = module2object[d + 1: len(module2object)] 44 | m = __import__(module2object[0:d], globals(), locals(), [menu_func]) 45 | return getattr(m, menu_func, None) 46 | except ImportError: 47 | return None 48 | 49 | 50 | class Bootable(object): 51 | 52 | def startup(self): 53 | raise NotImplementedError("Must implement startup in subclass") 54 | 55 | def suhtdown(self): 56 | raise NotImplementedError("Must implement shutdown in subclass") 57 | -------------------------------------------------------------------------------- /zephyr/boot/asset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import os 18 | 19 | 20 | class AssetBoot(object): 21 | 22 | def __init__(self, options=None): 23 | self.config(options) 24 | 25 | def config(self, options=None): 26 | dirname = os.path.dirname 27 | path = dirname(dirname(os.path.normpath(__file__))) 28 | options = options or self.options 29 | group = options.group("Asset settings") 30 | _ = group.define 31 | _('--asset.url_prefix', default='/assets/', help='Asset url path prefix: (default %(default)r)') 32 | _('--asset.path', default=os.path.join(path, "asset"), help='Asset files path (default %(default)r)') 33 | -------------------------------------------------------------------------------- /zephyr/boot/database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | class DatabaseBoot(object): 19 | 20 | def __init__(self, options=None): 21 | self.config(options) 22 | 23 | def config(self, options=None): 24 | options = options or self.options 25 | with options.group("DB settings") as group: 26 | group.define('--db.db', default='zephyr', help='The database name (default %(default)r)') 27 | group.define('--db.host', default='localhost', help='The host of the database (default %(default)r)') 28 | group.define('--db.user', default='zephyr', help='The user of the database (default %(default)r)') 29 | group.define('--db.passwd', default='zephyr', help='The password of the database (default %(default)r)') 30 | group.define('--db.port', default=3306, help='The port of the database (default %(default)r)', type=int) 31 | -------------------------------------------------------------------------------- /zephyr/boot/jinja2t.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | class Jinja2tBoot(object): 19 | 20 | def __init__(self, options=None): 21 | self.config(options) 22 | 23 | def config(self, options=None): 24 | options = options or self.options 25 | group = options.group("Jinja2 settings") 26 | _ = group.define 27 | _('--jinja2.cache_path', default=None, help='Jinja2 cache code byte path: (default %(default)r)') 28 | _('--jinja2.cache_size', default=-1, type=int, help='Jinja2 cache size: (default %(default)r)') 29 | _('--jinja2.auto_reload', action='store_true', default=False, help='Jinja2 filesystem checks (default %(default)r)') 30 | -------------------------------------------------------------------------------- /zephyr/boot/pedis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | class PedisBoot(object): 19 | 20 | def __init__(self, options=None): 21 | self.config(options) 22 | 23 | def config(self, options=None): 24 | options = options or self.options 25 | with options.group("Redis settings") as group: 26 | _ = group.define 27 | _('--redis.host', default='localhost', help='The host of the redis (default %(default)r)') 28 | _('--redis.port', default=6379, help='The port of the redis (default %(default)r)', type=int) 29 | _('--redis.db', default=0, help='The db of the redis (default %(default)r)', type=int) 30 | _('--redis.password', default=None, help='The user of the redis (default %(default)r)') 31 | _('--redis.max_connections', default=None, help='The max connections of the redis (default %(default)r)') 32 | -------------------------------------------------------------------------------- /zephyr/boot/site.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | class SiteBoot(object): 19 | 20 | def __init__(self, options=None): 21 | self.config(options) 22 | 23 | def config(self, options=None): 24 | from zephyr import __version__ 25 | options = options or self.options 26 | group = options.group("Service settings") 27 | _ = group.define 28 | _('-H', '--tornado.host', default='localhost', help='The host of the tornado server (default %(default)r)') 29 | _('-p', '--tornado.port', default=8888, help='The port of the tornado server (default %(default)r)', type=int) 30 | _('-d', '--debug', help='Open debug mode (default %(default)r)', action='store_true', default=False) 31 | _('--language', default='en_GB', help="The language for the site (default %(default)r)") 32 | _('--content_path', default='/upload', help="The Upload path for storing uploaded assets (default %(default)r)") 33 | _('--theme', default='default', help="The theme for the site (default %(default)r)") 34 | _('--secert_key', default="7oGwHH8NQDKn9hL12Gak9G/MEjZZYk4PsAxqKU4cJoY=", help='The secert key for secure cookies (default %(default)r)') 35 | _('-c', '--config', default='/etc/zephyr/app.conf', help="config path (default %(default)r)", metavar="FILE") 36 | _("-v", "--version", help="Show zephyr version %s" % __version__) 37 | -------------------------------------------------------------------------------- /zephyr/breeze/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .patch import patch_tornado 3 | 4 | patch_tornado() 5 | 6 | from .core import Handler, RenderHandler, Backend, Model, Thing, url, AssetHandler 7 | from .app import Application 8 | 9 | __all__ = ('Handler', 10 | 'RenderHandler', 11 | 'Backend', 'Model', 'Thing', 12 | 'url', 'AssetHandler', 13 | 'Application' 14 | ) 15 | -------------------------------------------------------------------------------- /zephyr/breeze/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import tornado.web 18 | 19 | from .hook import HookMap 20 | 21 | 22 | class Application(tornado.web.Application): 23 | 24 | hookpoints = ['on_start_request', 'on_end_request', 25 | 'before_error_response', 'after_error_response'] 26 | 27 | def __init__(self, handlers, **settings): 28 | self.error_pages = {} 29 | self.hooks = HookMap() 30 | tornado.web.Application.__init__( 31 | self, handlers, **settings) 32 | 33 | def attach(self, point, callback, failsafe=None, priority=None, **kwargs): 34 | if point not in self.hookpoints: 35 | return 36 | self.hooks.attach(point, callback, failsafe, priority, **kwargs) 37 | 38 | def error_page(self, code, callback): 39 | if type(code) is not int: 40 | raise TypeError("code:%d is not int type" % (code)) 41 | self.error_pages[str(code)] = callback 42 | -------------------------------------------------------------------------------- /zephyr/breeze/flash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import tornado 18 | from zephyr.jinja2t import Markup 19 | 20 | class FlashMessagesMixin(object): 21 | @property 22 | def messages(self): 23 | if not hasattr(self, '_messages'): 24 | messages = self.get_secure_cookie('flash_messages') 25 | self._messages = [] 26 | if messages: 27 | self._messages = tornado.escape.json_decode(messages) 28 | return self._messages 29 | 30 | def flash(self, message, level='error'): 31 | if isinstance(message, str): 32 | message = message.decode('utf8') 33 | 34 | self.messages.append((level, message)) 35 | self.set_secure_cookie('flash_messages',tornado.escape.json_encode(self.messages)) 36 | 37 | def get_flashed_messages(self): 38 | messages = self.messages 39 | self._messages = [] 40 | self.clear_cookie('flash_messages') 41 | if messages: 42 | html = '
\n' 43 | for category, message in messages: 44 | html += '

%s

\n' % (category, message) 45 | html += '
' 46 | return Markup(html) 47 | return '' -------------------------------------------------------------------------------- /zephyr/breeze/patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | __all__ = ['patch_tornado'] 19 | 20 | import traceback 21 | 22 | 23 | def patch_tornado(): 24 | 25 | from functools import wraps 26 | 27 | import datetime 28 | import decimal 29 | 30 | try: 31 | import simplejson as json # try external module 32 | except ImportError: 33 | import json 34 | 35 | def as_json(o): 36 | """Returns the json serialize content 37 | when the o is a object isinstance and has as_json method, then it will call the method, 38 | and dumps the return content. Also it can handle the datetime.date and decimal dumps 39 | """ 40 | if hasattr(o, '__json__') and callable(o.__json__): 41 | return o.__json__() 42 | if isinstance(o, (datetime.date, 43 | datetime.datetime, 44 | datetime.time)): 45 | return o.isoformat()[:19].replace('T', ' ') 46 | elif isinstance(o, (int, long)): 47 | return int(o) 48 | elif isinstance(o, decimal.Decimal): 49 | return str(o) 50 | else: 51 | raise TypeError(repr(o) + " is not JSON serializable") 52 | 53 | def json_encode(value, ensure_ascii=True, default=as_json): 54 | """Returns the json serialize stream""" 55 | return json.dumps(value, default=default, ensure_ascii=ensure_ascii) 56 | 57 | def write_error(self, status_code, **kwargs): 58 | """Handle the last unanticipated exception. (Core)""" 59 | try: 60 | self.hooks.run("before_error_response", 61 | self, status_code, **kwargs) 62 | handler = self.application.error_pages.get(str(status_code), None) 63 | if handler: 64 | handler(self, status_code, **kwargs) 65 | else: 66 | if self.settings.get("serve_traceback") and "exc_info" in kwargs: 67 | # in debug mode, try to send a traceback 68 | self.set_header('Content-Type', 'text/plain') 69 | for line in traceback.format_exception(*kwargs["exc_info"]): 70 | self.write(line) 71 | self.finish() 72 | else: 73 | self.finish("%(code)d: %(message)s" 74 | "%(code)d: %(message)s" % { 75 | "code": status_code, 76 | "message": self._reason, 77 | }) 78 | finally: 79 | self.hooks.run("after_error_response", self, status_code, **kwargs) 80 | 81 | 82 | from tornado import escape 83 | from tornado import web 84 | web.RequestHandler.write_error = write_error 85 | escape.json_encode = json_encode 86 | -------------------------------------------------------------------------------- /zephyr/cmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import os 18 | import sys 19 | 20 | from zephyr import options 21 | from zephyr.boot import BootOptions 22 | from zephyr.config import SelectConfig, ConfigFactory 23 | 24 | 25 | class Null(object): 26 | pass 27 | 28 | _Null = Null() 29 | 30 | 31 | class Cmd(object): 32 | 33 | def __init__(self, opt=None): 34 | 35 | self.options = opt or options 36 | 37 | def get_file_opt(self): 38 | opt = options.Options(None) 39 | opt.define('-c', '--config', default='/etc/zephyr/app.conf', 40 | help="config path (default %(default)r)", metavar="FILE") 41 | o = opt.parse_args(sys.argv) 42 | if os.path.exists(o.config): 43 | config = ConfigFactory.parseFile(o.config, pystyle=True) 44 | return config 45 | else: 46 | return ConfigFactory.empty(pystyle=True) 47 | 48 | def parse_cmd(self, help_doc): 49 | self.options.setup_options(help_doc) 50 | BootOptions(self.options) 51 | 52 | c = self._set_defaults() 53 | opt = self.options.parse_args() 54 | config = c.toSelectConfig() 55 | config.update(vars(opt)) 56 | return config 57 | 58 | def _set_defaults(self): 59 | c = self.get_file_opt() 60 | opt = options.Options(None) 61 | BootOptions(opt) 62 | opt = opt.parse_args() 63 | d = {} 64 | config = vars(opt) 65 | for k in config: 66 | v = c.get(k, _Null) 67 | if v != _Null: 68 | d[k] = v 69 | self.options.set_defaults(**d) 70 | return c 71 | 72 | 73 | __cmd = Cmd() 74 | 75 | 76 | def parse_cmd(help_doc): 77 | return __cmd.parse_cmd(help_doc) 78 | -------------------------------------------------------------------------------- /zephyr/config/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 Self-Released 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | # Human-Optimized Config Object Notation 18 | 19 | from ._config import ConfigFactory, SelectConfig 20 | 21 | 22 | __all__ = ('ConfigFactory', ) 23 | -------------------------------------------------------------------------------- /zephyr/config/errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 Self-Released 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | # Human-Optimized Config Object Notation 18 | 19 | 20 | class ConfigError(Exception): 21 | pass 22 | 23 | 24 | class HoconParserException(ConfigError): 25 | pass 26 | 27 | 28 | class HoconTokenizerException(ConfigError): 29 | pass 30 | -------------------------------------------------------------------------------- /zephyr/helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from zephyr.lib.memoize import memoize 19 | from zephyr.breeze import Backend 20 | 21 | 22 | @memoize() 23 | def categories(): 24 | return Backend('Category').categories() 25 | 26 | 27 | @memoize() 28 | def menus(): 29 | return Backend('Page').menu(True) 30 | 31 | 32 | @memoize() 33 | def cached_user(uid): 34 | return Backend('User').find(uid) 35 | 36 | 37 | class SiteConfig(object): 38 | 39 | def sitename(self): 40 | return self.config.get('sitename', 'White') 41 | 42 | def description(self): 43 | return self.config.get('site_description', '') 44 | 45 | def posts_per_page(self, perpage=10): 46 | return self.config.get('posts_per_page', perpage) 47 | 48 | def comment_moderation_keys(self): 49 | return self.config.get('comment_moderation_keys', []) 50 | 51 | def get(self, key, default=None): 52 | return self.config.get(key, default) 53 | 54 | @property 55 | def config(self): 56 | return self._config() 57 | 58 | @memoize() 59 | def _config(self): 60 | return Backend('Pair').find('system').json_value() 61 | 62 | def clear_cache(self): 63 | self._config.cache.flush() 64 | 65 | 66 | site = SiteConfig() 67 | -------------------------------------------------------------------------------- /zephyr/hooks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from zephyr.session import SessionManager 19 | 20 | def on_load_session(req): 21 | req.account = None 22 | SessionManager(req).loadByRequest() 23 | 24 | 25 | def on_not_found(req, status_code, **kw): 26 | theme = req.application.config.get('theme', 'default') 27 | tpl = 'theme/' + theme + '/404.html' 28 | req.render(tpl, page_title='Not Found') -------------------------------------------------------------------------------- /zephyr/jinja2t.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import os 18 | 19 | from jinja2 import Environment, FileSystemLoader, FileSystemBytecodeCache 20 | from jinja2 import Markup 21 | import jinja2 22 | import tornado.template 23 | 24 | 25 | 26 | class FixedTemplate(jinja2.Template): 27 | """ Subclass of jinja2.Template 28 | Override Template.generate method to adapt render_string method\ 29 | from tornado.RequestHandler 30 | """ 31 | 32 | def generate(self, **kwargs): 33 | return self.render(**kwargs) 34 | 35 | # Change The template class that returned by Environment.get_templte 36 | Environment.template_class = FixedTemplate 37 | 38 | 39 | class Jinja2Loader(tornado.template.Loader): 40 | 41 | """ inherit form tornado.template.Loader 42 | Implementing customized Template Loader of for tornado to generate\ 43 | jinja2 template 44 | """ 45 | 46 | def __init__(self, root_directory, cache_path=None, cache_size=-1, 47 | auto_reload=False, 48 | autoescape=True, **kwargs): 49 | super(Jinja2Loader, self).__init__(root_directory, **kwargs) 50 | bcc = None 51 | if cache_path: 52 | # if not os.path.exists(cache_path): 53 | # os.makedirs(cache_path) 54 | bcc = FileSystemBytecodeCache(directory=cache_path) 55 | self.env = Environment(loader=FileSystemLoader(self.root), bytecode_cache=bcc, 56 | auto_reload=auto_reload, 57 | cache_size=cache_size, 58 | autoescape=autoescape) 59 | 60 | def _create_template(self, name): 61 | return self.env.get_template(name) 62 | 63 | def reset(self): 64 | if hasattr(self.env, 'bytecode_cache') and self.env.bytecode_cache: 65 | self.env.bytecode_cache.clear() 66 | -------------------------------------------------------------------------------- /zephyr/lang/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import codecs 18 | import os 19 | import os.path 20 | 21 | 22 | def setup(language=None): 23 | global __lines 24 | language = language or 'en_GB' 25 | langpath = os.path.join(os.path.dirname(__file__), language) 26 | __lines = {} 27 | for root, dirs, files in os.walk(langpath): 28 | for file in files: 29 | name, ext = file.split('.') 30 | if ext == 'py' and name != '__init__': 31 | ns = {} 32 | with codecs.open(os.path.join(langpath, file), "r", "utf-8") as f: 33 | code = f.read() 34 | exec code in ns 35 | __lines[name] = ns['t'] 36 | 37 | 38 | def text(key, default=None, args=None): 39 | parts = key.split('.') 40 | if len(parts) == 2: 41 | name = parts[0] 42 | key = parts[1] 43 | if len(parts) == 1: 44 | name = 'global' 45 | key = parts.pop(0) 46 | 47 | t = __lines.get(name) 48 | if t: 49 | text = t.get(key, default) 50 | if text and args: 51 | text = text % args 52 | else: 53 | text = default 54 | return text 55 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/lang/en_GB/__init__.py -------------------------------------------------------------------------------- /zephyr/lang/en_GB/category.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'category': 'Category', 4 | 'categories': 'Categories', 5 | 6 | 'create_category': 'Create a new category', 7 | 'edit_category': 'Editing “%s”', 8 | 9 | # form fields 10 | 'title': 'Title', 11 | 'title_explain': 'Your category title.', 12 | 'title_missing': 'Please enter a title', 13 | 14 | 'slug': 'Slug', 15 | 'slug_explain': 'The slug for your category.', 16 | 17 | 'description': 'Description', 18 | 'description_explain': 'What your category is about.', 19 | 20 | # messages 21 | 'created': 'Your new category has been added.', 22 | 'updated': 'Your category has been updated.', 23 | 'deleted': 'Your category has been deleted.', 24 | 'delete_error': 'You must have at least one category.', 25 | 26 | } 27 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/comment.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'comment': 'Comments', 4 | 'nocomments_desc': 'No comments yet.', 5 | 'editing_comment': 'Editing comment', 6 | 'view_comment': 'View comment', 7 | 8 | # form fields 9 | 'name': 'Name', 10 | 'name_explain': 'Author name', 11 | 'name_missing': 'Please enter a name', 12 | 13 | 'email': 'Email address', 14 | 'email_explain': 'Author email', 15 | 'email_missing': 'Please enter a valid email address', # frontend message (appears on your site!) 16 | 17 | 'content': 'Comment', 18 | 'content_explain': '', 19 | 'content_missing': 'Please enter comment text', # frontend message (appears on your site!) 20 | 21 | 'status': 'Status', 22 | 'status_explain': '', 23 | 24 | # messages 25 | 'created': 'Your comment has been added', # frontend message (appears on your site!) 26 | 'updated': 'Your comment has been updated', 27 | 'deleted': 'Your comment has been deleted', 28 | 29 | # email notification 30 | 'notify_subject': 'New comment has been added', 31 | 'nofity_heading': 'A new comment has been submitted to your site.' 32 | } 33 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/extend.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'extend': 'Extend', 4 | 5 | 'fields': 'Custom Fields', 6 | 'fields_desc': 'Create additional fields', 7 | 8 | 'variables': 'Site Variables', 9 | 'variables_desc': 'Create additional metadata', 10 | 11 | 'create_field': 'Create a new field', 12 | 'editing_custom_field': 'Editing field “%s”', 13 | 'nofields_desc': 'No fields yet', 14 | 15 | 'create_variable': 'Create a new variable', 16 | 'editing_variable': 'Editing variable “%s”', 17 | 'novars_desc': 'No variables yet', 18 | 19 | # form fields 20 | 'type': 'Type', 21 | 'type_explain': 'The type of content you want to add this field to.', 22 | 23 | 'field': 'Field', 24 | 'field_explain': 'Html input type', 25 | 26 | 'key': 'Unique Key', 27 | 'key_explain': 'The unique key for your field', 28 | 'key_missing': 'Please enter a unique key', 29 | 'key_exists': 'Key is already in use', 30 | 31 | 'label': 'Label', 32 | 'label_explain': 'Human readable name for your field', 33 | 'label_missing': 'Please enter a label', 34 | 35 | 'attribute_type': 'File types', 36 | 'attribute_type_explain': 'Comma separated list of accepted file types, empty to accept all.', 37 | 38 | # images 39 | 'attributes_size_width': 'Image max width', 40 | 'attributes_size_width_explain': 'Images will be resized if they are bigger than the max size', 41 | 42 | 'attributes_size_height': 'Image max height', 43 | 'attributes_size_height_explain': 'Images will be resized if they are bigger than the max size', 44 | 45 | # custom vars 46 | 'name': 'Name', 47 | 'name_explain': 'A unique name', 48 | 'name_missing': 'Please enter a unique name', 49 | 'name_exists': 'Name is already in use', 50 | 51 | 'value': 'Value', 52 | 'value_explain': 'The data you want to store (up to 64kb)', 53 | 'value_code_snipet': 'Snippet to insert into your template:
site_meta(\'%s\')', 54 | 55 | # messages 56 | 'variable_created': 'Your variable was created', 57 | 'variable_updated': 'Your variable was updated', 58 | 'variable_deleted': 'Your variable was deleted', 59 | 60 | 'field_created': 'Your field was created', 61 | 'field_updated': 'Your field was updated', 62 | 'field_deleted': 'Your field was deleted' 63 | 64 | } 65 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/global.py: -------------------------------------------------------------------------------- 1 | t = { 2 | # words 3 | 'save': 'Save', 4 | 'delete': 'Delete', 5 | 'update': 'Update', 6 | 'edit': 'Edit', 7 | 'editing': 'Editing', 8 | 'create': 'Create', 9 | 'created': 'Created', 10 | 'submit': 'Submit', 11 | 'close': 'Close', 12 | 'status': 'Status', 13 | 'manage': 'Manage', 14 | 'reset': 'Reset', 15 | 'all': 'All', 16 | 17 | # pagination 18 | 'next': 'Next', 19 | 'previous': 'Previous', 20 | 'first': 'First', 21 | 'last': 'Last', 22 | 23 | # statuses 24 | 'draft': 'Draft', 25 | 'archived': 'Archived', 26 | 'published': 'Published', 27 | 'pending': 'Pending', 28 | 'approved': 'Approved', 29 | 'spam': 'Spam', 30 | 31 | 'inactive': 'Inactive', 32 | 'active': 'Active', 33 | 34 | # roles 35 | 'administrator': 'Admin', 36 | 'editor': 'Editor', 37 | 'user': 'User', 38 | 39 | 'log_in': 'Log in', 40 | 'login': 'Login', 41 | 'log_out': 'Log out', 42 | 'logout': 'Logout', 43 | 44 | # pharses 45 | 'visit_your_site': 'Visit your site', 46 | 'powered_by_white': 'Powered by White, version %s', 47 | 'make_blogging_beautiful': 'Make blogging beautiful.', 48 | 49 | # intro 50 | 'welcome_to_white': 'Welcome to White', 51 | 'welcome_to_white_lets_go': 'Welcome to White. Let\'s go.', 52 | 'run_the_installer': 'Run the installer', 53 | 54 | # upgrade 55 | 'upgrade': 'Upgrade', 56 | 'good_news': 'Great News!', 57 | 'new_version_available': 'There\'s a new version of white available.', 58 | 'download_now': 'Download Now', 59 | 'upgrade_later': 'Upgrade later', 60 | 61 | # debug profiler 62 | 'profile': 'Profile', 63 | 'profile_memory_usage': 'Total memory usage', 64 | 65 | # messages 66 | 'confirm_delete': 'Are you sure you want to delete? This can\'t be undone!' 67 | } 68 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/menu.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'menu': 'Menu', 4 | 5 | } 6 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/metadata.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'metadata': 'Site Metadata', 4 | 'metadata_desc': 'Manage your site data', 5 | 6 | 'comment_settings': 'Comments', 7 | 'theme_settings': 'Apperance', 8 | 9 | # form fields 10 | 'sitename': 'Site name', 11 | 'sitename_explain': '', 12 | 'sitename_missing': 'Your site needs a name!', 13 | 14 | 'sitedescription': 'Site description', 15 | 'sitedescription_explain': '', 16 | 'sitedescription_missing': 'Your site needs a description!', 17 | 18 | 'homepage': 'Home Page', 19 | 'homepage_explain': '', 20 | 21 | 'postspage': 'Posts Page', 22 | 'postspage_explain': '', 23 | 24 | 'posts_per_page': 'Posts per page', 25 | 'posts_per_page_explain': '', 26 | 27 | 'auto_publish_comments': 'Auto-allow comments', 28 | 'auto_publish_comments_explain': '', 29 | 30 | 'comment_notifications': 'Email notification for new comments', 31 | 'comment_notifications_explain': '', 32 | 33 | 'comment_moderation_keys': 'Spam keywords', 34 | 'comment_moderation_keys_explain' : 'Comma separated list of keywords to blacklist against. \ 35 | Comments will automatically be set as spam.', 36 | 37 | 'current_theme': 'Current theme', 38 | 'current_theme_explain': '', 39 | 40 | # messages 41 | 'updated': 'Metadata updated', 42 | 43 | } 44 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/page.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'page': 'Pages', 4 | 5 | 'create_page': 'Create a new page', 6 | 'nopages_desc': 'You don\'t have any pages.', 7 | 'redirect': 'Redirect', 8 | 9 | # form fields 10 | 'redirect_url': 'Redirect Url', 11 | 'redirect_missing': 'Please enter a valid url', 12 | 13 | 'title': 'Page title', 14 | 'title_explain': '', 15 | 'title_missing': 'Please enter a page title', 16 | 17 | 'content': 'Content', 18 | 'content_explain': 'Your page\'s content. Uses Markdown.', 19 | 20 | 'show_in_menu': 'Show In Menu', 21 | 'show_in_menu_explain': '', 22 | 23 | 'name': 'Name', 24 | 'name_explain': '', 25 | 26 | 'slug': 'Slug', 27 | 'slug_explain': 'Slug uri to identify your page, should only contain ascii characters', 28 | 'slug_missing': 'Please enter a slug uri, slugs can only contain ascii characters', 29 | 'slug_duplicate': 'Slug already exists', 30 | 'slug_invalid': 'Slug must contain letters', 31 | 32 | 'status': 'Status', 33 | 'status_explain': '', 34 | 35 | 'parent': 'Parent', 36 | 'parent_explain': '', 37 | 38 | # messages 39 | 'updated': 'Your page was updated.', 40 | 'created': 'Your page was created.', 41 | 'deleted': 'Your page was deleted.' 42 | 43 | } 44 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/post.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'post': 'Posts', 4 | 5 | 'create_post': 'Create a new post', 6 | 'noposts_desc': 'You don\'t have any posts!', 7 | 8 | # form fields 9 | 'title': 'Post title', 10 | 'title_explain': '', 11 | 'title_missing': 'Please enter a title', 12 | 13 | 'content': 'Post Content', 14 | 'content_explain': 'Just write.', 15 | 16 | 'slug': 'Slug', 17 | 'slug_explain': 'Slug uri to identify your post, should only contain ascii characters', 18 | 'slug_missing': 'Please enter a slug uri, slugs can only contain ascii characters', 19 | 'slug_duplicate': 'Slug already exists', 20 | 'slug_invalid': 'Slug must contain letters', 21 | 22 | 'description': 'Description', 23 | 'description_explain': '', 24 | 25 | 'status': 'Status', 26 | 'status_explain': '', 27 | 28 | 'category': 'Category', 29 | 'category_explain': '', 30 | 31 | 'allow_comments': 'Allow Comments', 32 | 'allow_comments_explain': '', 33 | 34 | 'custom_css': 'Custom CSS', 35 | 'custom_css_explain': '', 36 | 37 | 'custom_js': 'Custom JS', 38 | 'custom_js_explain': '', 39 | 40 | # messages 41 | 'updated': 'Your article has been updated', 42 | 'created': 'Your new article was created', 43 | 'deleted': 'Your article have been deleted' 44 | 45 | } 46 | -------------------------------------------------------------------------------- /zephyr/lang/en_GB/user.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'user': 'Users', 4 | 5 | 'create_user': 'Create a new user', 6 | 'add_user': 'Add a new user', 7 | 'editing_user': 'Editing %s’s Profile', 8 | 'remembered': 'I know my password', 9 | 'forgotten_password': 'Forgotten your password?', 10 | 11 | # roles 12 | 'administrator': 'Admin', 13 | 'administrator_explain': '', 14 | 15 | 'editor': 'Editor', 16 | 'editor_explain': '', 17 | 18 | 'user': 'User', 19 | 'user_explain': '', 20 | 21 | # form fields 22 | 'real_name': 'Real Name', 23 | 'real_name_explain': '', 24 | 25 | 'bio': 'Biography', 26 | 'bio_explain': '', 27 | 28 | 'status': 'Status', 29 | 'status_explain': '', 30 | 31 | 'role': 'Role', 32 | 'role_explain': '', 33 | 34 | 'username': 'Username', 35 | 'username_explain': '', 36 | 'username_missing': 'Please enter a username, must be 4-16 length can contains letter,digit, underscore', 37 | 38 | 'password': 'Password', 39 | 'password_explain': '', 40 | 'password_invalid': 'Password must be must be 4-16 length can contains letter,digit, underscore', 41 | 42 | 'new_password': 'New Password', 43 | 44 | 'email': 'Email', 45 | 'email_explain': '', 46 | 'email_missing': 'Please enter a valid email address', 47 | 'email_not_found': 'Profile not found.', 48 | 49 | # messages 50 | 'updated': 'User profile updated.', 51 | 'created': 'User profile created.', 52 | 'deleted': 'User profile deleted.', 53 | 'delete_error': 'You cannot delete your own profile', 54 | 'login_error': 'Username or password is wrong.', 55 | 'logout_notice': 'You are now logged out.', 56 | 'recovery_sent': 'We have sent you an email to confirm your password change.', 57 | 'recovery_expired': 'Password recovery token has expired, please try again.', 58 | 'password_reset': 'Your new password has been set. Go and login now!', 59 | 60 | # password recovery email 61 | 'recovery_subject': 'Password Reset', 62 | 'recovery_message': 'You have requested to reset your password.' + 63 | 'To continue follow the link below.' + '%s', 64 | 65 | } 66 | -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/lang/zh_CN/__init__.py -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/category.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'category' : u'分类', 4 | 'categories' : u'分类', 5 | 6 | 'create_category' : u'建立新分类', 7 | 'edit_category' : u'编辑 “%s”', 8 | 9 | # form fields 10 | 'title' : u'Title', 11 | 'title_explain' : u'分类名称.', 12 | 'title_missing' : u'请输入分类名称', 13 | 14 | 'slug' : u'Slug', 15 | 'slug_explain' : u'分类缩写.', 16 | 17 | 'description' : u'描述', 18 | 'description_explain' : u'关于您的分类的描述.', 19 | 20 | # messages 21 | 'created' : u'您的新分类已添加.', 22 | 'updated' : u'您的新分类已更新.', 23 | 'deleted' : u'您的分类已删除.', 24 | 'delete_error' : u'您必须有一个分区.', 25 | 26 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/comment.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'comment' : u'回复', 4 | 'nocomments_desc' : u'还没有恢复.', 5 | 'editing_comment' : u'编辑回复', 6 | 'view_comment' : u'查看回复', 7 | 8 | # form fields 9 | 'name' : u'Name', 10 | 'name_explain' : u'作者', 11 | 'name_missing' : u'请输入作者', 12 | 13 | 'email' : u'邮件地址', 14 | 'email_explain' : u'作者的电子邮件地址', 15 | 'email_missing' : u'请输入一个有效的电子邮件地址', # frontend message (appears on your site!) 16 | 17 | 'content' : u'回复', 18 | 'content_explain' : u'', 19 | 'content_missing' : u'请输入回复内容', # frontend message (appears on your site!) 20 | 21 | 'status' : u'统计', 22 | 'status_explain' : u'', 23 | 24 | # messages 25 | 'created' : u'您的回复已添加', # frontend message (appears on your site!) 26 | 'updated' : u'您的回复已更新', 27 | 'deleted' : u'您的回复已删除', 28 | 29 | # email notification 30 | 'notify_subject' : u'新回复已添加', 31 | 'nofity_heading' : u'有新回复来了.' 32 | 33 | } 34 | -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/extend.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'extend' : u'扩展', 4 | 5 | 'fields' : u'自定义字段', 6 | 'fields_desc' : u'创建自定义字段', 7 | 8 | 'variables' : u'网站变量', 9 | 'variables_desc' : u'创建网站变量', 10 | 11 | 'create_field' : u'创建一个新的自定义字段', 12 | 'editing_custom_field' : u'编辑自定义字段 “%s”', 13 | 'nofields_desc' : u'还没有自定义字段', 14 | 15 | 'create_variable' : u'创建一个新的变量', 16 | 'editing_variable' : u'编辑变量 “%s”', 17 | 'novars_desc' : u'还没有变量', 18 | 19 | # form fields 20 | 'type' : u'类型', 21 | 'type_explain' : u'要添加的内容类型.', 22 | 23 | 'field' : u'形式', 24 | 'field_explain' : u'Html 输入类型', 25 | 26 | 'key' : u'调用名', 27 | 'key_explain' : u'自定义字段的调用名', 28 | 'key_missing' : u'请输入一个调用名', 29 | 'key_exists' : u'这个名字被用了', 30 | 31 | 'label' : u'标签', 32 | 'label_explain' : u'自定义字段的备忘名', 33 | 'label_missing' : u'请输入一个标签', 34 | 35 | 'attribute_type' : u'文件类型', 36 | 'attribute_type_explain' : u'接受的文件类型,用逗号分隔,留空接受所有.', 37 | 38 | # images 39 | 'attributes_size_width' : u'图片最大宽度', 40 | 'attributes_size_width_explain' : u'如果图片宽度超过最大值,将会被压缩', 41 | 42 | 'attributes_size_height' : u'图片最大高度', 43 | 'attributes_size_height_explain' : u'如果图片高度超过最大值,将会被压缩', 44 | 45 | # custom vars 46 | 'name' : u'名称', 47 | 'name_explain' : u'唯一名称', 48 | 'name_missing' : u'请输入一个唯一名称', 49 | 'name_exists' : u'名称已被使用', 50 | 51 | 'value' : u'值', 52 | 'value_explain' : u'你要存储的数据(多达64KB)', 53 | 'value_code_snipet' : u'将被插入到模版的片段:
site_meta(\'%s\'', 54 | 55 | # messages 56 | 'variable_created' : u'您的变量已创建', 57 | 'variable_updated' : u'您的变量已更新', 58 | 'variable_deleted' : u'您的变量已删除', 59 | 60 | 'field_created' : u'您的自定义字段已创建', 61 | 'field_updated' : u'您的自定义字段已更新', 62 | 'field_deleted' : u'您的自定义字段已删除' 63 | 64 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/global.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | # words 4 | 'save' : u'保存', 5 | 'delete' : u'删除', 6 | 'update' : u'更新', 7 | 'edit' : u'编辑', 8 | 'editing' : u'编辑中', 9 | 'create' : u'创建', 10 | 'created' : u'已创建', 11 | 'submit' : u'提交', 12 | 'close' : u'关闭', 13 | 'status' : u'统计', 14 | 'manage' : u'管理', 15 | 'reset' : u'重置', 16 | 'all' : u'所有', 17 | 18 | # pagination 19 | 'next' : u'下一个', 20 | 'previous' : u'上一个', 21 | 'first' : u'第一个', 22 | 'last' : u'最后一个', 23 | 24 | # statuses 25 | 'draft' : u'草稿', 26 | 'archived' : u'存档', 27 | 'published' : u'发布', 28 | 'pending' : u'待定', 29 | 'approved' : u'批准', 30 | 'spam' : u'Spam', 31 | 32 | 'inactive' : u'待启用', 33 | 'active' : u'启用', 34 | 35 | # roles 36 | 'administrator' : u'管理员', 37 | 'editor' : u'编辑', 38 | 'user' : u'用户', 39 | 40 | 'log_in' : u'登录', 41 | 'login' : u'登录', 42 | 'log_out' : u'登出', 43 | 'logout' : u'登出', 44 | 45 | # pharses 46 | 'visit_your_site' : u'访问首页', 47 | 'powered_by_white' : u'Powered by White, version %s', 48 | 'make_blogging_beautiful' : u'编写精彩的博客。', 49 | 50 | # messages 51 | 'confirm_delete' : u'你确定要删除么?不能反悔哦!' 52 | 53 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/menu.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'menu' : u'导航', 4 | 5 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/metadata.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'metadata' : u'站点设置', 4 | 'metadata_desc' : u'管理您的站点设置', 5 | 6 | 'comment_settings' : u'回复', 7 | 'theme_settings' : u'模版', 8 | 9 | # form fields 10 | 'sitename' : u'站点名称', 11 | 'sitename_explain' : u'', 12 | 'sitename_missing' : u'您的网站需要一个名字!', 13 | 14 | 'sitedescription' : u'站点描述', 15 | 'sitedescription_explain' : u'', 16 | 'sitedescription_missing' : u'您的网站需要一个描述!', 17 | 18 | 'homepage' : u'首页', 19 | 'homepage_explain' : u'', 20 | 21 | 'postspage' : u'文章页', 22 | 'postspage_explain' : u'', 23 | 24 | 'posts_per_page' : u'每页文章数', 25 | 'posts_per_page_explain' : u'', 26 | 27 | 'auto_publish_comments' : u'自动通过回复', 28 | 'auto_publish_comments_explain' : u'', 29 | 30 | 'comment_notifications' : u'新回复邮件通知', 31 | 'comment_notifications_explain' : u'', 32 | 33 | 'comment_moderation_keys' : u'垃圾信息关键词', 34 | 'comment_moderation_keys_explain' : u'用逗号分隔创建关键字列表到黑名单,评论将被自动设置为垃圾评论.', 35 | 36 | 'current_theme' : u'网站模版', 37 | 'current_theme_explain' : u'', 38 | 39 | # messages 40 | 'updated' : u'站点信息已更新', 41 | 42 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/page.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'page' : u'页面', 4 | 5 | 'create_page' : u'创建新页面', 6 | 'nopages_desc' : u'您没有页面.', 7 | 'redirect' : u'重定向', 8 | 9 | # form fields 10 | 'redirect_url' : u'重定向链接', 11 | 'redirect_missing' : u'请输入一个有效的链接', 12 | 13 | 'title' : u'页面标题', 14 | 'title_explain' : u'', 15 | 'title_missing' : u'请输入一个页面标题', 16 | 17 | 'content' : u'内容', 18 | 'content_explain' : u'页面内容.使用 Markdown.', 19 | 20 | 'show_in_menu' : u'在导航中显示', 21 | 'show_in_menu_explain' : u'', 22 | 23 | 'name' : u'名称', 24 | 'name_explain' : u'', 25 | 26 | 'slug' : u'缩写', 27 | 'slug_explain' : u'您的页面的缩写网址,只能包含 ASCII 字符', 28 | 'slug_missing' : u'请输入缩写网址,只能包含 ASCII 字符', 29 | 'slug_duplicate' : u'缩写已存在', 30 | 'slug_invalid' : u'缩写必须包含字母', 31 | 32 | 'status' : u'统计', 33 | 'status_explain' : u'', 34 | 35 | 'parent' : u'Parent', 36 | 'parent_explain' : u'', 37 | 38 | # messages 39 | 'updated' : u'您的页面已更新.', 40 | 'created' : u'您的页面已创建.', 41 | 'deleted' : u'您的页面已删除.' 42 | 43 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/post.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'post' : u'文章', 4 | 5 | 'create_post' : u'发表文章', 6 | 'noposts_desc' : u'您没有文章!', 7 | 8 | # form fields 9 | 'title' : u'标题', 10 | 'title_explain' : u'', 11 | 'title_missing' : u'请输入标题', 12 | 13 | 'content' : u'文章正文', 14 | 'content_explain' : u'来写吧.', 15 | 16 | 'slug' : u'缩写', 17 | 'slug_explain' : u'用缩写来识别您的文章,只能包含 ASCII 字符', 18 | 'slug_missing' : u'请输入一个缩写网址,只能包含 ASCII 字符', 19 | 'slug_duplicate' : u'缩写已存在', 20 | 'slug_invalid' : u'缩写必须包含字母', 21 | 22 | 'description' : u'描述', 23 | 'description_explain' : u'', 24 | 25 | 'status' : u'统计', 26 | 'status_explain' : u'', 27 | 28 | 'category' : u'分类', 29 | 'category_explain' : u'', 30 | 31 | 'allow_comments' : u'允许评论', 32 | 'allow_comments_explain' : u'', 33 | 34 | 'custom_css' : u'自定义 CSS', 35 | 'custom_css_explain' : u'', 36 | 37 | 'custom_js' : u'自定义 JS', 38 | 'custom_js_explain' : u'', 39 | 40 | # messages 41 | 'updated' : u'您的文章已更新', 42 | 'created' : u'您的文章已创建', 43 | 'deleted' : u'您的文章已删除' 44 | 45 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_CN/user.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'user' : u'用户', 4 | 5 | 'create_user' : u'建立新用户', 6 | 'add_user' : u'添加一个新用户', 7 | 'editing_user' : u'编辑 %s’s Profile', 8 | 'remembered' : u'我知道我的密码', 9 | 'forgotten_password' : u'忘记密码了?', 10 | 11 | # roles 12 | 'administrator' : u'管理员', 13 | 'administrator_explain' : u'', 14 | 15 | 'editor' : u'编辑', 16 | 'editor_explain' : u'', 17 | 18 | 'user' : u'用户', 19 | 'user_explain' : u'', 20 | 21 | # form fields 22 | 'real_name' : u'真实姓名', 23 | 'real_name_explain' : u'', 24 | 25 | 'bio' : u'简介', 26 | 'bio_explain' : u'', 27 | 28 | 'status' : u'状态', 29 | 'status_explain' : u'', 30 | 31 | 'role' : u'角色', 32 | 'role_explain' : u'', 33 | 34 | 'username' : u'用户名', 35 | 'username_explain' : u'', 36 | 'username_missing' : u'请输入一个4-16长度且仅含字母数字下划线', 37 | 38 | 'password' : u'密码', 39 | 'password_explain' : u'', 40 | 'password_invalid' : u'请输入一个4-16长度且仅含字母数字下划线的密码', 41 | 42 | 'new_password' : u'新密码', 43 | 44 | 'email' : u'邮件地址', 45 | 'email_explain' : u'', 46 | 'email_missing' : u'请输入一个有效的电子信箱', 47 | 'email_not_found' : u'找不到个人资料.', 48 | 49 | # messages 50 | 'updated' : u'用户资料已更新.', 51 | 'created' : u'用户资料已创建.', 52 | 'deleted' : u'用户资料已删除.', 53 | 'delete_error' : u'您不能删除自己的个人资料', 54 | 'login_error' : u'用户名或密码错误.', 55 | 'logout_notice' : u'您已登出.', 56 | 'recovery_sent' : u'我们已经发送了一封邮件来确认您的密码更改.', 57 | 'recovery_expired' : u'密码恢复令牌已过期,请再试一次.', 58 | 'password_reset' : u'您的新密码已设置,去登录吧!', 59 | 60 | # password recovery email 61 | 'recovery_subject' : u'密码重置', 62 | 'recovery_message' : u'请重置您的密码. 要按照下面的链接继续. %s', 63 | 64 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/lang/zh_TW/__init__.py -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/category.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'category': u'分類', 4 | 'categories': u'分類', 5 | 6 | 'create_category': u'建立新分類', 7 | 'edit_category': u'編輯 “%s”', 8 | 9 | # form fields 10 | 'title': u'標題', 11 | 'title_explain': u'分類標題。', 12 | 'title_missing': u'請輸入標題', 13 | 14 | 'slug': u'縮寫', 15 | 'slug_explain': u'分類的縮寫。', 16 | 17 | 'description': u'描述', 18 | 'description_explain': u'分類關於什麼。', 19 | 20 | # messages 21 | 'created': u'分類已新增。', 22 | 'updated': u'分類已更新。', 23 | 'deleted': u'分類已刪除。', 24 | 'delete_error': u'至少要有一個分類。', 25 | 26 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/comment.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'comment' : u'評論', 4 | 'nocomments_desc' : u'暫無評論。', 5 | 'editing_comment' : u'編輯評論', 6 | 'view_comment' : u'查看評論', 7 | 8 | # form fields 9 | 'name' : u'名稱', 10 | 'name_explain' : u'作者名稱', 11 | 'name_missing' : u'請輸入名稱', 12 | 13 | 'email' : u'Email 地址', 14 | 'email_explain' : u'作者 Email', 15 | 'email_missing' : u'請輸入有效的 Email 地址', # frontend message (appears on your site!) 16 | 17 | 'content' : u'評論', 18 | 'content_explain' : u'', 19 | 'content_missing' : u'請輸入評論內容', # frontend message (appears on your site!) 20 | 21 | 'status' : u'狀態', 22 | 'status_explain' : u'', 23 | 24 | # messages 25 | 'created' : u'你的評論已新增', # frontend message (appears on your site!) 26 | 'updated' : u'你的評論已更新', 27 | 'deleted' : u'你的評論已刪除', 28 | 29 | # email notification 30 | 'notify_subject' : u'新評論已加入', 31 | 'nofity_heading' : u'新評論已被送交到你的網站。' 32 | 33 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/extend.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'extend' : u'擴充', 4 | 5 | 'fields' : u'自訂欄位', 6 | 'fields_desc' : u'建立額外欄位', 7 | 8 | 'variables' : u'網站變數', 9 | 'variables_desc' : u'建立額外變數', 10 | 11 | 'create_field' : u'建立新欄位', 12 | 'editing_custom_field' : u'編輯欄位 “%s”', 13 | 'nofields_desc' : u'還沒有欄位', 14 | 15 | 'create_variable' : u'建立新變數', 16 | 'editing_variable' : u'編輯變數 “%s”', 17 | 'novars_desc' : u'還沒有變數', 18 | 19 | # form fields 20 | 'type' : u'類型', 21 | 'type_explain' : u'你想要添加到此欄位內容的類型', 22 | 23 | 'field' : u'欄位', 24 | 'field_explain' : u'Html input 類型', 25 | 26 | 'key' : u'唯一鍵', 27 | 'key_explain' : u'欄位的唯一鍵', 28 | 'key_missing' : u'請輸入唯一鍵', 29 | 'key_exists' : u'鍵已使用', 30 | 31 | 'label' : u'標籤', 32 | 'label_explain' : u'欄位的人類可讀名稱', 33 | 'label_missing' : u'請輸入標籤', 34 | 35 | 'attribute_type' : u'檔案類型', 36 | 'attribute_type_explain' : u'以逗號分隔接受的檔案類型,留空接受所有。', 37 | 38 | # images 39 | 'attributes_size_width' : u'圖片最大寬度', 40 | 'attributes_size_width_explain' : u'如果大於最大值,圖片會被調整尺寸', 41 | 42 | 'attributes_size_height' : u'圖片最大高度', 43 | 'attributes_size_height_explain' : u'如果大於最大值,圖片會被調整尺寸', 44 | 45 | # custom vars 46 | 'name' : u'名稱', 47 | 'name_explain' : u'唯一名稱', 48 | 'name_missing' : u'請輸入唯一名稱', 49 | 'name_exists' : u'名稱已使用', 50 | 51 | 'value' : u'值', 52 | 'value_explain' : u'你想儲存的資料(最多 64Kb)', 53 | 'value_code_snipet' : u'要插入到樣板的片段:

site_meta(\'%s\')', 54 | 55 | # messages 56 | 'variable_created' : u'變數已建立', 57 | 'variable_updated' : u'變數已更新', 58 | 'variable_deleted' : u'變數已刪除', 59 | 60 | 'field_created' : u'欄位已建立', 61 | 'field_updated' : u'欄位已更新', 62 | 'field_deleted' : u'欄位已刪除' 63 | 64 | } 65 | -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/global.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | # words 4 | 'save' : u'儲存', 5 | 'delete' : u'刪除', 6 | 'update' : u'更新', 7 | 'edit' : u'編輯', 8 | 'editing' : u'編輯', 9 | 'create' : u'建立', 10 | 'created' : u'建立', 11 | 'submit' : u'送交', 12 | 'close' : u'關閉', 13 | 'status' : u'狀態', 14 | 'manage' : u'管理', 15 | 'reset' : u'重設', 16 | 'all' : u'全部', 17 | 18 | # pagination 19 | 'next' : u'下一頁', 20 | 'previous' : u'上一頁', 21 | 'first' : u'第一頁', 22 | 'last' : u'最後一頁', 23 | 24 | # statuses 25 | 'draft' : u'草稿', 26 | 'archived' : u'封存', 27 | 'published' : u'公佈', 28 | 'pending' : u'等待', 29 | 'approved' : u'通過', 30 | 'spam' : u'垃圾', 31 | 32 | 'inactive' : u'未啟用', 33 | 'active' : u'啟用', 34 | 35 | # roles 36 | 'administrator' : u'管理者', 37 | 'editor' : u'編輯者', 38 | 'user' : u'使用者', 39 | 40 | 'log_in' : u'登入', 41 | 'login' : u'登入', 42 | 'log_out' : u'登出', 43 | 'logout' : u'登出', 44 | 45 | # pharses 46 | 'visit_your_site' : u'造訪你的網站', 47 | 'powered_by_white' : u'由 White 驅動,版本 %s', 48 | 'make_blogging_beautiful' : u'讓部落格更美好。', 49 | 50 | # intro 51 | 'welcome_to_anchor' : u'歡迎來到 Anchor', 52 | 'welcome_to_anchor_lets_go' : u'歡迎來到 Anchor。我們走吧。', 53 | 'run_the_installer' : u'執行安裝程序', 54 | 55 | # upgrade 56 | 'upgrade' : u'升級', 57 | 'good_news' : u'好消息!', 58 | 'new_version_available' : u'有可用的 anchor 新版本。', 59 | 'download_now' : u'立即下載', 60 | 'upgrade_later' : u'稍後升級', 61 | 62 | # debug profiler 63 | 'profile' : u'分析', 64 | 'profile_memory_usage' : u'記憶體總使用量', 65 | 66 | # messages 67 | 'confirm_delete' : u'確定刪除?此操作不能還原!' 68 | 69 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/menu.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'menu' : u'選單', 4 | 5 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/metadata.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'metadata' : u'網站資料', 4 | 'metadata_desc' : u'管理網站資料', 5 | 6 | 'comment_settings' : u'評論', 7 | 'theme_settings' : u'外觀', 8 | 9 | # form fields 10 | 'sitename' : u'網站名稱', 11 | 'sitename_explain' : u'', 12 | 'sitename_missing' : u'網站需要名稱!', 13 | 14 | 'sitedescription' : u'網站描述', 15 | 'sitedescription_explain' : u'', 16 | 'sitedescription_missing' : u'網站需要描述!', 17 | 18 | 'homepage' : u'首頁', 19 | 'homepage_explain' : u'', 20 | 21 | 'postspage' : u'文章頁', 22 | 'postspage_explain' : u'', 23 | 24 | 'posts_per_page' : u'每頁文章', 25 | 'posts_per_page_explain' : u'', 26 | 27 | 'auto_publish_comments' : u'自動允許評論', 28 | 'auto_publish_comments_explain' : u'', 29 | 30 | 'comment_notifications' : u'Email 通知新評論', 31 | 'comment_notifications_explain' : u'', 32 | 33 | 'comment_moderation_keys' : u'垃圾評論關鍵字', 34 | 'comment_moderation_keys_explain' : u'用逗號分隔關鍵字黑名單列表。評論會自動被設為垃圾。', 35 | 36 | 'current_theme' : u'目前主題', 37 | 'current_theme_explain' : u'', 38 | 39 | # messages 40 | 'updated' : u'資料已更新', 41 | 42 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/page.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'page' : u'頁面', 4 | 5 | 'create_page' : u'建立新頁面', 6 | 'nopages_desc' : u'你沒有任何頁面', 7 | 'redirect' : u'重導', 8 | 9 | # form fields 10 | 'redirect_url' : u'重導 URL', 11 | 'redirect_missing' : u'請輸入有效的 URL', 12 | 13 | 'title' : u'頁面標題', 14 | 'title_explain' : u'', 15 | 'title_missing' : u'請輸入頁面標題', 16 | 17 | 'content' : u'內容', 18 | 'content_explain' : u'頁面內容。使用 Markdown。', 19 | 20 | 'show_in_menu' : u'顯示在選單', 21 | 'show_in_menu_explain' : u'', 22 | 23 | 'name' : u'名稱', 24 | 'name_explain' : u'', 25 | 26 | 'slug' : u'縮寫', 27 | 'slug_explain' : u'定義頁面的縮寫,只應該包含 ascii 字元', 28 | 'slug_missing' : u'請輸入縮寫 URL,縮寫只能包含 ascii 字元', 29 | 'slug_duplicate' : u'縮寫已存在', 30 | 'slug_invalid' : u'縮寫必須包含字母', 31 | 32 | 'status' : u'狀態', 33 | 'status_explain' : u'', 34 | 35 | 'parent' : u'上層', 36 | 'parent_explain' : u'', 37 | 38 | # messages 39 | 'updated' : u'頁面已更新。', 40 | 'created' : u'頁面已建立。', 41 | 'deleted' : u'頁面已刪除。' 42 | 43 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/post.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'post' : u'文章', 4 | 5 | 'create_post' : u'建立新文章', 6 | 'noposts_desc' : u'你沒有任何文章!', 7 | 8 | # form fields 9 | 'title' : u'文章標題', 10 | 'title_explain' : u'', 11 | 'title_missing' : u'請輸入標題', 12 | 13 | 'content' : u'文章內容', 14 | 'content_explain' : u'隨便寫。', 15 | 16 | 'slug' : u'縮寫', 17 | 'slug_explain' : u'定義頁面的縮寫,只應該包含 ascii 字元', 18 | 'slug_missing' : u'請輸入縮寫 URL,縮寫只能包含 ascii 字元', 19 | 'slug_duplicate' : u'縮寫已存在', 20 | 'slug_invalid' : u'縮寫必須包含字母', 21 | 22 | 'description' : u'描述', 23 | 'description_explain' : u'', 24 | 25 | 'status' : u'狀態', 26 | 'status_explain' : u'', 27 | 28 | 'category' : u'分類', 29 | 'category_explain' : u'', 30 | 31 | 'allow_comments' : u'允許評論', 32 | 'allow_comments_explain' : u'', 33 | 34 | 'custom_css' : u'自訂 CSS', 35 | 'custom_css_explain' : u'', 36 | 37 | 'custom_js' : u'自訂 JS', 38 | 'custom_js_explain' : u'', 39 | 40 | # messages 41 | 'updated' : u'文章已更新', 42 | 'created' : u'文章已建立', 43 | 'deleted' : u'文章已刪除' 44 | 45 | } -------------------------------------------------------------------------------- /zephyr/lang/zh_TW/user.py: -------------------------------------------------------------------------------- 1 | t = { 2 | 3 | 'user' : u'使用者', 4 | 5 | 'create_user' : u'建立新使用者', 6 | 'add_user' : u'新增使用者', 7 | 'editing_user' : u'編輯 %s 的個人資料', 8 | 'remembered' : u'我知道我的密碼', 9 | 'forgotten_password' : u'忘記密碼?', 10 | 11 | # roles 12 | 'administrator' : u'管理者', 13 | 'administrator_explain' : u'', 14 | 15 | 'editor' : u'編輯者', 16 | 'editor_explain' : u'', 17 | 18 | 'user' : u'使用者', 19 | 'user_explain' : u'', 20 | 21 | # form fields 22 | 'real_name' : u'名稱', 23 | 'real_name_explain' : u'', 24 | 25 | 'bio' : u'個人簡歷', 26 | 'bio_explain' : u'', 27 | 28 | 'status' : u'狀態', 29 | 'status_explain' : u'', 30 | 31 | 'role' : u'角色', 32 | 'role_explain' : u'', 33 | 34 | 'username' : u'使用者名稱', 35 | 'username_explain' : u'', 36 | 'username_missing' : u'請輸入使用者名稱長度在4-16個字元,必須是字母數字下劃線組合', 37 | 38 | 'password' : u'密碼', 39 | 'password_explain' : u'', 40 | 'password_invalid' : u'密碼長度在4-16個字元,必須是字母數字下劃線組合', 41 | 42 | 'new_password' : u'新密碼', 43 | 44 | 'email' : u'Email', 45 | 'email_explain' : u'', 46 | 'email_missing' : u'請輸入有效的 Email 地址', 47 | 'email_not_found' : u'找不到個人資料。', 48 | 49 | # messages 50 | 'updated' : u'使用者個人資料已更新。', 51 | 'created' : u'使用者個人資料已建立。', 52 | 'deleted' : u'使用者個人資料已刪除。', 53 | 'delete_error' : u'不能刪除自己的個人資料', 54 | 'login_error' : u'使用者名稱或密碼錯誤。', 55 | 'logout_notice' : u'現在你已經登出。', 56 | 'recovery_sent' : u'已發送確認密碼變更的信件。', 57 | 'recovery_expired' : u'密碼復原符記已過期,請重試。', 58 | 'password_reset' : u'新密碼已經設定。立即登入!', 59 | 60 | # password recovery email 61 | 'recovery_subject' : u'重設密碼', 62 | 'recovery_message' : u'你請求重設密碼。跟隨以下連結繼續。%s', 63 | 64 | } -------------------------------------------------------------------------------- /zephyr/lib/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. -------------------------------------------------------------------------------- /zephyr/lib/image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | import os 19 | import sys 20 | from PIL import Image 21 | 22 | 23 | def img_resize(path, size): 24 | im = Image.open(path) 25 | # im.resize(size) 26 | im.thumbnail(size, Image.ANTIALIAS) 27 | im.save(path) 28 | -------------------------------------------------------------------------------- /zephyr/lib/paginator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from zephyr.jinja2t import Markup 19 | 20 | 21 | class Paginator(object): 22 | 23 | def __init__(self, results, total, page, perpage, url, glue='/'): 24 | self.results = results 25 | self.total = total 26 | self.page = page 27 | self.first = 'First' 28 | self.last = 'Last' 29 | self._next = 'Next' 30 | self._prev = 'Previous' 31 | self.perpage = perpage 32 | self.url = url 33 | self._index = 0 34 | self.glue = glue 35 | 36 | def next_link(self, text='← Previous', default=''): 37 | text = text or self._next 38 | pages = (self.total / self.perpage) + 1 39 | if self.page < pages: 40 | page = self.page + 1 41 | return '' + text + '' 42 | 43 | return default 44 | 45 | def pre_link(self, text='← Previous', default=''): 46 | text = text or self._prev 47 | if self.page > 1: 48 | page = self.page - 1 49 | return Markup('' + text + '') 50 | 51 | return Markup(default) 52 | 53 | def links(self): 54 | html = '' 55 | pages = (self.total / self.perpage) 56 | if self.total % self.perpage != 0: 57 | pages += 1 58 | ranged = 4 59 | if pages > 1: 60 | if self.page > 1: 61 | page = self.page - 1 62 | html += '' + self.first + '' + \ 63 | '' + self._prev + '' 64 | for i in range(self.page - ranged, self.page + ranged): 65 | if i < 0: 66 | continue 67 | page = i + 1 68 | if page > pages: 69 | break 70 | 71 | if page == self.page: 72 | html += '' + str(page) + '' 73 | else: 74 | html += '' + str(page) + '' 75 | 76 | if self.page < pages: 77 | page = self.page + 1 78 | 79 | html += '' + self._next + ' ' + self.last + '' 81 | 82 | return html 83 | 84 | __html__ = links 85 | 86 | def __len__(self): 87 | return len(self.results) 88 | 89 | def __iter__(self): 90 | return self 91 | 92 | def next(self): 93 | try: 94 | result = self.results[self._index] 95 | except IndexError: 96 | raise StopIteration 97 | self._index += 1 98 | return result 99 | 100 | __next__ = next # py3 compat 101 | -------------------------------------------------------------------------------- /zephyr/log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | import logging 19 | 20 | 21 | def log_config(tag, debug=False): 22 | """" 23 | be used for confguration log level and format 24 | >>> log_config('log_test', debug=True) 25 | >>> logger = logging.getLogger('utils') 26 | >>> logger.debug('test debug') 27 | >>> logger.info('test info') 28 | 29 | """ 30 | logfmt = tag + \ 31 | '[%%(levelname)s] %s%%(message)s' % '%(name)s - ' 32 | config = lambda x: logging.basicConfig(level=x, format='[%(asctime)s] ' + logfmt, datefmt='%Y%m%d %H:%M:%S') 33 | if debug: 34 | config(logging.DEBUG) 35 | else: 36 | config(logging.INFO) 37 | -------------------------------------------------------------------------------- /zephyr/module/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/__init__.py -------------------------------------------------------------------------------- /zephyr/module/category/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/category/__init__.py -------------------------------------------------------------------------------- /zephyr/module/category/mapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from zephyr.orm import BaseMapper 19 | import db 20 | 21 | 22 | 23 | __all__ = ['CategoryMapper'] 24 | 25 | from .model import Category 26 | 27 | class CategoryMapper(BaseMapper): 28 | 29 | table = 'categories' 30 | model = Category 31 | 32 | def find(self, cid): 33 | """Find category by category id, return the category model instance if category id exists in database""" 34 | data = db.select(self.table).fields('title', 'slug', 'description', 'cid').condition('cid', cid).execute() 35 | if data: 36 | return self.load(data[0], self.model) 37 | 38 | def dropdown(self): 39 | """Returns the all category id""" 40 | return db.select(self.table).fields('cid', 'title').execute(as_dict=True) 41 | 42 | def order_by_title(self): 43 | results = db.select(self.table).fields('title', 'slug', 'description', 'cid').order_by('title').execute() 44 | return [self.load(data, self.model) for data in results] 45 | 46 | categories = order_by_title 47 | 48 | def find_by_slug(self, slug): 49 | """Find all categories by slug sql like rule""" 50 | data = db.select(self.table).fields('title', 'slug', 'description', 'cid').condition('slug', slug).execute() 51 | if data: 52 | return self.load(data[0], self.model) 53 | 54 | def count(self): 55 | return db.select(self.table).fields(db.expr('COUNT(*)')).execute()[0][0] 56 | 57 | def paginate(self, page=1, perpage=10): 58 | """Paginate the categories""" 59 | results = (db.select(self.table).fields('title', 'slug', 'description', 'cid') 60 | .limit(perpage).offset((page - 1) * perpage) 61 | .order_by('title').execute()) 62 | return [self.load(data, self.model) for data in results] 63 | 64 | def create(self, category): 65 | """Create a new category""" 66 | return db.execute("INSERT INTO categories(title, slug, description) VALUES(%s, %s, %s)", 67 | (category.title, category.slug, category.description)) 68 | 69 | def save(self, category): 70 | """Save and update the category""" 71 | return (db.update(self.table). 72 | mset(dict(title=category.title, 73 | description=category.description, 74 | slug=category.slug)) 75 | .condition('cid', category.cid).execute()) 76 | 77 | def delete(self, category_id): 78 | """Delete category by category id""" 79 | return db.delete(self.table).condition('cid', category_id).execute() 80 | -------------------------------------------------------------------------------- /zephyr/module/category/model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from zephyr.breeze import Backend 18 | 19 | __all__ = ['Category'] 20 | 21 | 22 | class Category(object): 23 | 24 | def __init__(self, title, slug, description, cid=None): 25 | """The Category is used by post and page model. when load from database it takes the cid""" 26 | self.title = title 27 | self.slug = slug 28 | self.description = description 29 | if cid is not None: 30 | self.cid = cid 31 | 32 | def __str__(self): 33 | return '' % (self.cid, self.title, self.slug) 34 | 35 | def is_uncategory(self): 36 | return self.cid == 1 37 | 38 | def category_url(self): 39 | return '/category/' + self.slug 40 | 41 | def category_count(self): 42 | return Backend('Post').category_count(self.cid) 43 | -------------------------------------------------------------------------------- /zephyr/module/category/thing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from zephyr.breeze import Backend 18 | 19 | from .model import Category 20 | 21 | 22 | from zephyr.lib.paginator import Paginator 23 | from zephyr.lang import text 24 | 25 | __all__ = ['CategoryThing'] 26 | 27 | class CategoryThing(object): 28 | 29 | def __init__(self): 30 | self.category_repo = Backend(Category.__name__) 31 | self.post_repo = Backend('Post') 32 | 33 | def get_by_cid(self, category_id): 34 | return self.category_repo.find(category_id) 35 | 36 | def dropdown(self): 37 | return self.category_repo.dropdown() 38 | 39 | def page(self, page=1, perpage=10): 40 | total = self.category_repo.count() 41 | pages = self.category_repo.paginate(page, perpage) 42 | pagination = Paginator(pages, total, page, perpage, '/admin/category') 43 | return pagination 44 | 45 | def add_category(self, title, slug, description): 46 | category = Category(title, slug, description) 47 | cid = self.category_repo.create(category) 48 | category.cid = cid 49 | return category 50 | 51 | def update_category(self, category_id, title, slug, description): 52 | slug = slug or title 53 | category = Category(title, slug, description, category_id) 54 | self.category_repo.save(category) 55 | return category 56 | 57 | def delete(self, category_id): 58 | if category_id == 1: 59 | return 60 | category = self.category_repo.find(category_id) 61 | if category and self.category_repo.delete(category_id): 62 | self.post_repo.reset_post_category(category_id) 63 | -------------------------------------------------------------------------------- /zephyr/module/category/view.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | def category_menu(m): 19 | r = m.r 20 | with m.menu("/admin") as menu: 21 | menu.connect('/category', CategoryPage, name="category_page1") 22 | menu.connect(r('/category/'), CategoryPage, name="category_page") 23 | menu.connect('/category/add', AddCategory, name="category_add") 24 | menu.connect(r('/category//edit'), EditCategory, name="category_edit") 25 | menu.connect(r('/category//delete'), DeleteCategory, name="category_delete") 26 | 27 | 28 | from zephyr.breeze import Thing 29 | from zephyr.lang import text 30 | from zephyr.lib.validator import Validator 31 | from zephyr.session import security, ADMIN 32 | 33 | category_service = Thing('Category') 34 | 35 | 36 | class CategoryPage: 37 | 38 | @security(ADMIN) 39 | def get(self, page=1): 40 | pagination = category_service.page(page) 41 | self.render('admin/category/index.html', categories=pagination) 42 | 43 | 44 | class AddCategory: 45 | 46 | @security(ADMIN) 47 | def get(self): 48 | self.render('admin/category/add.html') 49 | 50 | @security(ADMIN) 51 | def post(self): 52 | _ = self.get_argument 53 | title, slug, description = _('title'), _('slug'), _('description') 54 | 55 | validator = Validator() 56 | validator.check(title, 'min', text('category.title_missing'), 1) 57 | if validator.errors: 58 | self.flash(validator.errors, 'error') 59 | return self.render('admin/category/add.html') 60 | 61 | category_service.add_category(title, slug, description) 62 | self.redirect(self.reverse_url('category_page')) 63 | 64 | 65 | class EditCategory: 66 | 67 | @security(ADMIN) 68 | def get(self, category_id): 69 | category_id = int(category_id) 70 | category = category_service.get_by_cid(category_id) 71 | return self.render('admin/category/edit.html', category=category) 72 | 73 | @security(ADMIN) 74 | def post(self, category_id): 75 | _ = self.get_argument 76 | category_id, title, slug, description = int(category_id), _('title'), _('slug'), _('description') 77 | 78 | validator = Validator() 79 | validator.check(title, 'min', text('category.title_missing'), 1) 80 | if validator.errors: 81 | self.flash(validator.errors, 'error') 82 | return self.redirect(self.reverse_url('category_edit', category_id)) 83 | 84 | category = category_service.update_category( 85 | category_id, title, slug, description) 86 | self.flash(text('category.updated'), 'success') 87 | self.redirect(self.reverse_url('category_edit', category.cid)) 88 | 89 | 90 | class DeleteCategory: 91 | 92 | @security(ADMIN) 93 | def post(self, category_id): 94 | category_id = int(category_id) 95 | if category_id == 1: 96 | self.flash('The Uncategory cann\'t delete', 'error') 97 | return self.redirect(self.reverse_url('category_page')) 98 | 99 | category_service.delete(category_id) 100 | self.flash(text('category.deleted'), 'success') 101 | self.redirect(self.reverse_url('category_page')) 102 | 103 | get = post 104 | -------------------------------------------------------------------------------- /zephyr/module/comment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/comment/__init__.py -------------------------------------------------------------------------------- /zephyr/module/comment/mapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from .model import Comment 18 | 19 | from zephyr.orm import BaseMapper 20 | import db 21 | 22 | 23 | __all__ = ['CommentMapper'] 24 | 25 | 26 | class CommentMapper(BaseMapper): 27 | 28 | table = 'comments' 29 | model = Comment 30 | 31 | def find(self, cid): 32 | data = db.select(self.table).fields('post_id', 'name', 33 | 'email', 'content', 'status', 'created', 'cid').condition('cid', cid).execute() 34 | if data: 35 | return self.load(data[0], self.model) 36 | 37 | def find_by_post_id(self, post_id, status='approved'): 38 | q = db.select(self.table).fields('post_id', 'name', 39 | 'email', 'content', 'status', 'created', 'cid').condition('post_id', post_id) 40 | if status: 41 | q.condition('status', status) 42 | data = q.execute() 43 | return [self.load(_, self.model) for _ in data] 44 | 45 | def paginate(self, page=1, perpage=10, status='all'): 46 | q = db.select(self.table).fields('post_id', 'name', 'email', 'content', 'status', 'created', 'cid') 47 | if status != 'all': 48 | q.condition('status', status) 49 | results = q.limit(perpage).offset((page - 1) * perpage).order_by('created').execute() 50 | pages = [self.load(page, self.model) for page in results] 51 | return pages 52 | 53 | def count(self): 54 | return db.select(self.table).fields(db.expr('COUNT(*)')).execute()[0][0] 55 | 56 | def spam_count(self, domain): 57 | return db.select(self.table).fields(db.expr('COUNT(*)')).condition('email', domain, 'LIKE').execute()[0][0] 58 | 59 | def create(self, comment): 60 | """Create a new comment""" 61 | return db.execute("INSERT INTO comments(post_id, name, email, content, status, created) VALUES(%s, %s, %s, %s, %s, %s)", 62 | (comment.post_id, comment.name, comment.email, comment.content, comment.status, comment.created)) 63 | 64 | def save(self, comment): 65 | """Save Comment""" 66 | q = db.update(self.table) 67 | data = dict((_, getattr(comment, _)) for _ in ('post_id', 'name', 68 | 'email', 'content', 'status', 'created', 'cid')) 69 | q.mset(data) 70 | return q.condition('cid', comment.cid).execute() 71 | 72 | def delete(self, comment_id): 73 | """Delete category by commment id""" 74 | return db.delete(self.table).condition('cid', comment_id).execute() 75 | -------------------------------------------------------------------------------- /zephyr/module/comment/model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | __all__ = ['Comment'] 18 | 19 | 20 | from datetime import datetime 21 | 22 | class Comment(object): 23 | 24 | def __init__(self, post_id, name, email, content, status, created=None, cid=None): 25 | self.post_id = post_id 26 | self.name = name 27 | self.email = email 28 | self.content = content 29 | 30 | self.status = status 31 | self.created = created or datetime.now() 32 | self.cid = cid 33 | 34 | def __json__(self): 35 | return self.__dict__.copy() -------------------------------------------------------------------------------- /zephyr/module/comment/thing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import re 18 | 19 | from zephyr.breeze import Backend 20 | from .model import Comment 21 | from zephyr.lib.paginator import Paginator 22 | from zephyr.lang import text 23 | from zephyr.helper import site 24 | 25 | 26 | __all__ = ['CommentThing'] 27 | 28 | 29 | class CommentThing(object): 30 | 31 | comment_repo = Backend('Comment') 32 | 33 | get = lambda self, cid: self.comment_repo.find(cid) 34 | 35 | def get_by_post_id(self, post_id): 36 | return self.comment_repo.find_by_post_id(post_id) 37 | 38 | def page(self, status, page=1, perpage=10): 39 | total = self.comment_repo.count() 40 | pages = self.comment_repo.paginate(page, perpage, status) 41 | pagination = Paginator(pages, total, page, perpage, '/admin/comment') 42 | return pagination 43 | 44 | def add_comment(self, name, email, content, status, post): 45 | comment = Comment(post.pid, name, email, content, status) 46 | if self.is_spam(comment): 47 | comment.status = 'spam' 48 | cid = self.comment_repo.create(comment) 49 | comment.cid = cid 50 | return comment 51 | 52 | @classmethod 53 | def is_spam(self, comment): 54 | for word in site.comment_moderation_keys(): 55 | if word.strip() and re.match(word, comment.content, re.I): 56 | return True 57 | 58 | domain = comment.email.split('@')[1] 59 | if self.comment_repo.spam_count(domain): 60 | return True 61 | return False 62 | 63 | def update_comment(self, comment_id, name, email, content, status): 64 | comment = self.get(comment_id) 65 | if not comment: 66 | return None 67 | comment.status = status 68 | comment.name = name 69 | comment.content = content 70 | comment.email = email 71 | self.comment_repo.save(comment) 72 | return comment 73 | 74 | def delete(self, comment_id): 75 | comment = self.comment_repo.find(comment_id) 76 | if not comment: 77 | return None 78 | return self.comment_repo.delete(comment.cid) 79 | -------------------------------------------------------------------------------- /zephyr/module/comment/view.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | 19 | def comment_menu(m): 20 | r = m.r 21 | with m.menu("/admin") as menu: 22 | menu.connect('/comment', CommentPage, name="comment_page1") 23 | menu.connect(r('/comment/'), CommentPage, name="comment_page2") 24 | menu.connect(r('/comment//'), CommentPage, name="comment_page3") 25 | menu.connect(r('/comment//edit'), EditComment, name="comment_edit") 26 | menu.connect(r('/comment//delete'), DeleteComment, name="comment_delete") 27 | 28 | 29 | from zephyr.session import security, ADMIN, EDITOR 30 | from zephyr.breeze import Thing 31 | from zephyr.lang import text 32 | from zephyr.lib.validator import Validator 33 | 34 | from zephyr.helper import site 35 | 36 | 37 | comment_service = Thing('Comment') 38 | 39 | COMMENT_STATUSES = [ 40 | {'url': 'all', 'lang': text('global.all'), 'class': 'all'}, 41 | {'url': 'pending', 'lang': text('global.pending'), 'class': 'pending'}, 42 | {'url': 'approved', 'lang': text('global.approved'), 'class': 'approved'}, 43 | {'url': 'spam', 'lang': text('global.spam'), 'class': 'spam'} 44 | ] 45 | 46 | 47 | class CommentPage: 48 | 49 | @security(EDITOR) 50 | def get(self, page=1, status='all'): 51 | page = int(page) 52 | pagination = comment_service.page(status, page, site.posts_per_page()) 53 | self.render('admin//comment/index.html', 54 | statuses=COMMENT_STATUSES, 55 | status=status, 56 | comments=pagination) 57 | 58 | 59 | class EditComment: 60 | 61 | @security(EDITOR) 62 | def get(self, comment_id): 63 | comment_id = int(comment_id) 64 | 65 | statuses = { 66 | 'approved': text('global.approved'), 67 | 'pending': text('global.pending'), 68 | 'spam': text('global.spam') 69 | } 70 | comment = comment_service.get(comment_id) 71 | 72 | self.render('admin/comment/edit.html', 73 | comment=comment, 74 | statuses=statuses) 75 | @security(EDITOR) 76 | def post(self, comment_id): 77 | comment_id = int(comment_id) 78 | _ = self.get_argument 79 | name = _('name') 80 | email = _('email') 81 | content = _('content') 82 | status = _('status') 83 | 84 | name, content = name.strip(), content.strip() 85 | 86 | validator = Validator() 87 | (validator.check(name, 'min', text('comment.name_missing'), 1) 88 | .check(content, 'min', text('comment.content_missing'), 1) 89 | ) 90 | if validator.errors: 91 | self.flash(validator.errors, 'error') 92 | self.redirect(self.reverse_url('comment_edit', comment_id)) 93 | 94 | comment = comment_service.update_comment( 95 | comment_id, name, email, content, status) 96 | self.flash(text('comment.updated'), 'success') 97 | self.redirect(self.reverse_url('comment_edit', comment.cid)) 98 | 99 | 100 | class DeleteComment: 101 | @security(EDITOR) 102 | def get(self, comment_id): 103 | comment_id = int(comment_id) 104 | comment_service.delete(comment_id) 105 | self.flash(text('comment.deleted'), 'success') 106 | self.redirect(self.reverse_url('comment_page')) 107 | 108 | post = get 109 | -------------------------------------------------------------------------------- /zephyr/module/extend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/extend/__init__.py -------------------------------------------------------------------------------- /zephyr/module/extend/model.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ['Extend', 'Meta', 'Field'] 3 | 4 | 5 | from zephyr.util import markdown 6 | from zephyr.jinja2t import Markup 7 | 8 | class Extend(object): 9 | 10 | def __init__(self, type, key, label, field, attributes, eid=None): 11 | """The Extend simple model""" 12 | self.key = key 13 | self.label = label 14 | self.type = type 15 | self.field = field 16 | self.attributes = attributes or {} 17 | 18 | if eid: 19 | self.eid = eid 20 | 21 | def value(self, node_id, type='post'): 22 | meta = Backend('Meta').find(type, node_id, self.eid) 23 | meta = meta or Meta(node_id, type, self.eid) 24 | return Field(self, meta) 25 | 26 | def __str__(self): 27 | return "" %(self.key, self.label) 28 | 29 | 30 | class Meta(object): 31 | 32 | def __init__(self, node_id, type, extend, data=None, mid=None): 33 | self.node_id = node_id 34 | self.type = type 35 | self.extend = extend 36 | self.data = data or {} 37 | if mid: 38 | self.mid = mid 39 | 40 | def get(self, key, default=None): 41 | return self.data.get(key, default) 42 | 43 | 44 | class Field(object): 45 | 46 | def __init__(self, extend, meta): 47 | self.extend = extend 48 | self.meta = meta 49 | 50 | def value(self): 51 | field = self.field 52 | if field == 'text': 53 | value = self.meta.get('text', '') 54 | elif field == 'html': 55 | value = markdown(self.meta.get('html', '')) 56 | elif field in ('image', 'file'): 57 | f = self.meta.get('filename', '') 58 | if f: 59 | value = '/content/' + f 60 | else: 61 | value = '' 62 | return Markup(value) 63 | 64 | 65 | @property 66 | def field(self): 67 | return self.extend.field 68 | 69 | @property 70 | def key(self): 71 | return self.extend.key 72 | 73 | @property 74 | def label(self): 75 | return self.extend.label 76 | 77 | def __html__(self): 78 | field = self.field 79 | if field == 'text': 80 | value = self.meta.get('text', '') 81 | return '' 83 | 84 | if field == 'html': 85 | value = self.meta.get('html', '') 86 | return '' 88 | 89 | if field in ('file', 'image'): 90 | value = self.meta.get('filename', '') 91 | html = '' 92 | 93 | if value: 94 | html += '' + value + '' 95 | 96 | html += ' ' 98 | 99 | return html 100 | 101 | return '' 102 | 103 | html = __html__ 104 | -------------------------------------------------------------------------------- /zephyr/module/extend/view/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from .field import field_menu 19 | 20 | 21 | def extend_menu(m): 22 | 23 | with m.menu('/admin') as menu: 24 | menu.connect("/extend", render="admin/extend/index.html", security='admin') 25 | menu.connect("/extend/variable", render="admin/extend/variable/index.html", security='admin') 26 | menu.connect("/extend/variable/add", render="admin/extend/variable/add.html", security='admin') 27 | menu.connect("/extend/variable/add", render="admin/extend/plugin/index.html", security='admin') 28 | 29 | field_menu(m) 30 | -------------------------------------------------------------------------------- /zephyr/module/front/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/front/__init__.py -------------------------------------------------------------------------------- /zephyr/module/front/view.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from zephyr.breeze import Backend, Thing, AssetHandler 18 | 19 | 20 | def front_menu(m): 21 | r = m.r 22 | m.connect(r'/content/(.*)', AssetHandler) 23 | m.connect(r'/', FrontPage, name='site_page') 24 | m.connect(r('/'), FrontPage) 25 | m.connect(r('/post/'), SlugPostPage, name='site_post') 26 | m.connect(r'/posts', PostPage) 27 | m.connect(r('/posts/'), PostPage) 28 | m.connect(r('/posts/'), PostPage) 29 | m.connect(r('/posts//'), PostPage) 30 | m.connect(r'/feed/rss.json', FeedPage) 31 | m.connect(r'/feed/rss', FeedXMLPage) 32 | m.connect(r('/post/comment/'), PostComment) 33 | 34 | 35 | from .mixin import FeedMixin, FrontMixin 36 | from zephyr.helper import site 37 | 38 | 39 | class FrontPage(FrontMixin, FeedMixin): 40 | 41 | def get(self, slug=None): 42 | if slug: 43 | if slug == 'admin': 44 | return self.post_admin_page() 45 | elif slug == 'search': 46 | return self.search() 47 | elif slug == 'rss': 48 | return self.feed_rss() 49 | 50 | slug = slug.split('/')[-1] 51 | page = self.page_service.get_by_slug(slug) 52 | else: 53 | site_page = site.get('site_page', 0) 54 | if site_page == 0: 55 | return self.posts() 56 | else: 57 | page = self.page_service.get(site_page) 58 | 59 | if not page: 60 | self.notfound() 61 | self.theme_render('page.html', 62 | page_content=page.content, 63 | page_title=page.title, 64 | page=page) 65 | 66 | 67 | class PostComment(FrontMixin): 68 | 69 | def post(self, slug): 70 | self.post_comment(slug) 71 | 72 | 73 | class PostPage(FrontMixin): 74 | 75 | def get(self, page=1, category=None): 76 | page = int(page) 77 | self.posts(page, category) 78 | 79 | 80 | class SlugPostPage(FrontMixin): 81 | 82 | def get(self, slug): 83 | self.post_page(slug) 84 | 85 | 86 | class FeedPage(FeedMixin): 87 | 88 | def get(self): 89 | self.feed_json() 90 | 91 | 92 | class FeedXMLPage(FeedMixin): 93 | 94 | def get(self): 95 | self.feed_rss() 96 | -------------------------------------------------------------------------------- /zephyr/module/menu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/menu/__init__.py -------------------------------------------------------------------------------- /zephyr/module/menu/thing.py: -------------------------------------------------------------------------------- 1 | 2 | from zephyr.breeze import Backend 3 | from zephyr.lib.paginator import Paginator 4 | 5 | __all__ = ['MenuThing'] 6 | 7 | 8 | class MenuThing(object): 9 | 10 | def __init__(self): 11 | self.page_repo = Backend('Page') 12 | 13 | def menu(self, page=1): 14 | pages = self.page_repo.menu(True) 15 | return pages 16 | 17 | def update(self, sort): 18 | for menu_order, pid in enumerate(sort): 19 | self.page_repo.update_menu_order(pid, menu_order) -------------------------------------------------------------------------------- /zephyr/module/menu/view.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | def menu_menu(m): 19 | m.connect('/admin/menu', MenuPage, name='menu_page') 20 | m.connect('/admin/menu/update', UpdateMenu, name='menu_update') 21 | 22 | 23 | from zephyr.session import security, ADMIN 24 | from zephyr.breeze import Thing 25 | 26 | menuservice = Thing('Menu') 27 | 28 | 29 | class MenuPage: 30 | 31 | @security(ADMIN) 32 | def get(self): 33 | pages = menuservice.menu(True) 34 | self.render('admin/menu/index.html', messages='', 35 | pages=pages) 36 | 37 | 38 | class UpdateMenu: 39 | 40 | @security(ADMIN) 41 | def post(self): 42 | sort = self.get_arguments('sort') 43 | menuservice.update(sort) 44 | self.jsonify({'return': True}) 45 | 46 | get = post 47 | -------------------------------------------------------------------------------- /zephyr/module/page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/page/__init__.py -------------------------------------------------------------------------------- /zephyr/module/page/model.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Page'] 2 | 3 | 4 | from zephyr.breeze import Backend 5 | 6 | class Page(object): 7 | 8 | def __init__(self, parent, name, title, slug, content, status, redirect, show_in_menu, pid=None): 9 | 10 | self.parent = parent 11 | self.name = name 12 | self.title = title 13 | self.slug = slug 14 | self.content = content 15 | self.status = status 16 | self.redirect = redirect 17 | self.show_in_menu = show_in_menu 18 | if pid: 19 | self.pid = pid 20 | 21 | 22 | def custom_field(self, key): 23 | extend = Backend('extend').field('page', key) 24 | return extend.value(self.pid, type='page') 25 | 26 | def url(self): 27 | segments = [self.slug] 28 | parent = self.parent 29 | Store = Backend('page') 30 | while parent: 31 | page = Store.find(parent) 32 | if page: 33 | segments.insert(0, page.slug) 34 | parent = page.parent 35 | else: 36 | break 37 | 38 | return '/' + '/'.join(segments) -------------------------------------------------------------------------------- /zephyr/module/page/thing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from .model import Page 19 | 20 | from zephyr.breeze import Backend 21 | from zephyr.lib.paginator import Paginator 22 | from zephyr.lang import text 23 | 24 | 25 | __all__ = ['PageThing'] 26 | 27 | class PageThing(object): 28 | 29 | def __init__(self): 30 | self.page_repo = Backend(Page.__name__) 31 | 32 | get = lambda self, pid: self.page_repo.find(pid) 33 | 34 | def get_by_redirect(self, redirect): 35 | return self.page_repo.find_by_redirect(redirect) 36 | 37 | def get_by_slug(self, slug): 38 | return self.page_repo.find_by_slug(slug) 39 | 40 | def dropdown(self, show_in_menu=True): 41 | return self.page_repo.dropdown(show_in_menu) 42 | 43 | def page(self, status, page=1, perpage=10): 44 | total = self.page_repo.count(status) 45 | pages = self.page_repo.paginate(page, perpage, status) 46 | if status: 47 | url = '/admin/page/status/' + status 48 | else: 49 | url = '/admin/page' 50 | pagination = Paginator(pages, total, page, perpage, url) 51 | return pagination 52 | 53 | def delete(self, page_id): 54 | page = self.page_repo.find(page_id) 55 | if not page: 56 | return None 57 | return self.page_repo.delete(page.pid) 58 | 59 | def add_page(self, parent, name, title, slug, content, status, redirect, show_in_menu): 60 | redirect = redirect.strip() 61 | show_in_menu = 1 if show_in_menu else 0 62 | page = Page(parent, name, title, slug, content, status, redirect, show_in_menu) 63 | pid = self.page_repo.create(page) 64 | page.pid = pid 65 | return page 66 | 67 | def is_exist_slug(self, slug): 68 | return self.page_repo.count_slug(slug) == 1 69 | 70 | def update_page(self, parent, name, title, slug, content, status, redirect, show_in_menu, pid): 71 | show_in_menu = 1 if show_in_menu else 0 72 | redirect = redirect.strip() 73 | page = Page(parent, name, title, slug, content, status, redirect, show_in_menu, pid) 74 | self.page_repo.save(page) 75 | return page 76 | -------------------------------------------------------------------------------- /zephyr/module/post/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/post/__init__.py -------------------------------------------------------------------------------- /zephyr/module/post/model.py: -------------------------------------------------------------------------------- 1 | from zephyr.breeze import Backend 2 | from zephyr.helper import cached_user 3 | from zephyr.util import lazy_attr 4 | 5 | 6 | __all__ = ['Post'] 7 | 8 | class Post(object): 9 | 10 | def __init__(self, title, slug, description, html, css, js, category, status, allow_comment, 11 | author=None, updated=None, created=None, pid=None): 12 | self.title = title 13 | self.slug = slug 14 | self.description = description 15 | 16 | 17 | self.html = html 18 | self.css = css 19 | self.js = js 20 | self.category = category 21 | self.status = status 22 | self.allow_comment = allow_comment 23 | self.author = author 24 | self.pid = pid 25 | 26 | self.updated = updated or datetime.now() 27 | self.created = created or datetime.now() 28 | 29 | 30 | @property 31 | def user(self): 32 | return cached_user(self.author) 33 | 34 | @lazy_attr 35 | def comments(self): 36 | return Backend('Comment').find_by_post_id(self.pid) 37 | 38 | 39 | def __json__(self): 40 | data = self.__dict__.copy() 41 | del data['js'] 42 | del data['css'] 43 | del data['slug'] 44 | del data['commments'] 45 | return data 46 | 47 | 48 | def custom_field(self, key): 49 | extend = Backend('extend').field('post', key) 50 | return extend.value(self.pid, type='post') 51 | -------------------------------------------------------------------------------- /zephyr/module/storage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/storage/__init__.py -------------------------------------------------------------------------------- /zephyr/module/storage/mapper.py: -------------------------------------------------------------------------------- 1 | 2 | from zephyr.orm import BaseMapper 3 | import db 4 | from .model import Pair 5 | 6 | __all__ = ['PairMapper'] 7 | 8 | class PairMapper(BaseMapper): 9 | 10 | model = Pair 11 | table = 'storage' 12 | 13 | def lists(self, exclude=None, sorted=False): 14 | q = db.select(self.table) 15 | if sorted: 16 | q.sort_by('key') 17 | if exclude: 18 | db.condition('key', exculde, '<>') 19 | res = q.execute() 20 | return [self.load(row, self.model) for row in res] 21 | 22 | def find(self, key): 23 | data = db.select(self.table).condition('key', key).execute() 24 | if data: 25 | return self.load(data[0], self.model) 26 | 27 | def save(self, pair): 28 | return db.insert(self.table).values((pair.key, pair.value, pair.type)).execute() 29 | 30 | def update(self, pair): 31 | return db.update(self.table).set('value', pair.value).condition('key', pair.key).execute() 32 | 33 | def delete(self, pair): 34 | return db.delete(self.table).condition('key', pair.key).condition('type', pair.type).execute() 35 | -------------------------------------------------------------------------------- /zephyr/module/storage/model.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ['Pair'] 3 | 4 | 5 | from tornado.escape import json_decode 6 | 7 | class Pair(object): 8 | 9 | def __init__(self, key, value): 10 | self.key = key 11 | self.value = value 12 | 13 | def json_value(self): 14 | return json_decode(self.value) -------------------------------------------------------------------------------- /zephyr/module/storage/thing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | import re 19 | 20 | from .mapper import PairMapper 21 | from .model import Pair 22 | from tornado.escape import json_encode 23 | 24 | 25 | 26 | __all__ = ['PairThing'] 27 | 28 | 29 | class PairThing(object): 30 | 31 | def __init__(self): 32 | self.pair_repo = PairMapper() 33 | 34 | def site_meta(self): 35 | return self.pair_repo.find('system') 36 | 37 | def update_site_meta(self, sitename, description, site_page, 38 | posts_per_page, auto_published_comments, comment_moderation_keys): 39 | 40 | meta = self.site_meta() 41 | config = meta.json_value() 42 | 43 | try: 44 | sitename = sitename or sitename.strip() 45 | if sitename: 46 | config['sitename'] = sitename 47 | 48 | description = description or description.strip() 49 | if description: 50 | config['description'] = description 51 | 52 | site_page = int(site_page) 53 | if site_page >= 0: 54 | config['site_page'] = site_page 55 | 56 | posts_per_page = int(posts_per_page) 57 | if posts_per_page: 58 | config['posts_per_page'] = posts_per_page 59 | 60 | auto_published_comments = bool(auto_published_comments) 61 | config['auto_published_comments'] = auto_published_comments 62 | if comment_moderation_keys is not None: 63 | keys = [key.strip() for key in re.split(' +', comment_moderation_keys) if key.strip()] 64 | config['comment_moderation_keys'] = keys 65 | meta.value = json_encode(config) 66 | self.pair_repo.update(meta) 67 | return True 68 | except: 69 | return False 70 | -------------------------------------------------------------------------------- /zephyr/module/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteclover/Zephyr/381a4c456d41ed24cded13913cc36d38284ce42d/zephyr/module/user/__init__.py -------------------------------------------------------------------------------- /zephyr/module/user/mapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 2015-2016 Copyright (C) zephyr 3 | 4 | from .model import User 5 | import db 6 | from zephyr.orm import BaseMapper 7 | 8 | __all__ = ['UserMapper'] 9 | 10 | class UserMapper(BaseMapper): 11 | 12 | model = User 13 | table = 'users' 14 | 15 | def find(self, uid): 16 | """Find and load the user from database by uid(user id)""" 17 | data = (db.select(self.table).select('username', 'email', 'real_name', 18 | 'secure_pass', 'secure_salt', 'bio', 'status', 'role', 'uid'). 19 | condition('uid', uid).execute() 20 | ) 21 | if data: 22 | return self.load(data[0], self.model) 23 | 24 | def find_by_username(self, username): 25 | """Return user by username if find in database otherwise None""" 26 | data = (db.select(self.table).select('username', 'email', 'real_name', 27 | 'secure_pass', 'secure_salt', 'bio', 'status', 'role', 'uid'). 28 | condition('username', username).execute() 29 | ) 30 | if data: 31 | return self.load(data[0], self.model) 32 | 33 | def find_by_email(self, email): 34 | """Return user by email if find in database otherwise None""" 35 | data = (db.select(self.table).select('username', 'email', 'real_name', 36 | 'secure_pass', 'secure_salt', 'bio', 'status', 'role', 'uid'). 37 | condition('email', email).execute() 38 | ) 39 | if data: 40 | return self.load(data[0], self.model) 41 | 42 | 43 | def create(self, user): 44 | return db.execute("INSERT INTO users(username, email, real_name, secure_pass, secure_salt, bio, status, role) \ 45 | VALUES(%s, %s, %s, %s, %s, %s, %s)", 46 | (user.username, user.email, user.real_name, user.secure_pass, user.secure_salt, user.bio, user.status, user.role)) 47 | 48 | def search(self, **kw): 49 | """Find the users match the condition in kw""" 50 | q = db.select(self.table).select('username', 'email', 'real_name', 51 | 'secure_pass', 'secure_salt', 'bio', 'status', 'role', 'uid').condition('status', 'active') 52 | for k, v in kw: 53 | q.condition(k, v) 54 | data = q.execute() 55 | users = [] 56 | for user in data: 57 | users.append(self.load(user, self.model)) 58 | return users 59 | 60 | def count(self): 61 | return db.query('SELECT COUNT(*) FROM ' + self.table)[0][0] 62 | 63 | def take(self, page=1, perpage=10): 64 | count = self.count() 65 | q = db.select(self.table).select('username', 'email', 'real_name', 66 | 'secure_pass', 'secure_salt', 'bio', 'status', 'role', 'uid') 67 | results = q.limit(perpage).offset((page - 1) * perpage).order_by('real_name', 'desc').execute() 68 | users = [self.load(user, self.model) for user in results] 69 | return users 70 | 71 | def save(self, user): 72 | q = db.update(self.table) 73 | data = dict( (_, getattr(user, _)) for _ in ('username', 'email', 'real_name', 'secure_pass', 'secure_salt', 74 | 'bio', 'status', 'role')) 75 | q.mset(data) 76 | return q.condition('uid', user.uid).execute() 77 | 78 | def delete(self, user): 79 | return db.delete(self.table).condition('uid', user.uid).execute() 80 | -------------------------------------------------------------------------------- /zephyr/module/user/model.py: -------------------------------------------------------------------------------- 1 | 2 | import base64 3 | import uuid 4 | from hashlib import sha224 5 | 6 | from datetime import datetime 7 | 8 | 9 | __all__ = ['User'] 10 | 11 | class User(object): 12 | 13 | 14 | def __init__(self, username, email, real_name, password, salt, bio, status, role='user', uid=None): 15 | """If the user load from database, if will intialize the uid and secure password. 16 | Otherwise will hash encrypt the real password 17 | 18 | arg role enum: the string in ('root', 'user', 'editor', 'administrator') 19 | arg status enum: the string in ('active', 'inactive') 20 | arg password fix legnth string: the use sha224 password hash 21 | """ 22 | self.username = username 23 | self.email = email 24 | self.real_name = real_name 25 | self.bio = bio 26 | self.status = status 27 | self.role = role 28 | 29 | self.uid = uid 30 | if self.uid is not None: 31 | self._secure_pass = password 32 | self.secure_salt = salt 33 | else: 34 | self.secure_salt = self.gen_salt() 35 | self._secure_pass = self.secure_password(password) 36 | 37 | 38 | def inactive(self): 39 | return self.status == 'inactive' 40 | 41 | def gen_salt(self): 42 | return base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes) 43 | 44 | def secure_pass(): 45 | doc = "The secure_pass property." 46 | def fget(self): 47 | return self._secure_pass 48 | def fset(self, value): 49 | self._secure_pass = self.secure_password(value) 50 | def fdel(self): 51 | del self._secure_pass 52 | return locals() 53 | secure_pass = property(**secure_pass()) 54 | 55 | def secure_password(self, password): 56 | """Encrypt password to sha224 hash""" 57 | return sha224(self.secure_salt + password).hexdigest() 58 | 59 | def check(self, password): 60 | return self.secure_pass == self.secure_password(password) -------------------------------------------------------------------------------- /zephyr/options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from argparse import ArgumentParser 19 | import sys 20 | 21 | from zephyr.config import ConfigFactory 22 | 23 | 24 | class Options(object): 25 | 26 | def __init__(self, help_doc=None, args=sys.argv): 27 | self.args = args 28 | if help_doc is None: 29 | self.argparser = ArgumentParser(add_help=False) 30 | else: 31 | self.argparser = ArgumentParser(help_doc) 32 | 33 | def group(self, help_doc): 34 | group_parser = self.argparser.add_argument_group(help_doc) 35 | return GroupOptions(group_parser) 36 | 37 | def set_defaults(self, **c): 38 | self.argparser.set_defaults(**c) 39 | 40 | @property 41 | def define(self): 42 | return self.argparser.add_argument 43 | 44 | def parse_args(self, args=None): 45 | args = args if args is not None else self.args 46 | opt, _ = self.argparser.parse_known_args(args) 47 | return opt 48 | 49 | 50 | class GroupOptions(object): 51 | 52 | def __init__(self, group_parser): 53 | self.group_parser = group_parser 54 | 55 | @property 56 | def define(self): 57 | return self.group_parser.add_argument 58 | 59 | def __enter__(self): 60 | return self 61 | 62 | def __exit__(self, exc_type, exc_value, traceback): 63 | pass 64 | 65 | __options = None 66 | 67 | 68 | def setup_options(doc): 69 | global __options 70 | __options = Options(doc) 71 | 72 | 73 | def group(help_doc): 74 | return __options.group(help_doc) 75 | 76 | 77 | def define(*args, **kw): 78 | return __options.define(*args, **kw) 79 | 80 | 81 | def set_defaults(**c): 82 | __options.set_defaults(**c) 83 | 84 | 85 | def parse_args(): 86 | return __options.parse_args() 87 | -------------------------------------------------------------------------------- /zephyr/orm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | import db 19 | 20 | 21 | class BaseMapper(object): 22 | 23 | def load(self, data, o): 24 | return o(*data) 25 | 26 | 27 | class PrimaryTrait(object): 28 | 29 | def findByKey(self, **kw): 30 | q = db.select(self.table) 31 | for k, v in kw.items(): 32 | q.condition(k, v) 33 | data = q.query() 34 | if data: 35 | return self.load(data[0], self.model) 36 | -------------------------------------------------------------------------------- /zephyr/pedis.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | # 4 | # Copyright 2015-2016 zephyr 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | 19 | import redis 20 | 21 | 22 | __redis = {} 23 | 24 | 25 | def setup(host='localhost', port=6379, db=0, max_connections=None, key='default', password=None, socket_timeout=None, **connection_kwargs): 26 | pool = redis.ConnectionPool(host=host, port=port, db=db, max_connections=max_connections, password=password, socket_timeout=socket_timeout, **connection_kwargs) 27 | __redis[key] = redis.Redis(connection_pool=pool) 28 | 29 | 30 | def db(key='default'): 31 | return __redis[key] 32 | -------------------------------------------------------------------------------- /zephyr/template/admin/403.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

4 |

403 Forbidden

5 |

You cant's access this module {{message}}

6 |

7 |
8 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/category/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('category.create_category')}}

4 |
5 |
6 | {{ flash() }} 7 |
8 | {##} 9 |
10 |

11 | 12 | 13 | {{__('category.title_explain')}} 14 |

15 |

16 | 17 | 18 | {{__('category.slug_explain')}} 19 |

20 |

21 | 22 | 24 | {{__('category.description_explain')}} 25 |

26 |
27 | 30 |
31 |
32 | 33 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/category/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 | {% set title = __('category.edit_category', args = (category.title,)) %} 4 |

{{title|safe}}

5 |
6 |
7 | {{ flash() }} 8 | 9 |
10 |
11 |

12 | 13 | 14 | {{__('category.title_explain')}} 15 |

16 |

17 | 18 | 19 | {{__('category.slug_explain')}} 20 |

21 |

22 | 23 | 24 | {{__('category.description_explain')}} 25 |

26 |
27 | 31 |
32 |
33 | 34 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/category/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('category.category')}}

4 | 7 |
8 |
9 | {{ flash() }} 10 | 20 | 21 |
22 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/comment/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('comment.editing_comment')}}

4 |
5 |
6 |
7 | {{flash()}} 8 |
9 |

10 | 11 | 12 | {{__('comment.name_explain')}} 13 |

14 |

15 | 16 | 17 | {{__('comment.email_explain')}} 18 |

19 |

20 | 21 | 22 | {{__('comment.content_explain')}} 23 |

24 |

25 | 26 | 35 | {{__('comment.status_explain')}} 36 |

37 |
38 | 42 |
43 |
44 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/comment/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('comment.comment')}}

4 |
5 |
6 | {{flash()}} 7 | 12 | {% if comments %} 13 | 24 | 25 | {% else %} 26 |

27 | 28 | {{__('comment.nocomments_desc')}} 29 |

30 | {% endif %} 31 |
32 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/extend/field/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('extend.create_field')}}

4 |
5 |
6 | {{ flash() }} 7 |
8 | {# #} 9 |
10 |

11 | 12 | 17 | {{__('extend.type_explain')}} 18 |

19 |

20 | 21 | 26 | {{__('extend.field_explain')}} 27 |

28 |

29 | 30 | 31 | {{__('extend.key_explain')}} 32 |

33 |

34 | 35 | 36 | {{__('extend.label_explain')}} 37 |

38 |

39 | 40 | 41 | {{__('extend.attribute_type_explain')}} 42 |

43 |

44 | 45 | 47 | {{__('extend.attributes_size_width_explain')}} 48 |

49 |

50 | 51 | 53 | {{__('extend.attributes_size_height_explain')}} 54 |

55 |
56 | 59 |
60 |
61 | 62 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/extend/field/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 | {% set title = __('extend.editing_custom_field', args=(field.label,)) %} 4 |

{{title|safe}}

5 |
6 |
7 | {{ flash() }} 8 |
9 |
10 |

11 | 12 | 18 | {{__('extend.type_explain')}} 19 |

20 |

21 | 22 | 28 | {{__('extend.field_explain')}} 29 |

30 |

31 | 32 | 33 | {{__('extend.key_explain')}} 34 |

35 |

36 | 37 | 38 | {{__('extend.label_explain')}} 39 |

40 |

41 | 42 | {% set value = field.attributes.get('type', '') %} 43 | 44 | {{__('extend.attribute_type_explain')}} 45 |

46 |

47 | 48 | {% set value = field.attributes['size'].get('width') if 'size' in field.attributes and field.attributes['size'].get('width') else '' %} 49 | 51 | {{__('extend.attributes_size_width_explain')}} 52 |

53 |

54 | 55 | {% set value = field.attributes['size'].get('height') if 'size' in field.attributes and field.attributes['size'].get('height') else '' %} 56 | 58 | {{__('extend.attributes_size_height_explain')}} 59 |

60 |
61 | 67 |
68 |
69 | 70 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/extend/field/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('extend.fields')}}

4 | 7 |
8 |
9 | {% if fields %} 10 | 20 | 21 | {% else %} 22 |

23 | {{__('extend.nofields_desc')}} 24 |

25 | {% endif %} 26 |
27 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/extend/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('extend.variables')}}

4 |
5 |
6 | 26 |
27 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/extend/metadata/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('metadata.metadata')}}

4 |
5 |
6 | {{ flash() }} 7 |
8 |
9 |

10 | 11 | 12 | {{__('metadata.sitename_explain')}} 13 |

14 |

15 | 16 | 17 | {{__('metadata.sitedescription_explain')}} 18 |

19 |

20 | 21 | 35 | {{__('metadata.homepage_explain')}} 36 |

37 |

38 | 39 | 40 | {{__('metadata.posts_per_page_explain')}} 41 |

42 |
43 |
44 | {{__('metadata.comment_settings')}} 45 |

46 | 47 | {% set checked = handler.get_argument('auto_published_comments', auto_published_comments) and ' checked' or '' %} 48 | 49 | {{__('metadata.auto_publish_comments_explain')}} 50 |

51 |

52 | 53 | 55 | {{__('metadata.comment_moderation_keys_explain')}} 56 |

57 |
58 | 61 |
62 |
63 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/extend/plugin/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

Plugins

4 |
5 |
6 |

7 | Soon. 8 |

9 |
10 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/layout/edit.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /zephyr/template/admin/layout/footer.html: -------------------------------------------------------------------------------- 1 |
2 | {{__('global.powered_by_white', args= (0.1,))}} 3 | {{__('global.make_blogging_beautiful')}} 4 |
5 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /zephyr/template/admin/layout/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{__('global.manage')}} - {{site.sitename()}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | {% if not current_user.is_guest() %} 22 | 36 | {{__('global.logout')}} 37 | {% else %} 38 | 41 | {% endif %} 42 |
43 |
-------------------------------------------------------------------------------- /zephyr/template/admin/menu/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('menu.menu', 'Menu')}}

4 |
5 |
6 | {% if pages %} 7 |
    8 | {% for page in pages %} 9 |
  • 10 | {{page.name}} 11 |
  • 12 | {% endfor %} 13 |
14 | {% else %} 15 |

16 | 17 | No menu items yet. 18 |

19 | {% endif %} 20 |
21 | 22 | 38 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/page/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 | {{ flash() }} 4 |
5 |
6 | 7 | 11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 | {% include "admin/layout/edit.html" %} 22 |
23 |
24 |
25 |
26 |

27 | 28 | 29 | {{__('page.show_in_menu_explain')}} 30 |

31 |

32 | 33 | 34 | {{__('page.name_explain')}} 35 |

36 |

37 | 38 | 39 | {{__('page.slug_explain')}} 40 |

41 |

42 | 43 | 48 | {{__('page.status_explain')}} 49 |

50 |

51 | 52 | 57 | {{__('page.parent_explain')}} 58 |

59 | {% for field in fields %} 60 |

61 | 62 | {{field}} 63 |

64 | {% endfor %} 65 |
66 |
67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/page/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |
4 | {{ flash() }} 5 |
6 | 7 | 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 | {% include "admin/layout/edit.html" %} 23 |
24 |
25 |
26 |
27 |

28 | 29 | 30 | {{__('page.show_in_menu_explain')}} 31 |

32 |

33 | 34 | 35 | {{__('page.name_explain')}} 36 |

37 |

38 | 39 | 40 | {{__('page.slug_explain')}} 41 |

42 |

43 | 44 | 53 | {{__('page.status_explain')}} 54 |

55 |

56 | 57 | 62 | {{__('page.parent_explain')}} 63 |

64 | {% for field in fields %} 65 |

66 | 67 | {{field}} 68 |

69 | {% endfor %} 70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 80 | {%include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/page/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('page.page')}}

4 | {% if pages %} 5 | 8 | {% endif %} 9 |
10 |
11 | 18 | {% if pages %} 19 | 34 | 35 | 36 | {% else %} 37 | 42 | {% endif %} 43 |
44 | {%include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/post/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 | {{flash()}} 4 |
5 |
6 | 7 | 10 |
11 |
12 |
13 |
14 | 15 | {% include "admin/layout/edit.html" %} 16 |
17 | 18 |
19 |
20 |
21 |

22 | 23 | 24 | {{__('post.slug_explain')}} 25 |

26 |

27 | 28 | 29 | {{__('post.description_explain')}} 30 |

31 |

32 | 33 | 38 | {{__('post.status_explain')}} 39 |

40 |

41 | 42 | 47 | {{__('post.category_explain')}} 48 |

49 |

50 | 51 | 52 | {{__('post.allow_comments_explain')}} 53 |

54 |

55 | 56 | 57 | {{__('post.custom_css_explain')}} 58 |

59 |

60 | 61 | 62 | {{__('post.custom_js_explain')}} 63 |

64 | {% for field in fields %} 65 |

66 | 67 | {{field}} 68 |

69 | {% endfor %} 70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 81 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/post/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('post.post')}}

4 | {% if posts %} 5 | 8 | {% endif %} 9 |
10 |
11 | {{flash()}} 12 | 18 | {% if posts %} 19 | 35 | 36 | {% else %} 37 |

38 | 39 | {{__('post.noposts_desc')}}
40 | {{__('post.create_post')}} 41 |

42 | {% endif %} 43 |
44 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/user/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('user.add_user')}}

4 |
5 |
6 |
7 | {{flash()}} 8 |
9 |

10 | 11 | 12 | {{__('user.real_name_explain')}} 13 |

14 |

15 | 16 | 17 | {{__('user.bio_explain')}} 18 |

19 |

20 | 21 | 30 | {{__('user.status_explain')}} 31 |

32 |

33 | 34 | 43 | {{__('user.role_explain')}} 44 |

45 |
46 |
47 |

48 | 49 | 50 | {{__('user.role_explain')}} 51 |

52 |

53 | 54 | 55 | {{__('user.password_explain')}} 56 |

57 |

58 | 59 | 60 | {{__('user.email_explain')}} 61 |

62 |
63 | 66 |
67 |
68 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/user/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('users.editing_user', user.username)}}

4 |
5 |
6 |
7 | {{flash()}} 8 |
9 |

10 | 11 | 12 | {{__('user.real_name_explain')}} 13 |

14 |

15 | 16 | 17 | echo {{__('user.bio_explain')}} 18 |

19 |

20 | 21 | 30 | {{__('user.status_explain')}} 31 |

32 |

33 | 34 | 46 | {{__('user.role_explain')}} 47 |

48 |
49 |
50 |

51 | 52 | 53 | ('user.role_explain')}} 54 |

55 |

56 | 57 | 58 | {{__('user.password_explain')}} 59 |

60 |

61 | 62 | 63 | New Password 64 |

65 |

66 | 67 | 68 | Password Confirm 69 |

70 |

71 | 72 | 73 | {{__('user.email_explain')}} 74 |

75 |
76 | 82 |
83 |
84 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/user/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('user.user')}}

4 | {% if current_user.is_root() %} 5 | 8 | {% endif %} 9 |
10 |
11 | {{flash()}} 12 | 23 | 24 |
25 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/admin/user/login.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 | 17 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /zephyr/template/component/views.html: -------------------------------------------------------------------------------- 1 | {% macro ExtendView(extend) -%} 2 | {% set field = extend.field %} 3 | {% if field == 'text' %} 4 | {{extend.meta.get('html', '')}}> 8 | {% elif field in ('file', 'image') %} 9 | 10 | {% set value = extend.meta.get('filename', '') %} 11 | 12 | {% if value %} 13 | {{value}} 14 | {% endif %} 15 | 16 | 17 | 18 | 19 | 20 | {% endif %} 21 | {% endmacro %} 22 | -------------------------------------------------------------------------------- /zephyr/template/theme/default/403.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |
4 |

5 |

403 Forbidden

6 |

You cant's access this page

7 |

8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /zephyr/template/theme/default/404.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |
4 |

Page not found

5 |

Unfortunately, the page {{request.path}} could not be found. Your best is either to try the 6 | homepage , try searching , or go and cry in corner (although I don’t recommend the latter).

7 |

8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /zephyr/template/theme/default/article.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% if article.css or article.jss %} 3 | {% block head %} 4 | 5 | 6 | 7 | 8 | {% endblock %} 9 | {% endif %} 10 | {% block body %} 11 |
12 |

{{article.title}}

13 |
14 | {{article.html|markdown|safe}} 15 |
16 |
17 | {% if article.allow_comment %} 18 |
19 |
    20 | {% for i, comment in enumerate(comments) %} 21 |
  • 22 |
    23 |

    {{comment.name }}

    24 | 25 |
    26 | {{comment.content}} 27 |
    28 | {{ i + 1 }} 29 |
    30 |
  • 31 | {% endfor %} 32 |
33 |
34 |

35 | 36 | 37 |

38 | 42 |

43 | 44 | 45 |

46 |

47 | 48 |

49 |
50 |
51 | {% endif %} 52 | {% endblock %} -------------------------------------------------------------------------------- /zephyr/template/theme/default/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 11 | 12 | 24 |
25 |
26 | 27 |
28 | 29 | {% set menus = menus() %} 30 | {% if menus %} 31 | {% macro menu_active(menu) %} 32 | {% if page is defined and page.slug == menu.slug and page.pid == menu.pid %} 33 | class="active" 34 | {% endif %} 35 | {% endmacro %} 36 | 49 | {% endif %} 50 |
-------------------------------------------------------------------------------- /zephyr/template/theme/default/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | © 2015-2016 {{site.sitename()}}. All rights reserved. 4 | 5 | 12 |
13 | 14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /zephyr/template/theme/default/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{page_title}} - {{site.sitename()}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /zephyr/template/theme/default/layout.html: -------------------------------------------------------------------------------- 1 | {% include 'theme/default/header.html' %} 2 | {% block head %}{% endblock %} 3 | 4 | {% include 'theme/default/body.html' %} 5 | {% block body %}{% endblock %} 6 | 7 | {% block footer %}{% endblock %} 8 | {% include 'theme/default/footer.html' %} -------------------------------------------------------------------------------- /zephyr/template/theme/default/page.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |
4 |

{{page_title}}

5 | {{page_content|markdown|safe}} 6 |
7 | {% endblock %} -------------------------------------------------------------------------------- /zephyr/template/theme/default/posts.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 | {% if posts %} 4 |
5 |
    6 | {% set i = 1 %} 7 | {% for post in posts %} 8 | {% set bg = 'background: hsl(215, 28%%, %d%%)' %( (i /10 * 20) + 20) %} 9 | {% set i = i + 1 %} 10 |
  • 11 |
    12 |

    13 | {{ post.title }} 14 |

    15 |
    16 | Posted by author {{ post.user.username }}. 17 |
    18 |
    19 |
  • 20 | {% endfor %} 21 |
22 | {% if post_total > site.posts_per_page() %} 23 | 29 | {% endif %} 30 |
31 | {% else %} 32 |
33 |

Nothing

34 |

Looks like you have some writing to do!

35 |
36 | {% endif %} 37 | {% endblock %} -------------------------------------------------------------------------------- /zephyr/template/theme/default/search.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |

You searched for “{{search_term}}”.

4 | {% if articles %} 5 |
    6 | {% for article in articles %} 7 |
  • 8 | 13 |
  • 14 | {% endfor %} 15 |
16 | {% else %} 17 |

Unfortunately, there's no results for “{{key}}”. Did you spell everything correctly?

18 | {% endif %} 19 | {% if articles.total > site.posts_per_page() %} 20 | 26 | {% endif %} 27 | {% endblock %} -------------------------------------------------------------------------------- /zephyr/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2016 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from hashlib import sha256 18 | import os 19 | import re 20 | 21 | from markdown import Markdown 22 | from tornado.util import unicode_type 23 | 24 | 25 | def markdown(content): 26 | return Markdown().convert(content) 27 | 28 | 29 | 30 | def hide_pass_for_config(config): 31 | for key, value in config.iteritems(): 32 | if ('pass' in key.lower() or 'secret' in key.lower()) and isinstance(value, (str, unicode)): 33 | config[key] = 'hide: %s' % (sha256(value).hexdigest()) 34 | elif isinstance(value, dict): 35 | hide_pass_for_config(value) 36 | 37 | 38 | class lazy_attr(object): 39 | 40 | def __init__(self, wrapped): 41 | self.wrapped = wrapped 42 | try: 43 | self.__doc__ = wrapped.__doc__ 44 | except: # pragma: no cover 45 | pass 46 | 47 | def __get__(self, inst, objtype=None): 48 | if inst is None: 49 | return self 50 | val = self.wrapped(inst) 51 | setattr(inst, self.wrapped.__name__, val) 52 | return val 53 | 54 | 55 | def with_metaclass(meta, bases=(object,)): 56 | return meta("NewBase", bases, {}) 57 | 58 | 59 | 60 | _filename_ascii_strip_re = re.compile(r'[^A-Za-z0-9_.-]') 61 | 62 | def secure_filename(filename): 63 | r"""Pass it a filename and it will return a secure version of it. This 64 | filename can then safely be stored on a regular file system and passed 65 | to :func:`os.path.join`. The filename returned is an ASCII only string 66 | for maximum portability. 67 | 68 | On windows system the function also makes sure that the file is not 69 | named after one of the special device files. 70 | 71 | >>> secure_filename("My cool movie.mov") 72 | 'My_cool_movie.mov' 73 | >>> secure_filename("../../../etc/passwd") 74 | 'etc_passwd' 75 | >>> secure_filename(u'i contain cool \xfcml\xe4uts.txt') 76 | 'i_contain_cool_umlauts.txt' 77 | 78 | The function might return an empty filename. It's your responsibility 79 | to ensure that the filename is unique and that you generate random 80 | filename if the function returned an empty one. 81 | 82 | .. versionadded:: 0.5 83 | 84 | :param filename: the filename to secure 85 | """ 86 | if isinstance(filename, unicode_type): 87 | from unicodedata import normalize 88 | filename = normalize('NFKD', filename).encode('ascii', 'ignore') 89 | if not PY2: 90 | filename = filename.decode('ascii') 91 | for sep in os.path.sep, os.path.altsep: 92 | if sep: 93 | filename = filename.replace(sep, ' ') 94 | filename = str(_filename_ascii_strip_re.sub('', '_'.join( 95 | filename.split()))).strip('._') 96 | 97 | # on nt a couple of special files are present in each folder. We 98 | # have to ensure that the target file is not such a filename. In 99 | # this case we prepend an underline 100 | if os.name == 'nt' and filename and \ 101 | filename.split('.')[0].upper() in _windows_device_files: 102 | filename = '_' + filename 103 | 104 | return filename -------------------------------------------------------------------------------- /zephyrd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 zephyr 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | from zephyr.app import ZephyrService 19 | 20 | if __name__ == "__main__": 21 | 22 | ZephyrService().startup() 23 | --------------------------------------------------------------------------------