Initial import
3
MANIFEST.in
Normal file
@ -0,0 +1,3 @@
|
||||
include scribeengine/config/deployment.ini_tmpl
|
||||
recursive-include scribeengine/public *
|
||||
recursive-include scribeengine/templates *
|
19
README.txt
Normal file
@ -0,0 +1,19 @@
|
||||
This file is for you to describe the ScribeEngine application. Typically
|
||||
you would include information such as the information below:
|
||||
|
||||
Installation and Setup
|
||||
======================
|
||||
|
||||
Install ``ScribeEngine`` using easy_install::
|
||||
|
||||
easy_install ScribeEngine
|
||||
|
||||
Make a config file as follows::
|
||||
|
||||
paster make-config ScribeEngine config.ini
|
||||
|
||||
Tweak the config file as appropriate and then setup the application::
|
||||
|
||||
paster setup-app config.ini
|
||||
|
||||
Then you are ready to go.
|
10
ScribeEngine.egg-info/PKG-INFO
Normal file
@ -0,0 +1,10 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: ScribeEngine
|
||||
Version: 0.1dev
|
||||
Summary: UNKNOWN
|
||||
Home-page: UNKNOWN
|
||||
Author: UNKNOWN
|
||||
Author-email: UNKNOWN
|
||||
License: UNKNOWN
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
34
ScribeEngine.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,34 @@
|
||||
MANIFEST.in
|
||||
README.txt
|
||||
setup.cfg
|
||||
setup.py
|
||||
ScribeEngine.egg-info/PKG-INFO
|
||||
ScribeEngine.egg-info/SOURCES.txt
|
||||
ScribeEngine.egg-info/dependency_links.txt
|
||||
ScribeEngine.egg-info/entry_points.txt
|
||||
ScribeEngine.egg-info/not-zip-safe
|
||||
ScribeEngine.egg-info/paster_plugins.txt
|
||||
ScribeEngine.egg-info/requires.txt
|
||||
ScribeEngine.egg-info/top_level.txt
|
||||
scribeengine/__init__.py
|
||||
scribeengine/websetup.py
|
||||
scribeengine/config/__init__.py
|
||||
scribeengine/config/deployment.ini_tmpl
|
||||
scribeengine/config/environment.py
|
||||
scribeengine/config/middleware.py
|
||||
scribeengine/config/routing.py
|
||||
scribeengine/controllers/__init__.py
|
||||
scribeengine/controllers/error.py
|
||||
scribeengine/lib/__init__.py
|
||||
scribeengine/lib/app_globals.py
|
||||
scribeengine/lib/base.py
|
||||
scribeengine/lib/helpers.py
|
||||
scribeengine/model/__init__.py
|
||||
scribeengine/model/meta.py
|
||||
scribeengine/public/bg.png
|
||||
scribeengine/public/favicon.ico
|
||||
scribeengine/public/index.html
|
||||
scribeengine/public/pylons-logo.gif
|
||||
scribeengine/tests/__init__.py
|
||||
scribeengine/tests/test_models.py
|
||||
scribeengine/tests/functional/__init__.py
|
1
ScribeEngine.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
||||
|
7
ScribeEngine.egg-info/entry_points.txt
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
[paste.app_factory]
|
||||
main = scribeengine.config.middleware:make_app
|
||||
|
||||
[paste.app_install]
|
||||
main = pylons.util:PylonsInstaller
|
||||
|
1
ScribeEngine.egg-info/not-zip-safe
Normal file
@ -0,0 +1 @@
|
||||
|
2
ScribeEngine.egg-info/paster_plugins.txt
Normal file
@ -0,0 +1,2 @@
|
||||
PasteScript
|
||||
Pylons
|
2
ScribeEngine.egg-info/requires.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Pylons>=0.9.7
|
||||
SQLAlchemy>=0.5
|
1
ScribeEngine.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
||||
scribeengine
|
97
development.ini
Normal file
@ -0,0 +1,97 @@
|
||||
#
|
||||
# ScribeEngine - Pylons development environment configuration
|
||||
#
|
||||
# The %(here)s variable will be replaced with the parent directory of this file
|
||||
#
|
||||
[DEFAULT]
|
||||
debug = true
|
||||
# Uncomment and replace with the address which should receive any error reports
|
||||
#email_to = you@yourdomain.com
|
||||
smtp_server = localhost
|
||||
error_email_from = paste@localhost
|
||||
|
||||
[server:main]
|
||||
use = egg:Paste#http
|
||||
host = 0.0.0.0
|
||||
port = 5000
|
||||
|
||||
[app:main]
|
||||
use = egg:ScribeEngine
|
||||
full_stack = true
|
||||
static_files = true
|
||||
|
||||
cache_dir = %(here)s/data
|
||||
beaker.session.key = scribeengine
|
||||
beaker.session.secret = somesecret
|
||||
|
||||
# If you'd like to fine-tune the individual locations of the cache data dirs
|
||||
# for the Cache data, or the Session saves, un-comment the desired settings
|
||||
# here:
|
||||
#beaker.cache.data_dir = %(here)s/data/cache
|
||||
#beaker.session.data_dir = %(here)s/data/sessions
|
||||
|
||||
# SQLAlchemy database URL
|
||||
sqlalchemy.url = sqlite:///%(here)s/scribeengine.sqlite
|
||||
|
||||
# Images directory
|
||||
paths.images = %(here)s/images
|
||||
# Themes directory
|
||||
paths.themes = %(here)s/themes
|
||||
|
||||
# Security settings
|
||||
security.salt = xgH,{@1pgtU9,nLd
|
||||
|
||||
mail.on = true
|
||||
mail.manager = immediate
|
||||
mail.smtp.server = mail.saturnlaboratories.co.za
|
||||
mail.smtp.username = raoul.snyman+saturnlaboratories.co.za
|
||||
mail.smtp.password = 0miG0sh89
|
||||
|
||||
# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
|
||||
# Debug mode will enable the interactive debugging tool, allowing ANYONE to
|
||||
# execute malicious code after an exception is raised.
|
||||
#set debug = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root, routes, scribeengine, sqlalchemy
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = INFO
|
||||
handlers = console
|
||||
|
||||
[logger_routes]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = routes.middleware
|
||||
# "level = DEBUG" logs the route matched and routing variables.
|
||||
|
||||
[logger_scribeengine]
|
||||
level = DEBUG
|
||||
handlers =
|
||||
qualname = scribeengine
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
# "level = INFO" logs SQL queries.
|
||||
# "level = DEBUG" logs SQL queries and results.
|
||||
# "level = WARN" logs neither. (Recommended for production systems.)
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
19
docs/index.txt
Normal file
@ -0,0 +1,19 @@
|
||||
scribeengine
|
||||
++++++++++++
|
||||
|
||||
This is the main index page of your documentation. It should be written in
|
||||
`reStructuredText format <http://docutils.sourceforge.net/rst.html>`_.
|
||||
|
||||
You can generate your documentation in HTML format by running this command::
|
||||
|
||||
setup.py pudge
|
||||
|
||||
For this to work you will need to download and install `buildutils`_,
|
||||
`pudge`_, and `pygments`_. The ``pudge`` command is disabled by
|
||||
default; to ativate it in your project, run::
|
||||
|
||||
setup.py addcommand -p buildutils.pudge_command
|
||||
|
||||
.. _buildutils: http://pypi.python.org/pypi/buildutils
|
||||
.. _pudge: http://pudge.lesscode.org/
|
||||
.. _pygments: http://pygments.org/
|
276
ez_setup.py
Normal file
@ -0,0 +1,276 @@
|
||||
#!python
|
||||
"""Bootstrap setuptools installation
|
||||
|
||||
If you want to use setuptools in your package's setup.py, just include this
|
||||
file in the same directory with it, and add this to the top of your setup.py::
|
||||
|
||||
from ez_setup import use_setuptools
|
||||
use_setuptools()
|
||||
|
||||
If you want to require a specific version of setuptools, set a download
|
||||
mirror, or use an alternate download directory, you can do so by supplying
|
||||
the appropriate options to ``use_setuptools()``.
|
||||
|
||||
This file can also be run as a script to install or upgrade setuptools.
|
||||
"""
|
||||
import sys
|
||||
DEFAULT_VERSION = "0.6c9"
|
||||
DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
|
||||
|
||||
md5_data = {
|
||||
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
|
||||
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
|
||||
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
|
||||
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
|
||||
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
|
||||
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
|
||||
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
|
||||
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
|
||||
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
|
||||
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
|
||||
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
|
||||
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
|
||||
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
|
||||
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
|
||||
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
|
||||
'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
|
||||
'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
|
||||
'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
|
||||
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
|
||||
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
|
||||
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
|
||||
'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
|
||||
'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
|
||||
'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
|
||||
'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
|
||||
'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
|
||||
'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
|
||||
'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
|
||||
'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
|
||||
'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
|
||||
'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
|
||||
'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
|
||||
'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
|
||||
'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
|
||||
}
|
||||
|
||||
import sys, os
|
||||
try: from hashlib import md5
|
||||
except ImportError: from md5 import md5
|
||||
|
||||
def _validate_md5(egg_name, data):
|
||||
if egg_name in md5_data:
|
||||
digest = md5(data).hexdigest()
|
||||
if digest != md5_data[egg_name]:
|
||||
print >>sys.stderr, (
|
||||
"md5 validation of %s failed! (Possible download problem?)"
|
||||
% egg_name
|
||||
)
|
||||
sys.exit(2)
|
||||
return data
|
||||
|
||||
def use_setuptools(
|
||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
||||
download_delay=15
|
||||
):
|
||||
"""Automatically find/download setuptools and make it available on sys.path
|
||||
|
||||
`version` should be a valid setuptools version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end with
|
||||
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
|
||||
it is not already available. If `download_delay` is specified, it should
|
||||
be the number of seconds that will be paused before initiating a download,
|
||||
should one be required. If an older version of setuptools is installed,
|
||||
this routine will print a message to ``sys.stderr`` and raise SystemExit in
|
||||
an attempt to abort the calling script.
|
||||
"""
|
||||
was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
|
||||
def do_download():
|
||||
egg = download_setuptools(version, download_base, to_dir, download_delay)
|
||||
sys.path.insert(0, egg)
|
||||
import setuptools; setuptools.bootstrap_install_from = egg
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
return do_download()
|
||||
try:
|
||||
pkg_resources.require("setuptools>="+version); return
|
||||
except pkg_resources.VersionConflict, e:
|
||||
if was_imported:
|
||||
print >>sys.stderr, (
|
||||
"The required version of setuptools (>=%s) is not available, and\n"
|
||||
"can't be installed while this script is running. Please install\n"
|
||||
" a more recent version first, using 'easy_install -U setuptools'."
|
||||
"\n\n(Currently using %r)"
|
||||
) % (version, e.args[0])
|
||||
sys.exit(2)
|
||||
else:
|
||||
del pkg_resources, sys.modules['pkg_resources'] # reload ok
|
||||
return do_download()
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return do_download()
|
||||
|
||||
def download_setuptools(
|
||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
||||
delay = 15
|
||||
):
|
||||
"""Download setuptools from a specified location and return its filename
|
||||
|
||||
`version` should be a valid setuptools version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end
|
||||
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
||||
`delay` is the number of seconds to pause before an actual download attempt.
|
||||
"""
|
||||
import urllib2, shutil
|
||||
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
|
||||
url = download_base + egg_name
|
||||
saveto = os.path.join(to_dir, egg_name)
|
||||
src = dst = None
|
||||
if not os.path.exists(saveto): # Avoid repeated downloads
|
||||
try:
|
||||
from distutils import log
|
||||
if delay:
|
||||
log.warn("""
|
||||
---------------------------------------------------------------------------
|
||||
This script requires setuptools version %s to run (even to display
|
||||
help). I will attempt to download it for you (from
|
||||
%s), but
|
||||
you may need to enable firewall access for this script first.
|
||||
I will start the download in %d seconds.
|
||||
|
||||
(Note: if this machine does not have network access, please obtain the file
|
||||
|
||||
%s
|
||||
|
||||
and place it in this directory before rerunning this script.)
|
||||
---------------------------------------------------------------------------""",
|
||||
version, download_base, delay, url
|
||||
); from time import sleep; sleep(delay)
|
||||
log.warn("Downloading %s", url)
|
||||
src = urllib2.urlopen(url)
|
||||
# Read/write all in one block, so we don't create a corrupt file
|
||||
# if the download is interrupted.
|
||||
data = _validate_md5(egg_name, src.read())
|
||||
dst = open(saveto,"wb"); dst.write(data)
|
||||
finally:
|
||||
if src: src.close()
|
||||
if dst: dst.close()
|
||||
return os.path.realpath(saveto)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def main(argv, version=DEFAULT_VERSION):
|
||||
"""Install or upgrade setuptools and EasyInstall"""
|
||||
try:
|
||||
import setuptools
|
||||
except ImportError:
|
||||
egg = None
|
||||
try:
|
||||
egg = download_setuptools(version, delay=0)
|
||||
sys.path.insert(0,egg)
|
||||
from setuptools.command.easy_install import main
|
||||
return main(list(argv)+[egg]) # we're done here
|
||||
finally:
|
||||
if egg and os.path.exists(egg):
|
||||
os.unlink(egg)
|
||||
else:
|
||||
if setuptools.__version__ == '0.0.1':
|
||||
print >>sys.stderr, (
|
||||
"You have an obsolete version of setuptools installed. Please\n"
|
||||
"remove it from your system entirely before rerunning this script."
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
req = "setuptools>="+version
|
||||
import pkg_resources
|
||||
try:
|
||||
pkg_resources.require(req)
|
||||
except pkg_resources.VersionConflict:
|
||||
try:
|
||||
from setuptools.command.easy_install import main
|
||||
except ImportError:
|
||||
from easy_install import main
|
||||
main(list(argv)+[download_setuptools(delay=0)])
|
||||
sys.exit(0) # try to force an exit
|
||||
else:
|
||||
if argv:
|
||||
from setuptools.command.easy_install import main
|
||||
main(argv)
|
||||
else:
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
|
||||
|
||||
def update_md5(filenames):
|
||||
"""Update our built-in md5 registry"""
|
||||
|
||||
import re
|
||||
|
||||
for name in filenames:
|
||||
base = os.path.basename(name)
|
||||
f = open(name,'rb')
|
||||
md5_data[base] = md5(f.read()).hexdigest()
|
||||
f.close()
|
||||
|
||||
data = [" %r: %r,\n" % it for it in md5_data.items()]
|
||||
data.sort()
|
||||
repl = "".join(data)
|
||||
|
||||
import inspect
|
||||
srcfile = inspect.getsourcefile(sys.modules[__name__])
|
||||
f = open(srcfile, 'rb'); src = f.read(); f.close()
|
||||
|
||||
match = re.search("\nmd5_data = {\n([^}]+)}", src)
|
||||
if not match:
|
||||
print >>sys.stderr, "Internal error!"
|
||||
sys.exit(2)
|
||||
|
||||
src = src[:match.start(1)] + repl + src[match.end(1):]
|
||||
f = open(srcfile,'w')
|
||||
f.write(src)
|
||||
f.close()
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
|
||||
update_md5(sys.argv[2:])
|
||||
else:
|
||||
main(sys.argv[1:])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
0
scribeengine/__init__.py
Normal file
0
scribeengine/config/__init__.py
Normal file
63
scribeengine/config/deployment.ini_tmpl
Normal file
@ -0,0 +1,63 @@
|
||||
#
|
||||
# ScribeEngine - Pylons configuration
|
||||
#
|
||||
# The %(here)s variable will be replaced with the parent directory of this file
|
||||
#
|
||||
[DEFAULT]
|
||||
debug = true
|
||||
email_to = you@yourdomain.com
|
||||
smtp_server = localhost
|
||||
error_email_from = paste@localhost
|
||||
|
||||
[server:main]
|
||||
use = egg:Paste#http
|
||||
host = 0.0.0.0
|
||||
port = 5000
|
||||
|
||||
[app:main]
|
||||
use = egg:ScribeEngine
|
||||
full_stack = true
|
||||
static_files = true
|
||||
|
||||
cache_dir = %(here)s/data
|
||||
beaker.session.key = scribeengine
|
||||
beaker.session.secret = ${app_instance_secret}
|
||||
app_instance_uuid = ${app_instance_uuid}
|
||||
|
||||
# If you'd like to fine-tune the individual locations of the cache data dirs
|
||||
# for the Cache data, or the Session saves, un-comment the desired settings
|
||||
# here:
|
||||
#beaker.cache.data_dir = %(here)s/data/cache
|
||||
#beaker.session.data_dir = %(here)s/data/sessions
|
||||
|
||||
# SQLAlchemy database URL
|
||||
sqlalchemy.url = sqlite:///production.db
|
||||
|
||||
# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
|
||||
# Debug mode will enable the interactive debugging tool, allowing ANYONE to
|
||||
# execute malicious code after an exception is raised.
|
||||
set debug = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = INFO
|
||||
handlers = console
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
|
55
scribeengine/config/environment.py
Normal file
@ -0,0 +1,55 @@
|
||||
"""Pylons environment configuration"""
|
||||
import os
|
||||
|
||||
from mako.lookup import TemplateLookup
|
||||
from pylons import config
|
||||
from pylons.error import handle_mako_error
|
||||
from sqlalchemy import engine_from_config
|
||||
|
||||
from scribeengine.lib import app_globals
|
||||
from scribeengine.lib import helpers
|
||||
from scribeengine.config.routing import make_map
|
||||
from scribeengine.model import init_model, Variable
|
||||
from scribeengine.model.meta import Session
|
||||
|
||||
def load_environment(global_conf, app_conf):
|
||||
"""Configure the Pylons environment via the ``pylons.config``
|
||||
object
|
||||
"""
|
||||
# Setup the SQLAlchemy database engine
|
||||
engine = engine_from_config(app_conf, 'sqlalchemy.')
|
||||
init_model(engine)
|
||||
|
||||
# Pull out theme variable
|
||||
theme = Session.query(Variable).get(u'theme')
|
||||
if not theme:
|
||||
theme_name = u'stargazer'
|
||||
else:
|
||||
theme_name = theme.value
|
||||
|
||||
# Pylons paths
|
||||
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
theme_dir = os.path.join(app_conf[u'paths.themes'], theme_name)
|
||||
paths = dict(root=root,
|
||||
controllers=os.path.join(root, 'controllers'),
|
||||
static_files=os.path.join(theme_dir, 'public'),
|
||||
templates=[os.path.join(theme_dir, 'templates'),
|
||||
os.path.join(root, 'templates')])
|
||||
|
||||
# Initialize config with the basic options
|
||||
config.init_app(global_conf, app_conf, package='scribeengine', paths=paths)
|
||||
|
||||
config['routes.map'] = make_map()
|
||||
config['pylons.app_globals'] = app_globals.Globals()
|
||||
config['pylons.h'] = helpers
|
||||
|
||||
# Create the Mako TemplateLookup, with the default auto-escaping
|
||||
config['pylons.app_globals'].mako_lookup = TemplateLookup(
|
||||
directories=paths['templates'],
|
||||
error_handler=handle_mako_error,
|
||||
module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
|
||||
input_encoding='utf-8', default_filters=['escape'],
|
||||
imports=['from webhelpers.html import escape'])
|
||||
|
||||
# CONFIGURATION OPTIONS HERE (note: all config options will override
|
||||
# any Pylons config options)
|
69
scribeengine/config/middleware.py
Normal file
@ -0,0 +1,69 @@
|
||||
"""Pylons middleware initialization"""
|
||||
from beaker.middleware import CacheMiddleware, SessionMiddleware
|
||||
from paste.cascade import Cascade
|
||||
from paste.registry import RegistryManager
|
||||
from paste.urlparser import StaticURLParser
|
||||
from paste.deploy.converters import asbool
|
||||
from pylons import config
|
||||
from pylons.middleware import ErrorHandler, StatusCodeRedirect
|
||||
from pylons.wsgiapp import PylonsApp
|
||||
from routes.middleware import RoutesMiddleware
|
||||
|
||||
from scribeengine.config.environment import load_environment
|
||||
|
||||
def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
|
||||
"""Create a Pylons WSGI application and return it
|
||||
|
||||
``global_conf``
|
||||
The inherited configuration for this application. Normally from
|
||||
the [DEFAULT] section of the Paste ini file.
|
||||
|
||||
``full_stack``
|
||||
Whether this application provides a full WSGI stack (by default,
|
||||
meaning it handles its own exceptions and errors). Disable
|
||||
full_stack when this application is "managed" by another WSGI
|
||||
middleware.
|
||||
|
||||
``static_files``
|
||||
Whether this application serves its own static files; disable
|
||||
when another web server is responsible for serving them.
|
||||
|
||||
``app_conf``
|
||||
The application's local configuration. Normally specified in
|
||||
the [app:<name>] section of the Paste ini file (where <name>
|
||||
defaults to main).
|
||||
|
||||
"""
|
||||
# Configure the Pylons environment
|
||||
load_environment(global_conf, app_conf)
|
||||
|
||||
# The Pylons WSGI app
|
||||
app = PylonsApp()
|
||||
|
||||
# Routing/Session/Cache Middleware
|
||||
app = RoutesMiddleware(app, config['routes.map'])
|
||||
app = SessionMiddleware(app, config)
|
||||
app = CacheMiddleware(app, config)
|
||||
|
||||
# CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
|
||||
|
||||
if asbool(full_stack):
|
||||
# Handle Python exceptions
|
||||
app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
|
||||
|
||||
# Display error documents for 401, 403, 404 status codes (and
|
||||
# 500 when debug is disabled)
|
||||
if asbool(config['debug']):
|
||||
app = StatusCodeRedirect(app)
|
||||
else:
|
||||
app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
|
||||
|
||||
# Establish the Registry for this application
|
||||
app = RegistryManager(app)
|
||||
|
||||
if asbool(static_files):
|
||||
# Serve static files
|
||||
static_app = StaticURLParser(config['pylons.paths']['static_files'])
|
||||
app = Cascade([static_app, app])
|
||||
|
||||
return app
|
56
scribeengine/config/routing.py
Normal file
@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
Routes configuration
|
||||
|
||||
The more specific and detailed routes should be defined first so they
|
||||
may take precedent over the more generic routes. For more information
|
||||
refer to the routes manual at http://routes.groovie.org/docs/
|
||||
"""
|
||||
from pylons import config
|
||||
from routes import Mapper
|
||||
|
||||
def make_map():
|
||||
"""Create, configure and return the routes Mapper"""
|
||||
map = Mapper(directory=config['pylons.paths']['controllers'],
|
||||
always_scan=config['debug'])
|
||||
map.minimization = False
|
||||
|
||||
# The ErrorController route (handles 404/500 error pages); it should
|
||||
# likely stay at the top, ensuring it can always be resolved
|
||||
map.connect('/error/{action}', controller='error')
|
||||
map.connect('/error/{action}/{id}', controller='error')
|
||||
|
||||
# CUSTOM ROUTES HERE
|
||||
|
||||
map.connect('/archive/{year}', controller='blog', action='archive')
|
||||
map.connect('/archive/{year}/{month}', controller='blog', action='archive')
|
||||
map.connect('/archive/{year}/{month}/{day}', controller='blog', action='archive')
|
||||
map.connect('/archive/{year}/{month}/{day}/{url}', controller='blog', action='view')
|
||||
|
||||
map.connect('/{controller}/{action}')
|
||||
map.connect('/{controller}/{action}/{id}')
|
||||
|
||||
map.connect('/', controller='blog', action='index')
|
||||
|
||||
return map
|
0
scribeengine/controllers/__init__.py
Normal file
118
scribeengine/controllers/admin.py
Normal file
@ -0,0 +1,118 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import string
|
||||
import random
|
||||
|
||||
from scribeengine.lib.base import *
|
||||
from scribeengine.lib.validation.client import JSString, JSEmail
|
||||
from scribeengine.lib.validation.server import UnicodeString, Email, FieldsMatch
|
||||
from scribeengine.lib import utils
|
||||
from scribeengine.model import User
|
||||
from scribeengine.model.meta import Session
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class AdminController(BaseController):
|
||||
|
||||
def index(self):
|
||||
h.redirect_to('/admin/login')
|
||||
|
||||
def register(self):
|
||||
c.page_title = u'Register'
|
||||
return render(u'/admin/register.mako')
|
||||
|
||||
@jsvalidate(u'register-form')
|
||||
def register_jsschema(self):
|
||||
return {
|
||||
u'email': JSEmail(required=True, message=u'You haven\'t typed in an e-mail address.'),
|
||||
u'password': JSString(required=True, message=u'You haven\'t typed in a password.'),
|
||||
u'confirm-password': JSString(required=True, equalTo=u'password', message=u'Your passwords don\'t match.')
|
||||
}
|
||||
|
||||
def register_schema(self):
|
||||
return {
|
||||
'email': Email(not_empty=True, messages={'empty': u'You haven\'t typed in an e-mail address.'}),
|
||||
'password': UnicodeString(not_empty=True, messages={'empty': u'You haven\'t typed in a password.'}),
|
||||
'confirm': [FieldsMatch('password', 'confirm-passsword', messages={'invalid': u'Your passwords don\'t match.'})]
|
||||
}
|
||||
|
||||
def register_POST(self):
|
||||
activation_code = u''.join(random.sample(string.letters + string.digits, 40))
|
||||
user = User(
|
||||
nick=c.form_values[u'nick'],
|
||||
email=c.form_values[u'email'],
|
||||
password=utils.hash_password(c.form_values[u'password']),
|
||||
activation_key=activation_code
|
||||
)
|
||||
Session.add(user)
|
||||
Session.commit()
|
||||
h.redirect_to('/')
|
||||
|
||||
def login(self):
|
||||
c.page_title = u'Login'
|
||||
return render(u'/admin/login.mako')
|
||||
|
||||
@jsvalidate(u'login-form')
|
||||
def login_jsschema(self):
|
||||
return {
|
||||
u'email': JSEmail(required=True, message=u'You haven\'t typed in an e-mail address.'),
|
||||
u'password': JSString(required=True, message=u'You haven\'t typed in a password.')
|
||||
}
|
||||
|
||||
def login_schema(self):
|
||||
return {
|
||||
'email': Email(not_empty=True, messages={'empty': u'You haven\'t typed in an e-mail address.'}),
|
||||
'password': UnicodeString(not_empty=True, messages={'empty': u'You haven\'t typed in a password.'})
|
||||
}
|
||||
|
||||
def login_POST(self):
|
||||
log.debug('Logging in as "%s" with password "%s"', c.form_values[u'email'], c.form_values[u'password'])
|
||||
user = Session.query(User).filter_by(email=c.form_values[u'email']).first()
|
||||
password = utils.hash_password(c.form_values[u'password'])
|
||||
log.debug(user)
|
||||
if not user or user.password != password:
|
||||
log.debug('Username or password are incorrect.')
|
||||
h.flash.set_message(u'Your username or password are incorrect.', u'error')
|
||||
h.redirect_to('/login')
|
||||
elif user and user.password == password:
|
||||
log.debug('Logged in successfully.')
|
||||
redirect_url = str(session.get(u'redirect_url', u'/'))
|
||||
session[u'REMOTE_USER'] = user.id
|
||||
if u'redirect_url' in session:
|
||||
del session[u'redirect_url']
|
||||
session.save()
|
||||
h.flash.set_message(u'You have logged in successfully.', u'success')
|
||||
h.redirect_to(redirect_url)
|
||||
else:
|
||||
log.debug('"user" is None.')
|
||||
del session[u'REMOTE_USER']
|
||||
session.save()
|
||||
h.flash.set_message(u'There was a problem logging you in.', u'error')
|
||||
h.redirect_to('/login')
|
||||
|
||||
def logout(self):
|
||||
del session[u'REMOTE_USER']
|
||||
session.save()
|
||||
h.redirect_to('/')
|
||||
|
72
scribeengine/controllers/blog.py
Normal file
@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from scribeengine.lib.base import *
|
||||
from scribeengine.lib import utils
|
||||
from scribeengine.model import Post
|
||||
from scribeengine.model.meta import Session
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class BlogController(BaseController):
|
||||
|
||||
def index(self):
|
||||
c.posts = Session.query(Post)\
|
||||
.filter_by(status=u'published')\
|
||||
.order_by(Post.created.desc())\
|
||||
.all()
|
||||
return render(u'/blog/index.mako')
|
||||
|
||||
def archive(self, year=None, month=None, day=None):
|
||||
if day and month and year:
|
||||
start_date = datetime(int(year), int(month), int(day), 0, 0, 0, 0)
|
||||
end_date = datetime(int(year), int(month), int(day), 23, 59, 59, 99999)
|
||||
c.page_title = u'Archive: %s' % start_date.strftime('%d %B %Y')
|
||||
elif month and year and not day:
|
||||
start_date = utils.month_first_day(datetime(int(year), int(month), 1))
|
||||
end_date = utils.month_last_day(datetime(int(year), int(month), 1))
|
||||
c.page_title = u'Archive: %s' % start_date.strftime('%B %Y')
|
||||
elif year and not month:
|
||||
start_date = datetime(int(year), 1, 1, 0, 0, 0, 0)
|
||||
end_date = datetime(int(year), 12, 31, 23, 59, 59, 99999)
|
||||
c.page_title = u'Archive: %s' % start_date.strftime('%Y')
|
||||
else:
|
||||
start_date = None
|
||||
end_date = None
|
||||
c.posts = Session.query(Post)
|
||||
if start_date and end_date:
|
||||
c.posts = c.posts\
|
||||
.filter(Post.created >= start_date)\
|
||||
.filter(Post.created <= end_date)
|
||||
c.posts = c.posts.order_by(Post.created.desc()).all()
|
||||
return render(u'/blog/archive.mako')
|
||||
|
||||
def view(self, url):
|
||||
c.post = Session.query(Post)\
|
||||
.filter_by(url=url)\
|
||||
.filter_by(status=u'published')\
|
||||
.first()
|
||||
c.page_title = c.post.title
|
||||
return render(u'/blog/view.mako')
|
46
scribeengine/controllers/error.py
Normal file
@ -0,0 +1,46 @@
|
||||
import cgi
|
||||
|
||||
from paste.urlparser import PkgResourcesParser
|
||||
from pylons import request
|
||||
from pylons.controllers.util import forward
|
||||
from pylons.middleware import error_document_template
|
||||
from webhelpers.html.builder import literal
|
||||
|
||||
from scribeengine.lib.base import BaseController
|
||||
|
||||
class ErrorController(BaseController):
|
||||
|
||||
"""Generates error documents as and when they are required.
|
||||
|
||||
The ErrorDocuments middleware forwards to ErrorController when error
|
||||
related status codes are returned from the application.
|
||||
|
||||
This behaviour can be altered by changing the parameters to the
|
||||
ErrorDocuments middleware in your config/middleware.py file.
|
||||
|
||||
"""
|
||||
|
||||
def document(self):
|
||||
"""Render the error document"""
|
||||
resp = request.environ.get('pylons.original_response')
|
||||
content = literal(resp.body) or cgi.escape(request.GET.get('message', ''))
|
||||
page = error_document_template % \
|
||||
dict(prefix=request.environ.get('SCRIPT_NAME', ''),
|
||||
code=cgi.escape(request.GET.get('code', str(resp.status_int))),
|
||||
message=content)
|
||||
return page
|
||||
|
||||
def img(self, id):
|
||||
"""Serve Pylons' stock images"""
|
||||
return self._serve_file('/'.join(['media/img', id]))
|
||||
|
||||
def style(self, id):
|
||||
"""Serve Pylons' stock stylesheets"""
|
||||
return self._serve_file('/'.join(['media/style', id]))
|
||||
|
||||
def _serve_file(self, path):
|
||||
"""Call Paste's FileApp (a WSGI application) to serve the file
|
||||
at the specified path
|
||||
"""
|
||||
request.environ['PATH_INFO'] = '/%s' % path
|
||||
return forward(PkgResourcesParser('pylons', 'pylons'))
|
63
scribeengine/controllers/post.py
Normal file
@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from scribeengine.lib.base import *
|
||||
from scribeengine.lib import utils
|
||||
from scribeengine.model import Post
|
||||
from scribeengine.model.meta import Session
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class PostController(BaseController):
|
||||
|
||||
def index(self):
|
||||
h.redirect_to('/')
|
||||
|
||||
@authenticate(u'Add Posts')
|
||||
def new(self):
|
||||
c.page_title = 'New Post'
|
||||
return render(u'/post/new.mako')
|
||||
|
||||
@authenticate(u'Edit My Posts')
|
||||
def edit(self, id=None):
|
||||
c.page_title = 'New Post'
|
||||
return render(u'/post/edit.mako')
|
||||
|
||||
def edit_POST(self, id=None):
|
||||
url = utils.generate_url(c.form_values[u'title'])
|
||||
if id is None:
|
||||
post = Post()
|
||||
post.user = c.current_user
|
||||
else:
|
||||
post = Session.query(Post).get(id)
|
||||
post.modified = datetime.now()
|
||||
post.title = c.form_values[u'title']
|
||||
post.body = c.form_values[u'body']
|
||||
post.status = u'published'
|
||||
post.url = url
|
||||
Session.add(post)
|
||||
Session.commit()
|
||||
h.redirect_to(str('/archive/%s/%s' % (post.created.strftime('%Y/%m/%d'), post.url)))
|
||||
|
0
scribeengine/lib/__init__.py
Normal file
40
scribeengine/lib/app_globals.py
Normal file
@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""The application's Globals object"""
|
||||
|
||||
class Globals(object):
|
||||
|
||||
"""Globals acts as a container for objects available throughout the
|
||||
life of the application
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""One instance of Globals is created during application
|
||||
initialization and is available during requests via the
|
||||
'app_globals' variable
|
||||
|
||||
"""
|
||||
from turbomail.adapters import tm_pylons
|
||||
tm_pylons.start_extension()
|
||||
|
240
scribeengine/lib/base.py
Normal file
@ -0,0 +1,240 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
The base Controller API
|
||||
|
||||
Provides the BaseController class for subclassing.
|
||||
"""
|
||||
|
||||
from calendar import Calendar
|
||||
from datetime import datetime
|
||||
from decorator import decorator
|
||||
import logging
|
||||
|
||||
from paste.request import construct_url
|
||||
from webob.exc import HTTPMovedPermanently
|
||||
from pylons import c, request, session, response, config
|
||||
from pylons.controllers import WSGIController
|
||||
from pylons.templating import render_mako
|
||||
from sqlalchemy.sql.expression import asc, desc
|
||||
from formencode import Schema, Invalid
|
||||
|
||||
from scribeengine.lib import helpers as h
|
||||
from scribeengine.lib.validation import jsvalidate
|
||||
from scribeengine.model.meta import Session
|
||||
from scribeengine.model import Variable, User, Category, Page
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class BaseController(WSGIController):
|
||||
|
||||
def __before__(self):
|
||||
#c.theme_name = Session.query(Configuration).get(u'theme').value
|
||||
if session.get(u'REMOTE_USER'):
|
||||
c.current_user = Session.query(User).get(session[u'REMOTE_USER'])
|
||||
c.blog_title = Session.query(Variable).get(u'blog title').value
|
||||
c.blog_slogan = Session.query(Variable).get(u'blog slogan').value
|
||||
c.categories = Session.query(Category).order_by(Category.name.asc()).all()
|
||||
c.pages = Session.query(Page).all()
|
||||
c.calendar = Calendar(6)
|
||||
c.today = datetime.today()
|
||||
if not c.thismonth:
|
||||
c.thismonth = datetime.now()
|
||||
self._add_javascript(u'jquery.js')
|
||||
if c.jsvalidation:
|
||||
self._add_javascript(u'jquery.validate.js')
|
||||
self._add_javascript(u'scribeengine.js')
|
||||
self._add_jsinit(u'init.js')
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Invoke the Controller"""
|
||||
# WSGIController.__call__ dispatches to the Controller method
|
||||
# the request is routed to. This routing information is
|
||||
# available in environ['pylons.routes_dict']
|
||||
try:
|
||||
# If there's a jsschema function, create a template context variable
|
||||
# that will be used to call in the JavaScript validation.
|
||||
route = u'/%s/%s' % (environ[u'pylons.routes_dict'][u'controller'],
|
||||
environ[u'pylons.routes_dict'][u'action'])
|
||||
action = environ[u'pylons.routes_dict'][u'action']
|
||||
post = u'%s_POST' % action
|
||||
jsschema = u'%s_jsschema' % action
|
||||
schema = u'%s_schema' % action
|
||||
if getattr(self, jsschema, None) is not None:
|
||||
c.jsvalidation = route + u'_jsschema'
|
||||
if environ[u'REQUEST_METHOD'].upper() == u'POST':
|
||||
# Set up an initial, empty, set of form values.
|
||||
c.form_values = {}
|
||||
# Do validation according to schema here, even bfore it hits the <method>_POST
|
||||
validators = getattr(self, schema, None)
|
||||
if validators:
|
||||
schema = self._make_schema(validators)
|
||||
unvalidated_fields = {}
|
||||
for key in schema.fields.keys():
|
||||
if key in request.POST:
|
||||
value = request.POST.getall(key)
|
||||
if len(value) == 1:
|
||||
unvalidated_fields[key] = value[0]
|
||||
elif len(value) == 0:
|
||||
unvalidated_fields[key] = None
|
||||
else:
|
||||
unvalidated_fields[key] = value
|
||||
success, results = self._validate(schema, unvalidated_fields)
|
||||
# Any error messages plus the form values are put into the
|
||||
# c variable so that it is available to the called method.
|
||||
c.form_errors = results[u'errors']
|
||||
c.form_values = results[u'values']
|
||||
if not success:
|
||||
# If validation failed, go back to the original method.
|
||||
return WSGIController.__call__(self, environ, start_response)
|
||||
# Run through all of the POST variables, and make sure that
|
||||
# we stick any remaining variables into c.form_values
|
||||
for key in request.POST.keys():
|
||||
if key not in c.form_values:
|
||||
value = request.POST.getall(key)
|
||||
if len(value) == 1:
|
||||
c.form_values[key] = value[0]
|
||||
elif len(value) == 0:
|
||||
c.form_values[key] = None
|
||||
else:
|
||||
c.form_values[key] = value
|
||||
# So, at this stage, we've done all is necessary, but this is
|
||||
# a POST, so send us on to a POST method if it exists.
|
||||
if getattr(self, post, None):
|
||||
environ[u'pylons.routes_dict'][u'action'] = post
|
||||
return WSGIController.__call__(self, environ, start_response)
|
||||
finally:
|
||||
Session.remove()
|
||||
|
||||
def _make_schema(self, validators_function):
|
||||
"""
|
||||
_make_schema is a method which is used to automatically convert a
|
||||
dictionary of FormEncode validators into a schema.
|
||||
"""
|
||||
validators = validators_function()
|
||||
return Schema(**validators)
|
||||
|
||||
def _validate(self, schema, params):
|
||||
"""
|
||||
Validate the (usually POST) request parameters against a FormEncode
|
||||
schema.
|
||||
|
||||
Returns either:
|
||||
|
||||
`(True, values)`
|
||||
Validation passed, and the values returned are converted (ie, of
|
||||
the correct type and normalised - leading and/or trailing spaces
|
||||
removed, etc.)
|
||||
|
||||
`(False, errors)`
|
||||
Validation failed, and the errors returned are a dictionary:
|
||||
|
||||
`values`
|
||||
The values as given in the request
|
||||
|
||||
`errors`
|
||||
The validation errors (as many as can be given)
|
||||
"""
|
||||
if callable(schema):
|
||||
validator = schema()
|
||||
else:
|
||||
validator = schema
|
||||
# Remove 'submit', so that the schema doesn't need to contain it
|
||||
if u'submit' in params:
|
||||
del params[u'submit']
|
||||
try:
|
||||
values = validator.to_python(params)
|
||||
except Invalid, e:
|
||||
if e.error_dict:
|
||||
# Standard case - each item in the schema that fails is added
|
||||
# to error_dict with the key being the item name and the value
|
||||
# a FormEncode error object.
|
||||
error_dict = e.error_dict.copy()
|
||||
else:
|
||||
# On full-form validation failure (for example, sending additional
|
||||
# data), the form isn't validated, and thus there is no error_dict
|
||||
error_dict = {}
|
||||
error_dict[None] = e.unpack_errors()
|
||||
return False, {u'values': params, u'errors': error_dict}
|
||||
return True, {u'values': values, u'errors': {}}
|
||||
|
||||
def _add_javascript(self, filename, subdir=u'global'):
|
||||
"""
|
||||
This method dynamically adds javascript files to the <head> section
|
||||
of the template.
|
||||
"""
|
||||
if not getattr(c, u'scripts', None):
|
||||
c.scripts = []
|
||||
c.scripts.append((filename, subdir))
|
||||
|
||||
def _add_jsinit(self, filename):
|
||||
"""
|
||||
This method dynamically includes a special javascript initialisation
|
||||
file to the <head> section of the template.
|
||||
"""
|
||||
c.jsinit = filename
|
||||
|
||||
def _add_stylesheet(self, filename, subdir=None):
|
||||
"""
|
||||
This method dynamically adds javascript files to the <head> section
|
||||
of the template.
|
||||
"""
|
||||
if not getattr(c, u'styles', None):
|
||||
c.styles = []
|
||||
if subdir is None:
|
||||
subdir = c.theme_name
|
||||
c.styles.append(filename, subdir)
|
||||
|
||||
|
||||
def authenticate(permission=None):
|
||||
"""
|
||||
A decorator used to check the access level of a member against a permission.
|
||||
"""
|
||||
def validate(func, self, *args, **kwargs):
|
||||
if session.get(u'REMOTE_USER'):
|
||||
user = Session.query(User).get(session[u'REMOTE_USER'])
|
||||
if user:
|
||||
if permission and not user.has_permission(permission):
|
||||
h.flash.set_message(
|
||||
u'You don\'t have access to that area.', u'error')
|
||||
h.redirect_to('/')
|
||||
return func(self, *args, **kwargs)
|
||||
else:
|
||||
h.flash.set_message(
|
||||
u'You don\'t have access to that area.', u'error')
|
||||
h.redirect_to('/')
|
||||
else:
|
||||
session[u'redirect_url'] = request.environ[u'PATH_INFO']
|
||||
session.save()
|
||||
h.flash.set_message(u'You need to be logged in to do that.', u'error')
|
||||
h.redirect_to('/login')
|
||||
return decorator(validate)
|
||||
|
||||
|
||||
def render(template):
|
||||
if request.environ['PATH_INFO'] == '/':
|
||||
c.page_title = u'%s | %s ' % (c.blog_title, c.blog_slogan)
|
||||
else:
|
||||
c.page_title = u'%s | %s ' % (c.page_title, c.blog_title)
|
||||
return render_mako(template)
|
||||
|
84
scribeengine/lib/helpers.py
Normal file
@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
Helper functions
|
||||
|
||||
Consists of functions to typically be used within templates, but also
|
||||
available to Controllers. This module is available to both as 'h'.
|
||||
"""
|
||||
|
||||
from routes import url_for
|
||||
from webhelpers.html import escape, HTML, literal, url_escape
|
||||
from webhelpers.date import distance_of_time_in_words
|
||||
from pylons.controllers.util import redirect_to
|
||||
|
||||
class Flash(object):
|
||||
def set_message(self, message_text, message_type):
|
||||
session = self._get_session()
|
||||
session[u'flash.text'] = message_text
|
||||
session[u'flash.type'] = message_type
|
||||
session.save()
|
||||
|
||||
def has_message(self):
|
||||
session = self._get_session()
|
||||
return u'flash.text' in session
|
||||
|
||||
def get_message_text(self):
|
||||
session = self._get_session()
|
||||
message_text = session.pop(u'flash.text', None)
|
||||
if not message_text:
|
||||
return None
|
||||
session.save()
|
||||
return message_text
|
||||
|
||||
def get_message_type(self):
|
||||
session = self._get_session()
|
||||
message_type = session.pop(u'flash.type', None)
|
||||
if not message_type:
|
||||
return None
|
||||
session.save()
|
||||
return message_type
|
||||
|
||||
def _get_session(self):
|
||||
from pylons import session
|
||||
return session
|
||||
|
||||
|
||||
def teaser(text, url):
|
||||
position = text.find(u'</p>')
|
||||
if position > 0:
|
||||
return text[:position]
|
||||
elif len(text) > 300:
|
||||
text = text[:297]
|
||||
position = len(text) - 1
|
||||
while position > 0 and text[position] not in [u'<', u'>']:
|
||||
position -= 1
|
||||
if position != 0 and text[position + 1] == u'/':
|
||||
position -= 1
|
||||
while position > 0 and text[position] not in [u'<']:
|
||||
position -= 1
|
||||
if position != 0 and text[position] == u'<':
|
||||
text = text[:position]
|
||||
return text
|
||||
|
||||
flash = Flash()
|
113
scribeengine/lib/utils.py
Normal file
@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import re
|
||||
import hashlib
|
||||
import hmac
|
||||
import string
|
||||
from random import choice
|
||||
from datetime import datetime
|
||||
|
||||
from pylons import config
|
||||
from turbomail import Message
|
||||
|
||||
from scribeengine.lib.base import render, c
|
||||
|
||||
def send_mail(template, mail_to, mail_from, subject, variables={}, attachments=[]):
|
||||
"""
|
||||
Sends an e-mail using the template ``template``.
|
||||
|
||||
``template``
|
||||
The template to use.
|
||||
|
||||
``mail_to``
|
||||
One or more addresses to send the e-mail to.
|
||||
|
||||
``mail_from``
|
||||
The address to send e-mail from.
|
||||
|
||||
``subject``
|
||||
The subject of the e-mail.
|
||||
|
||||
``variables``
|
||||
Variables to be used in the template.
|
||||
|
||||
``attachments``
|
||||
If you want to attach files to the e-mail, use this list.
|
||||
"""
|
||||
for name, value in variables.iteritems():
|
||||
setattr(c, name, value)
|
||||
message = Message(mail_from, mail_to, subject)
|
||||
message.plain = render(template)
|
||||
message.send()
|
||||
|
||||
def generate_url(title):
|
||||
"""
|
||||
Generate a friendly URL from a blog post title.
|
||||
|
||||
``title``
|
||||
The title of the blog post.
|
||||
"""
|
||||
return re.sub(r'[^a-zA-Z0-9]+', u'-', title.lower())
|
||||
|
||||
def hash_password(password):
|
||||
"""
|
||||
Return an HMAC SHA256 hash of a password.
|
||||
|
||||
``password``
|
||||
The password to hash.
|
||||
"""
|
||||
return unicode(hmac.new(config[u'security.salt'], password,
|
||||
hashlib.sha256).hexdigest(), 'utf-8')
|
||||
|
||||
def generate_key(length):
|
||||
"""
|
||||
Generate a random set of letters and numbers of length ``length``. Usually
|
||||
used to generate activation keys.
|
||||
|
||||
``length``
|
||||
The length of the key.
|
||||
"""
|
||||
return ''.join([choice(string.letters + string.digits) for i in range(length)])
|
||||
|
||||
def month_first_day(datetime):
|
||||
"""
|
||||
Returns a modified datetime with the day being midnight of the first day of
|
||||
the month, given a datetime object.
|
||||
"""
|
||||
return datetime.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
def month_last_day(datetime):
|
||||
"""
|
||||
Returns a modified datetime with the day being the last day of the month,
|
||||
given a datetime object.
|
||||
"""
|
||||
if datetime.month in [1, 3, 5, 7, 8, 10, 12]:
|
||||
day = 31
|
||||
elif datetime.month in [4, 6, 9, 11]:
|
||||
day = 30
|
||||
else:
|
||||
if datetime.year % 4 == 0 and datetime.year % 100 != 0 or datetime.year % 400 == 0:
|
||||
day = 29
|
||||
else:
|
||||
day = 28
|
||||
return datetime.replace(day=day, hour=23, minute=59, second=59, microsecond=99999)
|
177
scribeengine/lib/validation/__init__.py
Normal file
@ -0,0 +1,177 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
Validation helper classes and methods.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from formencode.htmlfill import FillingParser
|
||||
from decorator import decorator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class ClassFillingParser(FillingParser):
|
||||
"""
|
||||
This parser inherits from the base htmlfill.FillingParser, but overrides
|
||||
the behaviour.
|
||||
|
||||
If it encounters the class `error_${name}` on an element and the name given
|
||||
is in the error dictionary, then the `form-error` class is added to the
|
||||
element. Otherwise, the class is removed.
|
||||
|
||||
If it encounters the class `errormessage_${name}` on an element and the name
|
||||
given is in the error dictionary, then the content of the element is
|
||||
replaced with the error from the error dictionary. Otherwise, the element
|
||||
is removed.
|
||||
|
||||
The class `error_` (ie, no name) will give any form errors.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Set up the filling parser.
|
||||
"""
|
||||
self.formname = kwargs.pop(u'formname', None)
|
||||
self.in_errormessage = None
|
||||
return FillingParser.__init__(self, *args, **kwargs)
|
||||
|
||||
def handle_starttag(self, tag, attrs, startend=False):
|
||||
"""
|
||||
Handle the start of an HTML tag.
|
||||
"""
|
||||
# Find any class attribute on the element
|
||||
class_attr = None
|
||||
for key, value in attrs:
|
||||
if key == u'class':
|
||||
class_attr = value
|
||||
break
|
||||
# Only have to handle the case where there is a class.
|
||||
if class_attr:
|
||||
classes = class_attr.split(u' ')
|
||||
for class_name in classes:
|
||||
# Replace error_${name} with "error" if name is in error dict
|
||||
if class_name.startswith(u'error_'):
|
||||
classes = [cls for cls in classes if cls != class_name]
|
||||
field = class_name[6:]
|
||||
if field in self.errors:
|
||||
classes.append(u'form-error')
|
||||
self.set_attr(attrs, u'class', u' '.join(classes))
|
||||
self.write_tag(tag, attrs, startend)
|
||||
self.skip_next = True
|
||||
return
|
||||
# Replace the contents of elements with class
|
||||
# errormessage_${name} with the error from the error
|
||||
# dictionary (or delete the element entirely if there is no
|
||||
# such error).
|
||||
if class_name.startswith(u'errormessage_'):
|
||||
field = class_name[13:]
|
||||
self.in_errormessage = tag
|
||||
self.skip_error = True
|
||||
# form errors
|
||||
if not field:
|
||||
field = None
|
||||
if field in self.errors:
|
||||
classes = [cls for cls in classes if cls != class_name]
|
||||
classes.append(u'form-error')
|
||||
self.set_attr(attrs, u'class', u' '.join(classes))
|
||||
self.write_tag(tag, attrs, startend)
|
||||
self.write_text(htmlliteral(unicode(self.errors[field])).text)
|
||||
self.write_text(u'</%s>' % tag)
|
||||
self.skip_next = True
|
||||
return
|
||||
return FillingParser.handle_starttag(self, tag, attrs, startend=False)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
"""
|
||||
Handle the ending HTML tag.
|
||||
"""
|
||||
FillingParser.handle_endtag(self, tag)
|
||||
# We're handling skipping of contents of an element with
|
||||
# errormessage_* on it.
|
||||
#
|
||||
# After we encounter the end tag, we can stop ignoring elements.
|
||||
if self.in_errormessage == tag:
|
||||
self.skip_error = False
|
||||
self.in_errormessage = None
|
||||
self.skip_next = True
|
||||
|
||||
@classmethod
|
||||
def html_error_fill(cls, form, errors_and_values):
|
||||
"""
|
||||
Create the custom ClassFillingParser, and pass through the values,
|
||||
errors, and formname from the errors dictionary.
|
||||
|
||||
Converts the incoming form from UTF-8-encoded byte strings to Unicode
|
||||
strings.
|
||||
"""
|
||||
p = cls(defaults=errors_and_values[u'form_values'],
|
||||
errors=errors_and_values[u'form_errors'],
|
||||
auto_error_formatter=False)
|
||||
p.feed(form.decode(u'utf-8'))
|
||||
p.close()
|
||||
return p.text().encode(u'utf-8')
|
||||
|
||||
|
||||
def jsvalidate(form_id):
|
||||
"""
|
||||
This decorator is used to generate JavaScript for client-side validate.
|
||||
|
||||
``form_id``
|
||||
The HTML ``id`` of the form to be validated.
|
||||
"""
|
||||
def entangle(func, self, *args, **kwargs):
|
||||
default_jscript = u"""/* jQuery Validation */
|
||||
$ProjectHQ.Events.bind_load(function () {
|
||||
$("#%s").validate({
|
||||
errorClass: "form-error",
|
||||
errorContainer: "#form-errors",
|
||||
errorLabelContainer: "#form-errors > ul",
|
||||
wrapper: "li",
|
||||
rules: {
|
||||
%s
|
||||
},
|
||||
messages: {
|
||||
%s
|
||||
}
|
||||
});
|
||||
});"""
|
||||
validators = []
|
||||
messages = []
|
||||
jsschema = func(self, *args, **kwargs)
|
||||
for name, validator in jsschema.iteritems():
|
||||
validators.append(u'%s: {%s}' % (name, validator.to_javascript()))
|
||||
if validator.get_message() is not None:
|
||||
validator_message = validator.get_message()
|
||||
if isinstance(validator_message, basestring):
|
||||
messages.append(u'%s: "%s"' % (name, validator.get_message()))
|
||||
else:
|
||||
validator_messages = []
|
||||
for key, value in validator_message.iteritems():
|
||||
validator_messages.append(u'%s: "%s"' % (key, value))
|
||||
messages.append(u'%s: {\n %s\n }' %
|
||||
(name, ',\n '.join(validator_messages)))
|
||||
jscript = default_jscript % (form_id, u',\n '.join(validators),
|
||||
u',\n '.join(messages))
|
||||
return jscript
|
||||
return decorator(entangle)
|
||||
|
230
scribeengine/lib/validation/client.py
Normal file
@ -0,0 +1,230 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
Client-side validators.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class JSValidator(object):
|
||||
"""
|
||||
This is a class used to create a validation rule in javascript.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
The constructor is used to create the validator.
|
||||
|
||||
field
|
||||
The name of the field.
|
||||
required
|
||||
Whether or not this field is required.
|
||||
type
|
||||
The type of field. Can be "string", "number", "email", "integer"
|
||||
"""
|
||||
if u'type' not in kwargs:
|
||||
raise KeyError(u'"type" is a required argument.')
|
||||
self.validators = {}
|
||||
for key, arg in kwargs.iteritems():
|
||||
self.validators[key] = arg
|
||||
|
||||
def to_javascript(self):
|
||||
js_validators = []
|
||||
for key, value in self.validators.iteritems():
|
||||
if key == u'message':
|
||||
continue
|
||||
elif key == u'type':
|
||||
if value in ['email', 'number', 'url']:
|
||||
key = value
|
||||
value = u'true'
|
||||
else:
|
||||
continue
|
||||
#elif key == u'checked':
|
||||
# key = 'required'
|
||||
# if value:
|
||||
# value = u'checked'
|
||||
# else:
|
||||
# value = u'unchecked'
|
||||
elif key == u'condition':
|
||||
conditions = value.split(u',')
|
||||
values = []
|
||||
for condition in conditions:
|
||||
subconditions = condition.split(u';')
|
||||
subvalues = []
|
||||
for subcondition in subconditions:
|
||||
if subcondition.find(u'==') >= 0:
|
||||
parts = subcondition.split(u'==')
|
||||
subvalues.append(u'$("%s").val() == %s' % (parts[0], parts[1]))
|
||||
elif subcondition.find(u'>=') >= 0:
|
||||
parts = subcondition.split(u'>=')
|
||||
subvalues.append(u'$("%s").val() >= %s' % (parts[0], parts[1]))
|
||||
elif subcondition.find(u'<=') >= 0:
|
||||
parts = subcondition.split(u'<=')
|
||||
subvalues.append(u'$("%s").val() <= %s' % (parts[0], parts[1]))
|
||||
elif subcondition.find(u'>') >= 0:
|
||||
parts = subcondition.split(u'>')
|
||||
subvalues.append(u'$("%s").val() > %s' % (parts[0], parts[1]))
|
||||
elif subcondition.find(u'<') >= 0:
|
||||
parts = subcondition.split(u'<')
|
||||
subvalues.append(u'$("%s").val() < %s' % (parts[0], parts[1]))
|
||||
elif subcondition.find(u'!=') >= 0:
|
||||
parts = subcondition.split(u'!=')
|
||||
subvalues.append(u'$("%s").val() != %s' % (parts[0], parts[1]))
|
||||
#elif subcondition.find(u'@') >= 0:
|
||||
# parts = subcondition.split(u':')
|
||||
# subvalues.append(u'$("%s").attr(%s)' % (parts[0], parts[1]))
|
||||
elif subcondition.find(u':') >= 0:
|
||||
parts = subcondition.split(u':')
|
||||
subvalues.append(u'$("%s").is(":%s")' % (parts[0], parts[1]))
|
||||
else:
|
||||
subvalues.append(u'$("%s")' % subcondition)
|
||||
values.append(u' && '.join(subvalues))
|
||||
value = u'function () { return (%s); }' % u') || ('.join(values)
|
||||
key = u'required'
|
||||
elif isinstance(value, bool):
|
||||
if value:
|
||||
value = u'true'
|
||||
else:
|
||||
value = u'false'
|
||||
elif isinstance(value, basestring):
|
||||
if isinstance(value, str):
|
||||
value = unicode(value, u'utf-8')
|
||||
value = u'"%s"' % value
|
||||
else:
|
||||
value = unicode(value)
|
||||
js_validators.append(u'%s: %s' % (key, value))
|
||||
return u', '.join(js_validators)
|
||||
|
||||
def get_message(self):
|
||||
"""
|
||||
If a message is set for this validator, return it, else return None.
|
||||
"""
|
||||
if 'message' in self.validators:
|
||||
return self.validators['message']
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class JSNumber(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for numbers.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'number'
|
||||
if 'condition' not in kwargs:
|
||||
kwargs['required'] = True
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSDigits(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for digits.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'digits'
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSString(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for strings.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'string'
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSName(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for names.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'string'
|
||||
kwargs['realname'] = True
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSAlphanumeric(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for alphanumeric strings.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'string'
|
||||
kwargs['alphanumeric'] = True
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSEmail(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for strings.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'email'
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSUrl(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for strings.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'url'
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSDate(JSValidator):
|
||||
"""
|
||||
This is a specialised version of JSValidator for dates.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['type'] = u'date'
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSDropdown(JSValidator):
|
||||
"""
|
||||
This is a copy of the DropdownValidator for Formencode.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'invalid_option' not in kwargs:
|
||||
invalid_option = 0
|
||||
else:
|
||||
invalid_option = kwargs['invalid_option']
|
||||
del kwargs['invalid_option']
|
||||
if 'condition' not in kwargs:
|
||||
kwargs['required'] = True
|
||||
kwargs['notvalue'] = invalid_option
|
||||
kwargs['type'] = u'string'
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class JSCheckbox(JSValidator):
|
||||
"""
|
||||
A validator for maching sure checkboxes are checked.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'checked' not in kwargs:
|
||||
kwargs['checked'] = True
|
||||
kwargs['type'] = u'string'
|
||||
JSValidator.__init__(self, *args, **kwargs)
|
58
scribeengine/lib/validation/server.py
Normal file
@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
Server-side validators.
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
|
||||
from formencode.api import FancyValidator, Invalid
|
||||
from formencode.validators import UnicodeString, Int, Email, FieldsMatch
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Password(FancyValidator):
|
||||
"""
|
||||
This validator checks for a decently secure password. The password has to
|
||||
contain a minimum of 6 characters, at least 1 number.
|
||||
"""
|
||||
regex = re.compile(r'^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[-.!@#%&]).{6,}$')
|
||||
|
||||
messages = {
|
||||
u'insecure': u'Your password must be longer than 6 characters and '
|
||||
u'must have at least 1 capital letter, 1 number and one '
|
||||
u'of the following characters: - . ~ @ # %% &'
|
||||
}
|
||||
|
||||
def _to_python(self, value, state):
|
||||
# _to_python gets run before validate_python. Here we
|
||||
# strip whitespace off the password, because leading and
|
||||
# trailing whitespace in a password is too elite.
|
||||
return value.strip()
|
||||
|
||||
def validate_python(self, value, state):
|
||||
if len(value) < self.min:
|
||||
raise Invalid(self.message(u'insecure', state), value, state)
|
||||
if not self.regex.match(value):
|
||||
raise Invalid(self.message(u'insecure', state), value, state)
|
||||
|
67
scribeengine/model/__init__.py
Normal file
@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
The application's model objects
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import mapper, relation
|
||||
|
||||
from scribeengine.model import meta
|
||||
from scribeengine.model.tables import categories_table, comments_table, \
|
||||
pages_table, permissions_table, posts_table, roles_table, tags_table, \
|
||||
users_table, variables_table, categories_posts_table, \
|
||||
permissions_roles_table, posts_tags_table, roles_users_table
|
||||
from scribeengine.model.classes import Category, Comment, Page, Permission, \
|
||||
Post, Role, Tag, User, Variable
|
||||
|
||||
def init_model(engine):
|
||||
"""Call me before using any of the tables or classes in the model"""
|
||||
meta.Session.configure(bind=engine)
|
||||
meta.engine = engine
|
||||
|
||||
mapper(Category, categories_table)
|
||||
mapper(Comment, comments_table)
|
||||
mapper(Page, pages_table)
|
||||
mapper(Permission, permissions_table)
|
||||
mapper(Post, posts_table,
|
||||
properties={
|
||||
u'categories': relation(Category, backref='posts', secondary=categories_posts_table),
|
||||
u'comments': relation(Comment, backref=u'post'),
|
||||
u'tags': relation(Tag, backref=u'posts', secondary=posts_tags_table)
|
||||
}
|
||||
)
|
||||
mapper(Role, roles_table,
|
||||
properties={
|
||||
u'permissions': relation(Permission, backref=u'roles', secondary=permissions_roles_table)
|
||||
}
|
||||
)
|
||||
mapper(Tag, tags_table)
|
||||
mapper(User, users_table,
|
||||
properties={
|
||||
u'comments': relation(Comment, backref=u'user'),
|
||||
#u'pages': relation(Page, backref=u'user'),
|
||||
u'posts': relation(Post, backref=u'user'),
|
||||
u'roles': relation(Role, backref=u'users', secondary=roles_users_table)
|
||||
}
|
||||
)
|
||||
mapper(Variable, variables_table)
|
119
scribeengine/model/classes.py
Normal file
@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
This module contains the Class definitions.
|
||||
"""
|
||||
|
||||
class BaseModel(object):
|
||||
"""
|
||||
A base model class which all the other classes inherit from. This provides
|
||||
all model classes with a set of utility methods.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
This constructor will set all the classes properties based on the
|
||||
keyword arguments supplied.
|
||||
"""
|
||||
for keyword, argument in kwargs.iteritems():
|
||||
setattr(self, keyword, argument)
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'id'):
|
||||
return '<%s id=%s>' % (self.__name__, self.id)
|
||||
|
||||
class Category(BaseModel):
|
||||
"""
|
||||
This is a category for blog posts.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Comment(BaseModel):
|
||||
"""
|
||||
All blog posts have comments. This is a single comment.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Page(BaseModel):
|
||||
"""
|
||||
A page on the blog. This is separate from a blog entry, for things like
|
||||
about pages.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Permission(BaseModel):
|
||||
"""
|
||||
A single permission.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Post(BaseModel):
|
||||
"""
|
||||
The most import part of all of this, the blog post.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Role(BaseModel):
|
||||
"""
|
||||
A role defines a set of permissions.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Tag(BaseModel):
|
||||
"""
|
||||
A tag, an unstructured category, for blog posts.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
"""
|
||||
The user.
|
||||
"""
|
||||
def has_permission(self, permission):
|
||||
if isinstance(permission, basestring):
|
||||
for role in self.roles:
|
||||
for perm in role.permissions:
|
||||
if perm.name == permission:
|
||||
return True
|
||||
return False
|
||||
elif isinstance(permission, Permission):
|
||||
for role in self.roles:
|
||||
for perm in role.permissions:
|
||||
if perm == permission:
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class Variable(BaseModel):
|
||||
"""
|
||||
System variables.
|
||||
"""
|
||||
pass
|
40
scribeengine/model/meta.py
Normal file
@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
SQLAlchemy Metadata and Session object
|
||||
"""
|
||||
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
__all__ = ['Session', 'engine', 'metadata']
|
||||
|
||||
# SQLAlchemy database engine. Updated by model.init_model()
|
||||
engine = None
|
||||
|
||||
# SQLAlchemy session manager. Updated by model.init_model()
|
||||
Session = scoped_session(sessionmaker())
|
||||
|
||||
# Global metadata. If you have multiple databases with overlapping table
|
||||
# names, you'll need a metadata for each database
|
||||
metadata = MetaData()
|
138
scribeengine/model/tables.py
Normal file
@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
This module contains the table definitions.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Table, Column, ForeignKey
|
||||
from sqlalchemy.types import Unicode, Integer, UnicodeText, DateTime
|
||||
|
||||
from scribeengine.model.meta import metadata
|
||||
|
||||
# Definition of the "categories" table
|
||||
categories_table = Table(u'categories', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'name', Unicode(100), nullable=False),
|
||||
Column(u'description', UnicodeText),
|
||||
Column(u'url', Unicode(255), nullable=False, index=True, unique=True),
|
||||
)
|
||||
|
||||
# Definition of the "comments" table
|
||||
comments_table = Table(u'comments', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'post_id', Integer, ForeignKey(u'posts.id'), nullable=True),
|
||||
Column(u'user_id', Integer, ForeignKey(u'users.id'), nullable=False),
|
||||
Column(u'title', Unicode(100), nullable=False),
|
||||
Column(u'body', UnicodeText, nullable=False),
|
||||
Column(u'status', Unicode(10), default='moderated'),
|
||||
Column(u'created', DateTime, default=datetime.now()),
|
||||
Column(u'modified', DateTime, default=datetime.now())
|
||||
)
|
||||
|
||||
# Definition of the "pages" table
|
||||
pages_table = Table(u'pages', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'title', Unicode(255), nullable=False),
|
||||
Column(u'body', UnicodeText),
|
||||
Column(u'url', Unicode(255), nullable=False, index=True, unique=True),
|
||||
Column(u'created', DateTime, default=datetime.now()),
|
||||
Column(u'modified', DateTime, default=datetime.now())
|
||||
)
|
||||
|
||||
# Definition of the "permissions" table
|
||||
permissions_table = Table(u'permissions', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'name', Unicode(80), nullable=False, index=True),
|
||||
Column(u'description', UnicodeText)
|
||||
)
|
||||
|
||||
# Definition of the "posts" table
|
||||
posts_table = Table(u'posts', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'user_id', Integer, ForeignKey(u'users.id'), nullable=False),
|
||||
Column(u'title', Unicode(255), nullable=False, index=True),
|
||||
Column(u'body', UnicodeText, nullable=False, index=True),
|
||||
Column(u'url', Unicode(255), nullable=False, index=True),
|
||||
Column(u'status', Unicode(10), default=u'draft', index=True),
|
||||
Column(u'comment_status', Unicode(10), default=u'open'),
|
||||
Column(u'created', DateTime, default=datetime.now()),
|
||||
Column(u'modified', DateTime, default=datetime.now())
|
||||
)
|
||||
|
||||
# Definition of the "roles" table
|
||||
roles_table = Table(u'roles', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'name', Unicode(80), nullable=False, index=True),
|
||||
Column(u'description', UnicodeText)
|
||||
)
|
||||
|
||||
# Definition of the "tags" table
|
||||
tags_table = Table(u'tags', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'name', Unicode(100), nullable=False),
|
||||
Column(u'url', Unicode(255), nullable=False, index=True),
|
||||
)
|
||||
|
||||
# Definition of the "users" table
|
||||
users_table = Table(u'users', metadata,
|
||||
Column(u'id', Integer, primary_key=True),
|
||||
Column(u'email', Unicode(200), nullable=False, index=True),
|
||||
Column(u'password', Unicode(64), nullable=False),
|
||||
Column(u'nick', Unicode(50), nullable=False, index=True),
|
||||
Column(u'first_name', Unicode(100)),
|
||||
Column(u'last_name', Unicode(100)),
|
||||
Column(u'homepage', Unicode(200)),
|
||||
Column(u'activation_key', Unicode(40))
|
||||
)
|
||||
|
||||
# Definition of the "variables" table
|
||||
variables_table = Table(u'variables', metadata,
|
||||
Column(u'key', Unicode(100), primary_key=True, index=True),
|
||||
Column(u'value', Unicode(100), nullable=False),
|
||||
Column(u'type', Unicode(10), default=u'string')
|
||||
)
|
||||
|
||||
# Definition of the "categories_posts" table
|
||||
categories_posts_table = Table(u'categories_posts', metadata,
|
||||
Column(u'category_id', Integer, ForeignKey(u'categories.id'), primary_key=True),
|
||||
Column(u'post_id', Integer, ForeignKey(u'posts.id'), primary_key=True)
|
||||
)
|
||||
|
||||
# Definition of the "permissions_roles" bridging table
|
||||
permissions_roles_table = Table(u'permissions_roles', metadata,
|
||||
Column(u'permission_id', Integer, ForeignKey(u'permissions.id'), primary_key=True),
|
||||
Column(u'role_id', Integer, ForeignKey(u'roles.id'), primary_key=True)
|
||||
)
|
||||
|
||||
# Definition of the "posts_tags" table
|
||||
posts_tags_table = Table(u'posts_tags', metadata,
|
||||
Column(u'post_id', Integer, ForeignKey(u'posts.id'), primary_key=True),
|
||||
Column(u'tag_id', Integer, ForeignKey(u'tags.id'), primary_key=True)
|
||||
)
|
||||
|
||||
# Definition of the "roles_users" bridging table
|
||||
roles_users_table = Table(u'roles_users', metadata,
|
||||
Column(u'user_id', Integer, ForeignKey(u'users.id'), primary_key=True),
|
||||
Column(u'role_id', Integer, ForeignKey(u'roles.id'), primary_key=True)
|
||||
)
|
BIN
scribeengine/public/images/img01.gif
Normal file
After Width: | Height: | Size: 399 B |
BIN
scribeengine/public/images/img02.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
scribeengine/public/images/img03.jpg
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
scribeengine/public/images/img04.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
scribeengine/public/images/img05.gif
Normal file
After Width: | Height: | Size: 810 B |
BIN
scribeengine/public/images/img06.gif
Normal file
After Width: | Height: | Size: 667 B |
BIN
scribeengine/public/images/img07.gif
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
scribeengine/public/images/spacer.gif
Normal file
After Width: | Height: | Size: 43 B |
BIN
scribeengine/public/pylons-logo.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
22
scribeengine/public/scripts/ScribeEngine.Init.js
Normal file
@ -0,0 +1,22 @@
|
||||
/*****************************************************************************
|
||||
* ScribeEngine - Open Source Blog Software *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2010 Raoul Snyman *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the *
|
||||
* Free Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
|
||||
* Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
*****************************************************************************/
|
||||
|
||||
$(document).ready(function () {
|
||||
ScribeEngine.Events.init();
|
||||
});
|
18
scribeengine/public/scripts/ScribeEngine.Post.js
Normal file
@ -0,0 +1,18 @@
|
||||
/*****************************************************************************
|
||||
* ScribeEngine - Open Source Blog Software *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2010 Raoul Snyman *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the *
|
||||
* Free Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
|
||||
* Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
*****************************************************************************/
|
237
scribeengine/public/scripts/ScribeEngine.js
Normal file
@ -0,0 +1,237 @@
|
||||
/*****************************************************************************
|
||||
* ScribeEngine - Open Source Blog Software *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2010 Raoul Snyman *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the *
|
||||
* Free Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
|
||||
* Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
*****************************************************************************/
|
||||
|
||||
window["ScribeEngine"] = {
|
||||
Namespace: {
|
||||
/**
|
||||
* Create a Javascript namespace.
|
||||
* Based on: http://code.google.com/p/namespacedotjs/
|
||||
* Idea behind this is to created nested namespaces that are not ugly.
|
||||
* Example:
|
||||
* Namespace(foo.bar);
|
||||
* foo.bar..myFunction = function () { } ;
|
||||
*/
|
||||
create: function (name, attributes) {
|
||||
var parts = name.split('.'),
|
||||
ns = window,
|
||||
i = 0;
|
||||
// find the deepest part of the namespace
|
||||
// that is already defined
|
||||
for(; i < parts.length && parts[i] in ns; i++)
|
||||
ns = ns[parts[i]];
|
||||
// initialize any remaining parts of the namespace
|
||||
for(; i < parts.length; i++)
|
||||
ns = ns[parts[i]] = {};
|
||||
// copy the attributes into the namespace
|
||||
for (var attr in attributes)
|
||||
ns[attr] = attributes[attr];
|
||||
},
|
||||
exists: function (namespace) {
|
||||
/**
|
||||
* Determine the namespace of a page
|
||||
*/
|
||||
page_namespace = $ScribeEngine.Namespace.get_page_namespace();
|
||||
return (namespace == page_namespace);
|
||||
},
|
||||
get_page_namespace: function () {
|
||||
return $("#content > h2").attr("id");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.append = function (elem) {
|
||||
this[this.length] = elem;
|
||||
}
|
||||
|
||||
ScribeEngine.Namespace.create("ScribeEngine.Events", {
|
||||
// Local variables
|
||||
onload_functions: Array(),
|
||||
// Functions
|
||||
load: function (func) {
|
||||
this.onload_functions.append(func);
|
||||
},
|
||||
click: function (selector, func) {
|
||||
$(selector).bind("click", func);
|
||||
},
|
||||
change: function (selector, func) {
|
||||
$(selector).bind("change", func);
|
||||
},
|
||||
submit: function (selector, func) {
|
||||
$(selector).bind("submit", func);
|
||||
},
|
||||
blur: function (selector, func) {
|
||||
$(selector).bind("blur", func);
|
||||
},
|
||||
paste: function (selector, func) {
|
||||
$(selector).bind("paste", func);
|
||||
},
|
||||
keyup: function (selector, func) {
|
||||
$(selector).bind("keyup", func);
|
||||
},
|
||||
keydown: function (selector, func) {
|
||||
$(selector).bind("keydown", func);
|
||||
},
|
||||
keypress: function (selector, func) {
|
||||
$(selector).bind("keypress", func);
|
||||
},
|
||||
get_element: function(event) {
|
||||
var targ;
|
||||
if (!event) {
|
||||
var event = window.event;
|
||||
}
|
||||
if (event.target) {
|
||||
targ = event.target;
|
||||
}
|
||||
else if (event.srcElement) {
|
||||
targ = event.srcElement;
|
||||
}
|
||||
if (targ.nodeType == 3) {
|
||||
// defeat Safari bug
|
||||
targ = targ.parentNode;
|
||||
}
|
||||
return $(targ);
|
||||
},
|
||||
init: function () {
|
||||
for (idx in this.onload_functions) {
|
||||
func = this.onload_functions[idx];
|
||||
func();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ScribeEngine.Namespace.create("ScribeEngine.Widgets", {
|
||||
/**
|
||||
* Adds a datepicker to an element.
|
||||
*/
|
||||
datepicker: function (selector)
|
||||
{
|
||||
$(selector).datepicker({showButtonPanel: true, dateFormat: "dd/mm/yy"});
|
||||
}
|
||||
});
|
||||
|
||||
ScribeEngine.Namespace.create("ScribeEngine.General", {
|
||||
/**
|
||||
* Fades out a message
|
||||
*/
|
||||
fade_message: function ()
|
||||
{
|
||||
$("#message").hide().fadeIn("slow", function() {
|
||||
setTimeout("$('#message').fadeOut('slow');", 1500);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Checks for a message and fades it in and out.
|
||||
*/
|
||||
show_message: function ()
|
||||
{
|
||||
if ($("#message"))
|
||||
{
|
||||
setTimeout("$ScribeEngine.General.fade_message()", 500);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Dynamically hide anything on the page that has a "jshidden" class.
|
||||
*/
|
||||
hide_elements: function ()
|
||||
{
|
||||
$(".jshidden").hide();
|
||||
},
|
||||
/**
|
||||
* Do all the funny things required to toggle a fieldset
|
||||
*/
|
||||
perform_toggle: function (fieldset)
|
||||
{
|
||||
content = $('> div', fieldset);
|
||||
if (fieldset.is('.collapsed'))
|
||||
{
|
||||
fieldset.removeClass('collapsed');
|
||||
content.slideDown('normal');
|
||||
}
|
||||
else
|
||||
{
|
||||
content.slideUp('normal',
|
||||
function()
|
||||
{
|
||||
fieldset.addClass('collapsed');
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Find the fieldset to toggle, and then perform the toggle
|
||||
*/
|
||||
toggle_fieldset: function ()
|
||||
{
|
||||
fieldset = $(this).parent().parent();
|
||||
$ScribeEngine.General.perform_toggle(fieldset);
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Sets the active project
|
||||
*/
|
||||
set_project: function ()
|
||||
{
|
||||
$('#project-selector > div.content').hide();
|
||||
$('#project-throbber').show();
|
||||
project_id = $("#CurrentProject").val();
|
||||
$.get('/dashboard/ajax_project/' + project_id, function () {
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Initialises collapsible fieldsets
|
||||
*/
|
||||
init_fieldsets: function ()
|
||||
{
|
||||
$("fieldset.collapsible > legend").each(function() {
|
||||
legend = $(this);
|
||||
legend_text = legend.text();
|
||||
legend.text("");
|
||||
legend.append(
|
||||
$("<a>").attr("href", "#").attr("title", "Expand/collapse details")
|
||||
.text(legend_text).click($ScribeEngine.General.toggle_fieldset));
|
||||
});
|
||||
$("fieldset.collapsed").each(function() {
|
||||
$("> div.content", this).slideUp("normal");
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Initialises elastic textareas
|
||||
*/
|
||||
init_textareas: function ()
|
||||
{
|
||||
$("textarea").elastic();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Global onload
|
||||
*
|
||||
* This function below will be executed on all page views.
|
||||
*/
|
||||
ScribeEngine.Events.load(function () {
|
||||
// Hide hidden elements
|
||||
ScribeEngine.General.hide_elements();
|
||||
// Initialise collapsible fieldsets
|
||||
ScribeEngine.General.init_fieldsets();
|
||||
// Initialise elastic textareas
|
||||
ScribeEngine.General.init_textareas();
|
||||
// Show any flash messages
|
||||
ScribeEngine.General.show_message();
|
||||
});
|
318
scribeengine/public/styles/style.css
Normal file
@ -0,0 +1,318 @@
|
||||
/*
|
||||
Design by Free CSS Templates
|
||||
http://www.freecsstemplates.org
|
||||
Released for free under a Creative Commons Attribution 2.5 License
|
||||
*/
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000000 url(../images/img01.gif) repeat-x;
|
||||
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
letter-spacing: -1px;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
p, ul, ol {
|
||||
margin-top: 1.8em;
|
||||
line-height: 180%;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-left: 3em;
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
#header {
|
||||
width: 960px;
|
||||
height: 80px;
|
||||
margin: 0 auto;
|
||||
background: url(../images/img02.jpg);
|
||||
}
|
||||
|
||||
#header h1, #header h2 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
padding: 30px 0 0 20px;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
#header h2 {
|
||||
padding: 47px 0 0 8px;
|
||||
font-size: 1.8em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#header a {
|
||||
text-decoration: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
|
||||
#menu {
|
||||
width: 960px;
|
||||
height: 51px;
|
||||
margin: 0 auto;
|
||||
background: url(../images/img03.jpg);
|
||||
}
|
||||
|
||||
#menu ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
line-height: 51px;
|
||||
}
|
||||
|
||||
#menu li {
|
||||
float: left;
|
||||
padding: 0 10px 0 20px;
|
||||
line-height: 51px;
|
||||
}
|
||||
|
||||
#menu a {
|
||||
text-decoration: none;
|
||||
letter-spacing: -1px;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
line-height: 51px;
|
||||
}
|
||||
|
||||
#menu a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Page */
|
||||
|
||||
#page {
|
||||
width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 20px 20px 20px;
|
||||
background: url(../images/img04.jpg) no-repeat;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
|
||||
#content {
|
||||
float: left;
|
||||
width: 605px;
|
||||
}
|
||||
|
||||
.post {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.post .title {
|
||||
border-bottom: 1px solid #454545;
|
||||
}
|
||||
|
||||
.post .title a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post .entry {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.post .meta {
|
||||
height: 20px;
|
||||
padding: 15px 20px;
|
||||
background: url(../images/img05.gif) no-repeat;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.post .meta a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.post .meta a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post .meta .byline {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.post .meta .comments {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.post .meta .read-more {
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
#sidebar {
|
||||
float: right;
|
||||
width: 295px;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#sidebar li {
|
||||
}
|
||||
|
||||
#sidebar li ul {
|
||||
padding: 0 0 20px 20px;
|
||||
list-style: square inside;
|
||||
}
|
||||
|
||||
#sidebar h2 {
|
||||
height: 50px;
|
||||
padding: 13px 20px 0 20px;
|
||||
background: url(../images/img06.gif) no-repeat;
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
|
||||
#search {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#search input {
|
||||
margin-bottom: 10px;
|
||||
padding: 3px 5px;
|
||||
background: #1F1F1F url(../images/img06.gif) no-repeat center center;
|
||||
border: 1px solid #454545;
|
||||
font: bold 1.2em "Trebuchet MS", Arial, Helvetica, sans-serif;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#search #s {
|
||||
width: 80%;
|
||||
background: #1F1F1F;
|
||||
}
|
||||
|
||||
/* Calendar */
|
||||
|
||||
#calendar {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#calendar table, #calendar caption {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#calendar caption {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .25em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#calendar thead th {
|
||||
background: #333333;
|
||||
}
|
||||
|
||||
#calendar tbody td {
|
||||
background: #111111;
|
||||
}
|
||||
|
||||
#calendar a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#calendar a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#footer {
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
background: url(../images/img07.gif) no-repeat;
|
||||
}
|
||||
|
||||
#footer p {
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
|
||||
form {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-text {
|
||||
font-size: 1.5em;
|
||||
padding: 4px 6px;
|
||||
width: 593px;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
height: 12em;
|
||||
width: 605px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 1em;
|
||||
}
|
20
scribeengine/templates/admin/login.mako
Normal file
@ -0,0 +1,20 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<div class="post">
|
||||
<h2 class="title">Log in</h2>
|
||||
<%include file="/errors.mako"/>
|
||||
<form id="post-new" action="${h.url_for('/admin/login')}" method="post">
|
||||
<fieldset>
|
||||
<div class="form-item">
|
||||
<label for="login-email">E-mail:</label>
|
||||
<input type="text" name="email" id="login-email" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="login-password">Password:</label>
|
||||
<input type="password" name="password" id="login-password" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<input type="submit" name="action" value="Login"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
28
scribeengine/templates/admin/register.mako
Normal file
@ -0,0 +1,28 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<div class="post">
|
||||
<h2 class="title">Register</h2>
|
||||
<%include file="/errors.mako"/>
|
||||
<form id="post-new" action="${h.url_for('/admin/register')}" method="post">
|
||||
<fieldset>
|
||||
<div class="form-item">
|
||||
<label for="register-nick">Nick:</label>
|
||||
<input type="text" name="nick" id="register-nick" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="register-email">E-mail:</label>
|
||||
<input type="text" name="email" id="register-email" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="register-password">Password:</label>
|
||||
<input type="password" name="password" id="register-password" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="register-confirm-password">Confirm Password:</label>
|
||||
<input type="password" name="confirm-password" id="register-confirm-password" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<input type="submit" name="action" value="Register"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
37
scribeengine/templates/base.mako
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>${c.page_title}</title>
|
||||
<meta name="keywords" content="" />
|
||||
<meta name="description" content="" />
|
||||
<link href="/styles/style.css" rel="stylesheet" type="text/css" media="screen" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1><a href="${h.url_for('/')}" title="${c.blog_title}">${c.blog_title}</a></h1>
|
||||
<h2>${c.blog_slogan}</h2>
|
||||
</div>
|
||||
<div id="menu">
|
||||
<ul>
|
||||
<li><a href="${h.url_for('/')}">Home</a></li>
|
||||
% for page in c.pages:
|
||||
<li><a href="${page.url}">${page.name}</a></li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="page">
|
||||
<div id="content">
|
||||
${next.body()}
|
||||
</div>
|
||||
<%include file="/sidebar.mako"/>
|
||||
<div style="clear: both;"> </div>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="footer">
|
||||
<p>Copyright © 2010 Raoul and Hannah Snyman. Powered by ScribeEngine. Design by <a href="http://www.freecsstemplates.org/">Free CSS Templates</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
21
scribeengine/templates/blog/index.mako
Normal file
@ -0,0 +1,21 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
% for post in c.posts:
|
||||
<% post.full_url = u'/archive/%s/%s/%s/%s' % (post.created.strftime('%Y'), post.created.strftime('%m'), post.created.strftime('%d'), post.url) %>
|
||||
<div class="post">
|
||||
<h2 class="title"><a href="${post.full_url}">${post.title}</a></h2>
|
||||
<div class="entry">
|
||||
${h.literal(h.teaser(post.body, post.full_url))}
|
||||
</div>
|
||||
<p class="meta">
|
||||
<span class="byline">Posted by ${post.user.nick} on ${post.created.strftime('%B %d, %Y')}</span>
|
||||
<a href="${post.full_url}" class="read-more">Read more</a>
|
||||
% if len(post.comments) == 0:
|
||||
<a href="${post.full_url}#comments" class="comments">No comments</a>
|
||||
% elif len(post.comments) == 1:
|
||||
<a href="${post.full_url}#comments" class="comments">1 comment</a>
|
||||
% else:
|
||||
<a href="${post.full_url}#comments" class="comments">${len(post.comments)} comments</a>
|
||||
% endif
|
||||
</p>
|
||||
</div>
|
||||
% endfor
|
7
scribeengine/templates/blog/teaser.mako
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="post">
|
||||
<h2 class="title"><a href="${h.url_for(year=post.created.strftime('%Y'), month=post.created.strftime('%m'), day=post.created.strftime('%d'), url=post.url)}">${post.title}</a></h2>
|
||||
<div class="entry">
|
||||
${h.literal(post.body)}
|
||||
</div>
|
||||
<p class="meta"><span class="byline">Posted by ${post.user.first_name} on ${post.created.strftime('%B %d, %Y')}</span> <a href="${h.url_for(year=post.created.strftime('%Y'), month=post.created.strftime('%m'), day=post.created.strftime('%d'), url=post.url)}#comments" class="comments">18 comments</a></p>
|
||||
</div>
|
48
scribeengine/templates/blog/view.mako
Normal file
@ -0,0 +1,48 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<div class="post">
|
||||
<h2 class="title"><a href="${h.url_for(year=c.post.created.strftime('%Y'), month=c.post.created.strftime('%m'), day=c.post.created.strftime('%d'), url=c.post.url)}">${c.post.title}</a></h2>
|
||||
<div class="info">Posted by ${c.post.user.nick} on ${c.post.created.strftime('%B %d, %Y')}</div>
|
||||
<div class="entry">
|
||||
${h.literal(c.post.body)}
|
||||
</div>
|
||||
<div> </div>
|
||||
% if len(c.post.comments) == 0:
|
||||
<h3 id="comments">No Responses</h3>
|
||||
<p> </p>
|
||||
% elif len(c.post.comments) == 1:
|
||||
<h3 id="comments">One Response</h3>
|
||||
% else:
|
||||
<h3 id="comments">${len(c.post.comments)} Responses</h3>
|
||||
% endif
|
||||
% if len(c.post.comments) > 0:
|
||||
<ol class="commentlist">
|
||||
% for num, comment in enumerate(c.post.comments):
|
||||
<li id="comment-${comment.id}">
|
||||
<cite>${comment.user.nick}</cite> Says:<br />
|
||||
<small class="commentmetadata"><a href="#comment-<?php comment_ID() ?>" title="">${comment.created.strftime('%B %d, %Y')} at ${comment.created.strftime('%H:%M')}</a> <a href="edit">edit</a></small>
|
||||
${comment.body}
|
||||
</li>
|
||||
% endfor
|
||||
</ol>
|
||||
% else:
|
||||
% if c.post.comment_status != u'open':
|
||||
<p class="nocomments">Comments are closed.</p>
|
||||
% endif
|
||||
% endif
|
||||
% if c.post.comment_status == u'open':
|
||||
<h3 id="respond">Leave a Reply</h3>
|
||||
% if not c.current_user:
|
||||
<p>You must be <a href="/login">logged in</a> to post a comment.</p>
|
||||
% else:
|
||||
<div> </div>
|
||||
<form action="${h.url_for('/comment/edit')}" method="post" id="commentform">
|
||||
<p>Logged in as <em>${c.current_user.nick}</em>. <a href="/logout" title="Log out of this account">Logout »</a></p>
|
||||
<p><textarea name="comment" id="comment" cols="80" rows="10" tabindex="4" style="width: 605px;"></textarea></p>
|
||||
<p>
|
||||
<input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" />
|
||||
<input type="hidden" name="post_id" value="${c.post.id}" />
|
||||
</p>
|
||||
</form>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
38
scribeengine/templates/calendar.mako
Normal file
@ -0,0 +1,38 @@
|
||||
<table summary="Calendar">
|
||||
<caption>
|
||||
${c.thismonth.strftime('%B %Y')}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th abbr="Sunday" scope="col" title="Sunday">S</th>
|
||||
<th abbr="Monday" scope="col" title="Monday">M</th>
|
||||
<th abbr="Tuesday" scope="col" title="Tuesday">T</th>
|
||||
<th abbr="Wednesday" scope="col" title="Wednesday">W</th>
|
||||
<th abbr="Thursday" scope="col" title="Thursday">T</th>
|
||||
<th abbr="Friday" scope="col" title="Friday">F</th>
|
||||
<th abbr="Saturday" scope="col" title="Saturday">S</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td abbr="October" colspan="3" id="prev"><a href="#" title="View posts for October 2007">« Oct</a></td>
|
||||
<td class="pad"> </td>
|
||||
<td abbr="December" colspan="3" id="next"><a href="#" title="View posts for October 2007">Dec »</a></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
% for week in c.calendar.monthdays2calendar(c.thismonth.year, c.thismonth.month):
|
||||
<tr>
|
||||
% for day, weekday in week:
|
||||
% if day == 0:
|
||||
<td class="pad"> </td>
|
||||
% elif day == c.today.day:
|
||||
<td id="today">${day}</td>
|
||||
% else:
|
||||
<td>${day}</td>
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
5
scribeengine/templates/email/test.mako
Normal file
@ -0,0 +1,5 @@
|
||||
Dear ${c.name},
|
||||
|
||||
This is a test e-mail. Please ignore it.
|
||||
|
||||
Thanks.
|
16
scribeengine/templates/errors.mako
Normal file
@ -0,0 +1,16 @@
|
||||
% if c.form_errors and len(c.form_errors) > 0:
|
||||
<div id="form-errors">
|
||||
<p>The following errors occurred:</p>
|
||||
<ul>
|
||||
% for field, message in c.form_errors.iteritems():
|
||||
<li>${message}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
% else:
|
||||
<div id="form-errors" clsas="hidden">
|
||||
<p>The following errors occurred:</p>
|
||||
<ul>
|
||||
</ul>
|
||||
</div>
|
||||
% endif
|
21
scribeengine/templates/post/new.mako
Normal file
@ -0,0 +1,21 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<div class="post">
|
||||
<h2 class="title">New Post</h2>
|
||||
<%include file="/errors.mako"/>
|
||||
<form id="post-new" action="${h.url_for('/post/edit')}" method="post">
|
||||
<fieldset>
|
||||
<div class="form-item">
|
||||
<!-- <label for="post-title">Title:</label> -->
|
||||
<input type="text" name="title" id="post-title" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<!-- <label for="post-body">Body:</label> -->
|
||||
<textarea name="body" id="post-body" class="form-textarea"></textarea>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<input type="submit" name="action" value="Save Draft"/>
|
||||
<input type="submit" name="action" value="Save & Publish"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
30
scribeengine/templates/sidebar.mako
Normal file
@ -0,0 +1,30 @@
|
||||
<div id="sidebar">
|
||||
<ul>
|
||||
<li id="search">
|
||||
<form id="searchform" method="get" action="/search">
|
||||
<div>
|
||||
<input type="text" name="keywords" id="s" size="15" />
|
||||
<br />
|
||||
<input name="submit" type="submit" value="Search" />
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
<li id="calendar">
|
||||
<h2>Calendar</h2>
|
||||
<div id="calendar_wrap">
|
||||
<%include file="/calendar.mako"/>
|
||||
</div>
|
||||
</li>
|
||||
% if len(c.categories) > 0:
|
||||
<li>
|
||||
<h2>Categories</h2>
|
||||
<ul>
|
||||
% for category in c.categories:
|
||||
<li><a href="${h.url_for('/category/%s' % str(category.url))}" title="${category.name}">${category.name}</a> (${len(category.posts)}) </li>
|
||||
% endfor
|
||||
</ul>
|
||||
</li>
|
||||
% endif
|
||||
</ul>
|
||||
<div style="clear: both; height: 40px;"> </div>
|
||||
</div>
|
36
scribeengine/tests/__init__.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""Pylons application test package
|
||||
|
||||
This package assumes the Pylons environment is already loaded, such as
|
||||
when this script is imported from the `nosetests --with-pylons=test.ini`
|
||||
command.
|
||||
|
||||
This module initializes the application via ``websetup`` (`paster
|
||||
setup-app`) and provides the base testing objects.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
|
||||
from paste.deploy import loadapp
|
||||
from paste.script.appinstall import SetupCommand
|
||||
from pylons import config, url
|
||||
from routes.util import URLGenerator
|
||||
from webtest import TestApp
|
||||
|
||||
import pylons.test
|
||||
|
||||
__all__ = ['environ', 'url', 'TestController']
|
||||
|
||||
# Invoke websetup with the current config file
|
||||
SetupCommand('setup-app').run([config['__file__']])
|
||||
|
||||
environ = {}
|
||||
|
||||
class TestController(TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if pylons.test.pylonsapp:
|
||||
wsgiapp = pylons.test.pylonsapp
|
||||
else:
|
||||
wsgiapp = loadapp('config:%s' % config['__file__'])
|
||||
self.app = TestApp(wsgiapp)
|
||||
url._push_object(URLGenerator(config['routes.map'], environ))
|
||||
TestCase.__init__(self, *args, **kwargs)
|
0
scribeengine/tests/functional/__init__.py
Normal file
7
scribeengine/tests/functional/test_admin.py
Normal file
@ -0,0 +1,7 @@
|
||||
from scribeengine.tests import *
|
||||
|
||||
class TestAdminController(TestController):
|
||||
|
||||
def test_index(self):
|
||||
response = self.app.get(url(controller='admin', action='index'))
|
||||
# Test response...
|
7
scribeengine/tests/functional/test_blog.py
Normal file
@ -0,0 +1,7 @@
|
||||
from scribeengine.tests import *
|
||||
|
||||
class TestBlogController(TestController):
|
||||
|
||||
def test_index(self):
|
||||
response = self.app.get(url(controller='blog', action='index'))
|
||||
# Test response...
|
7
scribeengine/tests/functional/test_post.py
Normal file
@ -0,0 +1,7 @@
|
||||
from scribeengine.tests import *
|
||||
|
||||
class TestPostController(TestController):
|
||||
|
||||
def test_index(self):
|
||||
response = self.app.get(url(controller='post', action='index'))
|
||||
# Test response...
|
0
scribeengine/tests/test_models.py
Normal file
66
scribeengine/websetup.py
Normal file
@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# ScribeEngine - Open Source Blog Software #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2010 Raoul Snyman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
Setup the ScribeEngine application
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from scribeengine.config.environment import load_environment
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def setup_app(command, conf, vars):
|
||||
"""Place any commands to setup scribeengine here"""
|
||||
load_environment(conf.global_conf, conf.local_conf)
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
from scribeengine.model.meta import metadata, Session, engine
|
||||
from scribeengine.model import Category, Permission, Post, Variable, \
|
||||
User, Role
|
||||
|
||||
# Create the tables if they don't already exist
|
||||
metadata.create_all(bind=engine, checkfirst=True)
|
||||
|
||||
blog_title = Variable(key=u'blog title', value=u'ScribeEngine')
|
||||
blog_slogan = Variable(key=u'blog slogan', value=u'open source blog software')
|
||||
|
||||
pylons_cat = Category(name=u'Pylons', url=u'pylons')
|
||||
database_cat = Category(name=u'Database', url=u'database')
|
||||
|
||||
perm_addposts = Permission(name=u'Add Posts')
|
||||
perm_editmyposts = Permission(name=u'Edit My Posts')
|
||||
perm_delmyposts = Permission(name=u'Delete My Posts')
|
||||
|
||||
role_admin = Role(name=u'Administrator')
|
||||
role_admin.permissions.extend([perm_addposts, perm_editmyposts, perm_delmyposts])
|
||||
|
||||
password = unicode(hmac.new(conf[u'security.salt'], u'omigosh',
|
||||
hashlib.sha256).hexdigest(), u'utf-8')
|
||||
user = User(email=u'raoul.snyman@saturnlaboratories.co.za',
|
||||
password=password, nick=u'raoul')
|
||||
user.roles.append(role_admin)
|
||||
|
||||
Session.add_all([blog_title, blog_slogan, user])
|
||||
Session.commit()
|
31
setup.cfg
Normal file
@ -0,0 +1,31 @@
|
||||
[egg_info]
|
||||
tag_build = dev
|
||||
tag_svn_revision = true
|
||||
|
||||
[easy_install]
|
||||
find_links = http://www.pylonshq.com/download/
|
||||
|
||||
[nosetests]
|
||||
with-pylons = test.ini
|
||||
|
||||
# Babel configuration
|
||||
[compile_catalog]
|
||||
domain = scribeengine
|
||||
directory = scribeengine/i18n
|
||||
statistics = true
|
||||
|
||||
[extract_messages]
|
||||
add_comments = TRANSLATORS:
|
||||
output_file = scribeengine/i18n/scribeengine.pot
|
||||
width = 80
|
||||
|
||||
[init_catalog]
|
||||
domain = scribeengine
|
||||
input_file = scribeengine/i18n/scribeengine.pot
|
||||
output_dir = scribeengine/i18n
|
||||
|
||||
[update_catalog]
|
||||
domain = scribeengine
|
||||
input_file = scribeengine/i18n/scribeengine.pot
|
||||
output_dir = scribeengine/i18n
|
||||
previous = true
|
37
setup.py
Normal file
@ -0,0 +1,37 @@
|
||||
try:
|
||||
from setuptools import setup, find_packages
|
||||
except ImportError:
|
||||
from ez_setup import use_setuptools
|
||||
use_setuptools()
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='ScribeEngine',
|
||||
version='0.1',
|
||||
description='',
|
||||
author='',
|
||||
author_email='',
|
||||
url='',
|
||||
install_requires=[
|
||||
"Pylons>=0.9.7",
|
||||
"SQLAlchemy>=0.5",
|
||||
],
|
||||
setup_requires=["PasteScript>=1.6.3"],
|
||||
packages=find_packages(exclude=['ez_setup']),
|
||||
include_package_data=True,
|
||||
test_suite='nose.collector',
|
||||
package_data={'scribeengine': ['i18n/*/LC_MESSAGES/*.mo']},
|
||||
#message_extractors={'scribeengine': [
|
||||
# ('**.py', 'python', None),
|
||||
# ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
|
||||
# ('public/**', 'ignore', None)]},
|
||||
zip_safe=False,
|
||||
paster_plugins=['PasteScript', 'Pylons'],
|
||||
entry_points="""
|
||||
[paste.app_factory]
|
||||
main = scribeengine.config.middleware:make_app
|
||||
|
||||
[paste.app_install]
|
||||
main = pylons.util:PylonsInstaller
|
||||
""",
|
||||
)
|
21
test.ini
Normal file
@ -0,0 +1,21 @@
|
||||
#
|
||||
# ScribeEngine - Pylons testing environment configuration
|
||||
#
|
||||
# The %(here)s variable will be replaced with the parent directory of this file
|
||||
#
|
||||
[DEFAULT]
|
||||
debug = true
|
||||
# Uncomment and replace with the address which should receive any error reports
|
||||
#email_to = you@yourdomain.com
|
||||
smtp_server = localhost
|
||||
error_email_from = paste@localhost
|
||||
|
||||
[server:main]
|
||||
use = egg:Paste#http
|
||||
host = 127.0.0.1
|
||||
port = 5000
|
||||
|
||||
[app:main]
|
||||
use = config:development.ini
|
||||
|
||||
# Add additional test specific configuration options as necessary.
|
BIN
themes/stargazer/public/images/img01.gif
Normal file
After Width: | Height: | Size: 399 B |
BIN
themes/stargazer/public/images/img02.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
themes/stargazer/public/images/img03.jpg
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
themes/stargazer/public/images/img04.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
themes/stargazer/public/images/img05.gif
Normal file
After Width: | Height: | Size: 810 B |
BIN
themes/stargazer/public/images/img06.gif
Normal file
After Width: | Height: | Size: 667 B |
BIN
themes/stargazer/public/images/img07.gif
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
themes/stargazer/public/images/spacer.gif
Normal file
After Width: | Height: | Size: 43 B |
BIN
themes/stargazer/public/pylons-logo.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
22
themes/stargazer/public/scripts/ScribeEngine.Init.js
Normal file
@ -0,0 +1,22 @@
|
||||
/*****************************************************************************
|
||||
* ScribeEngine - Open Source Blog Software *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2010 Raoul Snyman *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the *
|
||||
* Free Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
|
||||
* Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
*****************************************************************************/
|
||||
|
||||
$(document).ready(function () {
|
||||
ScribeEngine.Events.init();
|
||||
});
|
18
themes/stargazer/public/scripts/ScribeEngine.Post.js
Normal file
@ -0,0 +1,18 @@
|
||||
/*****************************************************************************
|
||||
* ScribeEngine - Open Source Blog Software *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2010 Raoul Snyman *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the *
|
||||
* Free Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
|
||||
* Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
*****************************************************************************/
|
237
themes/stargazer/public/scripts/ScribeEngine.js
Normal file
@ -0,0 +1,237 @@
|
||||
/*****************************************************************************
|
||||
* ScribeEngine - Open Source Blog Software *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2010 Raoul Snyman *
|
||||
* ------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the *
|
||||
* Free Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
|
||||
* Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
*****************************************************************************/
|
||||
|
||||
window["ScribeEngine"] = {
|
||||
Namespace: {
|
||||
/**
|
||||
* Create a Javascript namespace.
|
||||
* Based on: http://code.google.com/p/namespacedotjs/
|
||||
* Idea behind this is to created nested namespaces that are not ugly.
|
||||
* Example:
|
||||
* Namespace(foo.bar);
|
||||
* foo.bar..myFunction = function () { } ;
|
||||
*/
|
||||
create: function (name, attributes) {
|
||||
var parts = name.split('.'),
|
||||
ns = window,
|
||||
i = 0;
|
||||
// find the deepest part of the namespace
|
||||
// that is already defined
|
||||
for(; i < parts.length && parts[i] in ns; i++)
|
||||
ns = ns[parts[i]];
|
||||
// initialize any remaining parts of the namespace
|
||||
for(; i < parts.length; i++)
|
||||
ns = ns[parts[i]] = {};
|
||||
// copy the attributes into the namespace
|
||||
for (var attr in attributes)
|
||||
ns[attr] = attributes[attr];
|
||||
},
|
||||
exists: function (namespace) {
|
||||
/**
|
||||
* Determine the namespace of a page
|
||||
*/
|
||||
page_namespace = $ScribeEngine.Namespace.get_page_namespace();
|
||||
return (namespace == page_namespace);
|
||||
},
|
||||
get_page_namespace: function () {
|
||||
return $("#content > h2").attr("id");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.append = function (elem) {
|
||||
this[this.length] = elem;
|
||||
}
|
||||
|
||||
ScribeEngine.Namespace.create("ScribeEngine.Events", {
|
||||
// Local variables
|
||||
onload_functions: Array(),
|
||||
// Functions
|
||||
load: function (func) {
|
||||
this.onload_functions.append(func);
|
||||
},
|
||||
click: function (selector, func) {
|
||||
$(selector).bind("click", func);
|
||||
},
|
||||
change: function (selector, func) {
|
||||
$(selector).bind("change", func);
|
||||
},
|
||||
submit: function (selector, func) {
|
||||
$(selector).bind("submit", func);
|
||||
},
|
||||
blur: function (selector, func) {
|
||||
$(selector).bind("blur", func);
|
||||
},
|
||||
paste: function (selector, func) {
|
||||
$(selector).bind("paste", func);
|
||||
},
|
||||
keyup: function (selector, func) {
|
||||
$(selector).bind("keyup", func);
|
||||
},
|
||||
keydown: function (selector, func) {
|
||||
$(selector).bind("keydown", func);
|
||||
},
|
||||
keypress: function (selector, func) {
|
||||
$(selector).bind("keypress", func);
|
||||
},
|
||||
get_element: function(event) {
|
||||
var targ;
|
||||
if (!event) {
|
||||
var event = window.event;
|
||||
}
|
||||
if (event.target) {
|
||||
targ = event.target;
|
||||
}
|
||||
else if (event.srcElement) {
|
||||
targ = event.srcElement;
|
||||
}
|
||||
if (targ.nodeType == 3) {
|
||||
// defeat Safari bug
|
||||
targ = targ.parentNode;
|
||||
}
|
||||
return $(targ);
|
||||
},
|
||||
init: function () {
|
||||
for (idx in this.onload_functions) {
|
||||
func = this.onload_functions[idx];
|
||||
func();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ScribeEngine.Namespace.create("ScribeEngine.Widgets", {
|
||||
/**
|
||||
* Adds a datepicker to an element.
|
||||
*/
|
||||
datepicker: function (selector)
|
||||
{
|
||||
$(selector).datepicker({showButtonPanel: true, dateFormat: "dd/mm/yy"});
|
||||
}
|
||||
});
|
||||
|
||||
ScribeEngine.Namespace.create("ScribeEngine.General", {
|
||||
/**
|
||||
* Fades out a message
|
||||
*/
|
||||
fade_message: function ()
|
||||
{
|
||||
$("#message").hide().fadeIn("slow", function() {
|
||||
setTimeout("$('#message').fadeOut('slow');", 1500);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Checks for a message and fades it in and out.
|
||||
*/
|
||||
show_message: function ()
|
||||
{
|
||||
if ($("#message"))
|
||||
{
|
||||
setTimeout("$ScribeEngine.General.fade_message()", 500);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Dynamically hide anything on the page that has a "jshidden" class.
|
||||
*/
|
||||
hide_elements: function ()
|
||||
{
|
||||
$(".jshidden").hide();
|
||||
},
|
||||
/**
|
||||
* Do all the funny things required to toggle a fieldset
|
||||
*/
|
||||
perform_toggle: function (fieldset)
|
||||
{
|
||||
content = $('> div', fieldset);
|
||||
if (fieldset.is('.collapsed'))
|
||||
{
|
||||
fieldset.removeClass('collapsed');
|
||||
content.slideDown('normal');
|
||||
}
|
||||
else
|
||||
{
|
||||
content.slideUp('normal',
|
||||
function()
|
||||
{
|
||||
fieldset.addClass('collapsed');
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Find the fieldset to toggle, and then perform the toggle
|
||||
*/
|
||||
toggle_fieldset: function ()
|
||||
{
|
||||
fieldset = $(this).parent().parent();
|
||||
$ScribeEngine.General.perform_toggle(fieldset);
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Sets the active project
|
||||
*/
|
||||
set_project: function ()
|
||||
{
|
||||
$('#project-selector > div.content').hide();
|
||||
$('#project-throbber').show();
|
||||
project_id = $("#CurrentProject").val();
|
||||
$.get('/dashboard/ajax_project/' + project_id, function () {
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Initialises collapsible fieldsets
|
||||
*/
|
||||
init_fieldsets: function ()
|
||||
{
|
||||
$("fieldset.collapsible > legend").each(function() {
|
||||
legend = $(this);
|
||||
legend_text = legend.text();
|
||||
legend.text("");
|
||||
legend.append(
|
||||
$("<a>").attr("href", "#").attr("title", "Expand/collapse details")
|
||||
.text(legend_text).click($ScribeEngine.General.toggle_fieldset));
|
||||
});
|
||||
$("fieldset.collapsed").each(function() {
|
||||
$("> div.content", this).slideUp("normal");
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Initialises elastic textareas
|
||||
*/
|
||||
init_textareas: function ()
|
||||
{
|
||||
$("textarea").elastic();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Global onload
|
||||
*
|
||||
* This function below will be executed on all page views.
|
||||
*/
|
||||
ScribeEngine.Events.load(function () {
|
||||
// Hide hidden elements
|
||||
ScribeEngine.General.hide_elements();
|
||||
// Initialise collapsible fieldsets
|
||||
ScribeEngine.General.init_fieldsets();
|
||||
// Initialise elastic textareas
|
||||
ScribeEngine.General.init_textareas();
|
||||
// Show any flash messages
|
||||
ScribeEngine.General.show_message();
|
||||
});
|
318
themes/stargazer/public/styles/style.css
Normal file
@ -0,0 +1,318 @@
|
||||
/*
|
||||
Design by Free CSS Templates
|
||||
http://www.freecsstemplates.org
|
||||
Released for free under a Creative Commons Attribution 2.5 License
|
||||
*/
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000000 url(../images/img01.gif) repeat-x;
|
||||
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
letter-spacing: -1px;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
p, ul, ol {
|
||||
margin-top: 1.8em;
|
||||
line-height: 180%;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-left: 3em;
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
#header {
|
||||
width: 960px;
|
||||
height: 80px;
|
||||
margin: 0 auto;
|
||||
background: url(../images/img02.jpg);
|
||||
}
|
||||
|
||||
#header h1, #header h2 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
padding: 30px 0 0 20px;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
#header h2 {
|
||||
padding: 47px 0 0 8px;
|
||||
font-size: 1.8em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#header a {
|
||||
text-decoration: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
|
||||
#menu {
|
||||
width: 960px;
|
||||
height: 51px;
|
||||
margin: 0 auto;
|
||||
background: url(../images/img03.jpg);
|
||||
}
|
||||
|
||||
#menu ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
line-height: 51px;
|
||||
}
|
||||
|
||||
#menu li {
|
||||
float: left;
|
||||
padding: 0 10px 0 20px;
|
||||
line-height: 51px;
|
||||
}
|
||||
|
||||
#menu a {
|
||||
text-decoration: none;
|
||||
letter-spacing: -1px;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
line-height: 51px;
|
||||
}
|
||||
|
||||
#menu a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Page */
|
||||
|
||||
#page {
|
||||
width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 20px 20px 20px;
|
||||
background: url(../images/img04.jpg) no-repeat;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
|
||||
#content {
|
||||
float: left;
|
||||
width: 605px;
|
||||
}
|
||||
|
||||
.post {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.post .title {
|
||||
border-bottom: 1px solid #454545;
|
||||
}
|
||||
|
||||
.post .title a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post .entry {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.post .meta {
|
||||
height: 20px;
|
||||
padding: 15px 20px;
|
||||
background: url(../images/img05.gif) no-repeat;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.post .meta a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.post .meta a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post .meta .byline {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.post .meta .comments {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.post .meta .read-more {
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
#sidebar {
|
||||
float: right;
|
||||
width: 295px;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#sidebar li {
|
||||
}
|
||||
|
||||
#sidebar li ul {
|
||||
padding: 0 0 20px 20px;
|
||||
list-style: square inside;
|
||||
}
|
||||
|
||||
#sidebar h2 {
|
||||
height: 50px;
|
||||
padding: 13px 20px 0 20px;
|
||||
background: url(../images/img06.gif) no-repeat;
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
|
||||
#search {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#search input {
|
||||
margin-bottom: 10px;
|
||||
padding: 3px 5px;
|
||||
background: #1F1F1F url(../images/img06.gif) no-repeat center center;
|
||||
border: 1px solid #454545;
|
||||
font: bold 1.2em "Trebuchet MS", Arial, Helvetica, sans-serif;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#search #s {
|
||||
width: 80%;
|
||||
background: #1F1F1F;
|
||||
}
|
||||
|
||||
/* Calendar */
|
||||
|
||||
#calendar {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#calendar table, #calendar caption {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#calendar caption {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .25em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#calendar thead th {
|
||||
background: #333333;
|
||||
}
|
||||
|
||||
#calendar tbody td {
|
||||
background: #111111;
|
||||
}
|
||||
|
||||
#calendar a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#calendar a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#footer {
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
background: url(../images/img07.gif) no-repeat;
|
||||
}
|
||||
|
||||
#footer p {
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
|
||||
form {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-text {
|
||||
font-size: 1.5em;
|
||||
padding: 4px 6px;
|
||||
width: 593px;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
height: 12em;
|
||||
width: 605px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 1em;
|
||||
}
|
37
themes/stargazer/templates/base.mako
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>${c.page_title}</title>
|
||||
<meta name="keywords" content="" />
|
||||
<meta name="description" content="" />
|
||||
<link href="/styles/style.css" rel="stylesheet" type="text/css" media="screen" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1><a href="${h.url_for('/')}" title="${c.blog_title}">${c.blog_title}</a></h1>
|
||||
<h2>${c.blog_slogan}</h2>
|
||||
</div>
|
||||
<div id="menu">
|
||||
<ul>
|
||||
<li><a href="${h.url_for('/')}">Home</a></li>
|
||||
% for page in c.pages:
|
||||
<li><a href="${page.url}">${page.name}</a></li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="page">
|
||||
<div id="content">
|
||||
${next.body()}
|
||||
</div>
|
||||
<%include file="/sidebar.mako"/>
|
||||
<div style="clear: both;"> </div>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="footer">
|
||||
<p>Copyright © 2010 Raoul and Hannah Snyman. Powered by ScribeEngine. Design by <a href="http://www.freecsstemplates.org/">Free CSS Templates</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
22
themes/stargazer/templates/blog/archive.mako
Normal file
@ -0,0 +1,22 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<h2 class="title">${c.page_title}</h2>
|
||||
% for post in c.posts:
|
||||
<% post.full_url = u'/archive/%s/%s/%s/%s' % (post.created.strftime('%Y'), post.created.strftime('%m'), post.created.strftime('%d'), post.url) %>
|
||||
<div class="post">
|
||||
<h2 class="title"><a href="${post.full_url}">${post.title}</a></h2>
|
||||
<div class="entry">
|
||||
${h.literal(h.teaser(post.body, post.full_url))}
|
||||
</div>
|
||||
<p class="meta">
|
||||
<span class="byline">Posted by ${post.user.nick} on ${post.created.strftime('%B %d, %Y')}</span>
|
||||
<a href="${post.full_url}" class="read-more">Read more</a>
|
||||
% if len(post.comments) == 0:
|
||||
<a href="${post.full_url}#comments" class="comments">No comments</a>
|
||||
% elif len(post.comments) == 1:
|
||||
<a href="${post.full_url}#comments" class="comments">1 comment</a>
|
||||
% else:
|
||||
<a href="${post.full_url}#comments" class="comments">${len(post.comments)} comments</a>
|
||||
% endif
|
||||
</p>
|
||||
</div>
|
||||
% endfor
|
21
themes/stargazer/templates/blog/index.mako
Normal file
@ -0,0 +1,21 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
% for post in c.posts:
|
||||
<% post.full_url = u'/archive/%s/%s/%s/%s' % (post.created.strftime('%Y'), post.created.strftime('%m'), post.created.strftime('%d'), post.url) %>
|
||||
<div class="post">
|
||||
<h2 class="title"><a href="${post.full_url}">${post.title}</a></h2>
|
||||
<div class="entry">
|
||||
${h.literal(h.teaser(post.body, post.full_url))}
|
||||
</div>
|
||||
<p class="meta">
|
||||
<span class="byline">Posted by ${post.user.nick} on ${post.created.strftime('%B %d, %Y')}</span>
|
||||
<a href="${post.full_url}" class="read-more">Read more</a>
|
||||
% if len(post.comments) == 0:
|
||||
<a href="${post.full_url}#comments" class="comments">No comments</a>
|
||||
% elif len(post.comments) == 1:
|
||||
<a href="${post.full_url}#comments" class="comments">1 comment</a>
|
||||
% else:
|
||||
<a href="${post.full_url}#comments" class="comments">${len(post.comments)} comments</a>
|
||||
% endif
|
||||
</p>
|
||||
</div>
|
||||
% endfor
|
7
themes/stargazer/templates/blog/teaser.mako
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="post">
|
||||
<h2 class="title"><a href="${h.url_for(year=post.created.strftime('%Y'), month=post.created.strftime('%m'), day=post.created.strftime('%d'), url=post.url)}">${post.title}</a></h2>
|
||||
<div class="entry">
|
||||
${h.literal(post.body)}
|
||||
</div>
|
||||
<p class="meta"><span class="byline">Posted by ${post.user.first_name} on ${post.created.strftime('%B %d, %Y')}</span> <a href="${h.url_for(year=post.created.strftime('%Y'), month=post.created.strftime('%m'), day=post.created.strftime('%d'), url=post.url)}#comments" class="comments">18 comments</a></p>
|
||||
</div>
|
48
themes/stargazer/templates/blog/view.mako
Normal file
@ -0,0 +1,48 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<div class="post">
|
||||
<h2 class="title"><a href="${h.url_for(year=c.post.created.strftime('%Y'), month=c.post.created.strftime('%m'), day=c.post.created.strftime('%d'), url=c.post.url)}">${c.post.title}</a></h2>
|
||||
<div class="info">Posted by ${c.post.user.nick} on ${c.post.created.strftime('%B %d, %Y')}</div>
|
||||
<div class="entry">
|
||||
${h.literal(c.post.body)}
|
||||
</div>
|
||||
<div> </div>
|
||||
% if len(c.post.comments) == 0:
|
||||
<h3 id="comments">No Responses</h3>
|
||||
<p> </p>
|
||||
% elif len(c.post.comments) == 1:
|
||||
<h3 id="comments">One Response</h3>
|
||||
% else:
|
||||
<h3 id="comments">${len(c.post.comments)} Responses</h3>
|
||||
% endif
|
||||
% if len(c.post.comments) > 0:
|
||||
<ol class="commentlist">
|
||||
% for num, comment in enumerate(c.post.comments):
|
||||
<li id="comment-${comment.id}">
|
||||
<cite>${comment.user.nick}</cite> Says:<br />
|
||||
<small class="commentmetadata"><a href="#comment-<?php comment_ID() ?>" title="">${comment.created.strftime('%B %d, %Y')} at ${comment.created.strftime('%H:%M')}</a> <a href="edit">edit</a></small>
|
||||
${comment.body}
|
||||
</li>
|
||||
% endfor
|
||||
</ol>
|
||||
% else:
|
||||
% if c.post.comment_status != u'open':
|
||||
<p class="nocomments">Comments are closed.</p>
|
||||
% endif
|
||||
% endif
|
||||
% if c.post.comment_status == u'open':
|
||||
<h3 id="respond">Leave a Reply</h3>
|
||||
% if not c.current_user:
|
||||
<p>You must be <a href="/login">logged in</a> to post a comment.</p>
|
||||
% else:
|
||||
<div> </div>
|
||||
<form action="${h.url_for('/comment/edit')}" method="post" id="commentform">
|
||||
<p>Logged in as <em>${c.current_user.nick}</em>. <a href="/logout" title="Log out of this account">Logout »</a></p>
|
||||
<p><textarea name="comment" id="comment" cols="80" rows="10" tabindex="4" style="width: 605px;"></textarea></p>
|
||||
<p>
|
||||
<input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" />
|
||||
<input type="hidden" name="post_id" value="${c.post.id}" />
|
||||
</p>
|
||||
</form>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
38
themes/stargazer/templates/calendar.mako
Normal file
@ -0,0 +1,38 @@
|
||||
<table summary="Calendar">
|
||||
<caption>
|
||||
${c.thismonth.strftime('%B %Y')}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th abbr="Sunday" scope="col" title="Sunday">S</th>
|
||||
<th abbr="Monday" scope="col" title="Monday">M</th>
|
||||
<th abbr="Tuesday" scope="col" title="Tuesday">T</th>
|
||||
<th abbr="Wednesday" scope="col" title="Wednesday">W</th>
|
||||
<th abbr="Thursday" scope="col" title="Thursday">T</th>
|
||||
<th abbr="Friday" scope="col" title="Friday">F</th>
|
||||
<th abbr="Saturday" scope="col" title="Saturday">S</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td abbr="October" colspan="3" id="prev"><a href="#" title="View posts for October 2007">« Oct</a></td>
|
||||
<td class="pad"> </td>
|
||||
<td abbr="December" colspan="3" id="next"><a href="#" title="View posts for October 2007">Dec »</a></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
% for week in c.calendar.monthdays2calendar(c.thismonth.year, c.thismonth.month):
|
||||
<tr>
|
||||
% for day, weekday in week:
|
||||
% if day == 0:
|
||||
<td class="pad"> </td>
|
||||
% elif day == c.today.day:
|
||||
<td id="today">${day}</td>
|
||||
% else:
|
||||
<td>${day}</td>
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
20
themes/stargazer/templates/post/new.mako
Normal file
@ -0,0 +1,20 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<div class="post">
|
||||
<h2 class="title">New Post</h2>
|
||||
<form id="post-new" action="${h.url_for('/post/edit')}" method="post">
|
||||
<fieldset>
|
||||
<div class="form-item">
|
||||
<!-- <label for="post-title">Title:</label> -->
|
||||
<input type="text" name="title" id="post-title" class="form-text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<!-- <label for="post-body">Body:</label> -->
|
||||
<textarea name="body" id="post-body" class="form-textarea"></textarea>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<input type="submit" name="action" value="Save Draft"/>
|
||||
<input type="submit" name="action" value="Save & Publish"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
30
themes/stargazer/templates/sidebar.mako
Normal file
@ -0,0 +1,30 @@
|
||||
<div id="sidebar">
|
||||
<ul>
|
||||
<li id="search">
|
||||
<form id="searchform" method="get" action="/search">
|
||||
<div>
|
||||
<input type="text" name="keywords" id="s" size="15" />
|
||||
<br />
|
||||
<input name="submit" type="submit" value="Search" />
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
<li id="calendar">
|
||||
<h2>Calendar</h2>
|
||||
<div id="calendar_wrap">
|
||||
<%include file="/calendar.mako"/>
|
||||
</div>
|
||||
</li>
|
||||
% if len(c.categories) > 0:
|
||||
<li>
|
||||
<h2>Categories</h2>
|
||||
<ul>
|
||||
% for category in c.categories:
|
||||
<li><a href="${h.url_for('/category/%s' % str(category.url))}" title="${category.name}">${category.name}</a> (${len(category.posts)}) </li>
|
||||
% endfor
|
||||
</ul>
|
||||
</li>
|
||||
% endif
|
||||
</ul>
|
||||
<div style="clear: both; height: 40px;"> </div>
|
||||
</div>
|