├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
10 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/zephyr/template/admin/layout/edit.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/zephyr/template/admin/layout/footer.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
11 |
12 |