- Added a menu panel in the sidebar

- Added an account page
- Added a timezone
- Added a change password page
- Various other bugfixes and tweaks
This commit is contained in:
Raoul Snyman 2010-04-08 07:46:14 +02:00
commit ca205d287f
19 changed files with 364 additions and 32 deletions

View File

@ -42,6 +42,7 @@ paths.themes = %(here)s/themes
# Security settings # Security settings
security.salt = secretsalt security.salt = secretsalt
# Mail server settings
mail.on = false mail.on = false
mail.manager = immediate mail.manager = immediate
mail.transport = smtp mail.transport = smtp
@ -49,6 +50,9 @@ mail.smtp.server = mail.mydomain.com
mail.smtp.username = mymailusername mail.smtp.username = mymailusername
mail.smtp.password = mymailpassword mail.smtp.password = mymailpassword
# Server-related settings
server.timezone = 'Africa/Johannesburg'
# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
# Debug mode will enable the interactive debugging tool, allowing ANYONE to # Debug mode will enable the interactive debugging tool, allowing ANYONE to
# execute malicious code after an exception is raised. # execute malicious code after an exception is raised.

View File

@ -51,9 +51,12 @@ def make_map():
map.connect('/tag/{id}', controller='blog', action='tag') map.connect('/tag/{id}', controller='blog', action='tag')
map.connect('/calendar/{year}/{month}', controller='blog', action='calendar') map.connect('/calendar/{year}/{month}', controller='blog', action='calendar')
map.connect('/{controller}')
map.connect('/{controller}/{action}') map.connect('/{controller}/{action}')
map.connect('/{controller}/{action}/{id}') map.connect('/{controller}/{action}/{id}')
map.connect('/{url}', controller='page', action='view')
map.connect('/', controller='blog', action='index') map.connect('/', controller='blog', action='index')
return map return map

View File

@ -27,6 +27,7 @@ from urlparse import urlsplit
from datetime import datetime from datetime import datetime
from formencode.validators import Int from formencode.validators import Int
from pytz import all_timezones
from scribeengine.lib.base import * from scribeengine.lib.base import *
from scribeengine.lib.validation.client import JSString, JSEmail from scribeengine.lib.validation.client import JSString, JSEmail
@ -39,26 +40,94 @@ log = logging.getLogger(__name__)
class AccountController(BaseController): class AccountController(BaseController):
@authenticate()
def index(self): def index(self):
h.redirect_to(h.url_for(controller=u'account', action=u'login')) c.page_title = u'My Account'
c.timezones = all_timezones
return render(u'/account/index.mako')
@jsvalidate(u'account-account')
def index_jsschema(self):
return {
u'account-nick': JSString(required=True,
message=u'You need to type in a nick.'),
u'account-email': JSEmail(required=True,
message=u'You need to supply a valid e-mail address.')
}
def index_schema(self):
return {
'account-nick': UnicodeString(not_empty=True,
messages={u'empty': u'You need to type in a nick.'}),
'account-email': Email(not_empty=True,
messages={u'empty': u'You need to supply a valid e-mail address.'})
}
def index_POST(self):
try:
for key, value in c.form_values.iteritems():
setattr(c.current_user, key[8:], value)
Session.add(c.current_user)
Session.commit()
h.flash.set_message(u'Successfully updated your account.', u'success')
except:
h.flash.set_message(u'There was a problem updating your account.', u'error')
h.redirect_to(h.url_for(controller=u'account'))
@authenticate()
def password(self):
c.page_title = u'Your Password'
return render(u'/account/password.mako')
@jsvalidate(u'account-password')
def password_jsschema(self):
return {
u'password-password': JSString(required=True, message=u'You haven\'t typed in a password.'),
u'password-confirm': JSString(required=True, equalTo=u'#password-password', message=u'Your passwords don\'t match.')
}
def password_schema(self):
return {
'password-password': UnicodeString(not_empty=True, messages={'empty': u'You haven\'t typed in a password.'}),
'confirm-password': [FieldsMatch('password-password', 'password-confirm', messages={'invalid': u'Your passwords don\'t match.'})]
}
@authenticate()
def password_POST(self):
password_hash = utils.hash_password(c.form_values[u'password-password'])
log.debug('Old Hash: "%s"', c.current_user.password)
log.debug('New Hash: "%s"', password_hash)
c.current_user.password = password_hash
c.current_user.modified = datetime.now()
Session.add(c.current_user)
Session.commit()
h.flash.set_message(u'Successfully updated your password.', u'success')
h.redirect_to('/account/password')
def register(self): def register(self):
c.page_title = u'Register' c.page_title = u'Register'
return render(u'/account/register.mako') return render(u'/account/register.mako')
@jsvalidate(u'register-form') @jsvalidate(u'account-register')
def register_jsschema(self): def register_jsschema(self):
return { return {
u'register-email': JSEmail(required=True, message=u'You haven\'t typed in an e-mail address.'), u'register-email': JSEmail(required=True,
u'register-password': JSString(required=True, message=u'You haven\'t typed in a password.'), message=u'You haven\'t typed in an e-mail address.'),
u'register-confirm': JSString(required=True, equalTo=u'#password', message=u'Your passwords don\'t match.') u'register-password': JSString(required=True,
message=u'You haven\'t typed in a password.'),
u'register-confirm': JSString(required=True, equalTo=u'#password',
message=u'Your passwords don\'t match.')
} }
def register_schema(self): def register_schema(self):
return { return {
'register-email': Email(not_empty=True, messages={'empty': u'You haven\'t typed in an e-mail address.'}), 'register-email': Email(not_empty=True,
'register-password': UnicodeString(not_empty=True, messages={'empty': u'You haven\'t typed in a password.'}), messages={'empty': u'You haven\'t typed in an e-mail address.'}),
'confirm-password': [FieldsMatch('register-password', 'register-confirm', messages={'invalid': u'Your passwords don\'t match.'})] 'register-password': UnicodeString(not_empty=True,
messages={'empty': u'You haven\'t typed in a password.'}),
'confirm-password': [
FieldsMatch('register-password', 'register-confirm',
messages={'invalid': u'Your passwords don\'t match.'})]
} }
def register_POST(self): def register_POST(self):
@ -168,7 +237,10 @@ class AccountController(BaseController):
u'e-mail.', u'success') u'e-mail.', u'success')
h.redirect_to('/account/login') h.redirect_to('/account/login')
def password(self, id=None): def resetpassword(self, id=None):
"""
Reset your password.
"""
if not id or not request.GET.get(u'code'): if not id or not request.GET.get(u'code'):
h.flash.set_message(u'There was a problem with your activation code, please reset your password again.', u'error') h.flash.set_message(u'There was a problem with your activation code, please reset your password again.', u'error')
h.redirect_to(h.url_for(controller=u'account', action=u'login')) h.redirect_to(h.url_for(controller=u'account', action=u'login'))
@ -179,33 +251,35 @@ class AccountController(BaseController):
if c.user.activation_key != request.GET.get(u'code'): if c.user.activation_key != request.GET.get(u'code'):
h.flash.set_message(u'There was a problem with your activation code, please reset your password again.', u'error') h.flash.set_message(u'There was a problem with your activation code, please reset your password again.', u'error')
h.redirect_to(h.url_for(controller=u'account', action=u'login')) h.redirect_to(h.url_for(controller=u'account', action=u'login'))
c.page_title = u'Change Password' c.page_title = u'Reset Password'
return render(u'/account/password.mako') return render(u'/account/resetpassword.mako')
@jsvalidate(u'account-password') @jsvalidate(u'account-resetpassword')
def password_jsschema(self): def resetpassword_jsschema(self):
return { return {
u'password-password': JSString(required=True, message=u'You haven\'t typed in a password.'), u'password-password': JSString(required=True, message=u'You haven\'t typed in a password.'),
u'password-confirm': JSString(required=True, equalTo=u'#password-password', message=u'Your passwords don\'t match.') u'password-confirm': JSString(required=True, equalTo=u'#password-password', message=u'Your passwords don\'t match.')
} }
def password_schema(self): def resetpassword_schema(self):
return { return {
'password-password': UnicodeString(not_empty=True, messages={'empty': u'You haven\'t typed in a password.'}), 'password-password': UnicodeString(not_empty=True, messages={'empty': u'You haven\'t typed in a password.'}),
'confirm-password': [FieldsMatch('password-password', 'password-confirm', messages={'invalid': u'Your passwords don\'t match.'})] 'confirm-password': [FieldsMatch('password-password', 'password-confirm', messages={'invalid': u'Your passwords don\'t match.'})]
} }
def password_POST(self, id=None): def resetpassword_POST(self, id=None):
user = Session.query(User).get(id) user = Session.query(User).get(id)
if not user: if not user:
h.flash.set_message(u'There was a problem with your account, please reset your password again.', u'error') h.flash.set_message(u'There was a problem with your account, '
u'please reset your password again.', u'error')
h.redirect_to(h.url_for(controller=u'account', action=u'login')) h.redirect_to(h.url_for(controller=u'account', action=u'login'))
user.password = utils.hash_password(c.form_values[u'password-password']) user.password = utils.hash_password(c.form_values[u'password-password'])
user.activation_key = None user.activation_key = None
user.modified = datetime.now() user.modified = datetime.now()
Session.add(user) Session.add(user)
Session.commit() Session.commit()
h.flash.set_message(u'Successfully updated your password. Please login with your new password.', u'success') h.flash.set_message(u'Successfully updated your password. Please login '
u'with your new password.', u'success')
h.redirect_to('/account/login') h.redirect_to('/account/login')
def login(self): def login(self):

View File

@ -25,6 +25,7 @@ from datetime import datetime
from pprint import pformat from pprint import pformat
from sqlalchemy.sql import or_ from sqlalchemy.sql import or_
from pytz import timezone
from scribeengine.lib.base import * from scribeengine.lib.base import *
from scribeengine.lib import utils from scribeengine.lib import utils
@ -170,7 +171,10 @@ class BlogController(BaseController):
def calendar(self, year, month): def calendar(self, year, month):
#c.calendar = Calendar(6) #c.calendar = Calendar(6)
#c.today = datetime.today() #c.today = datetime.today()
c.thismonth = datetime.now().replace(int(year), int(month)) server_tz = timezone(config.get(u'server.timezone', u'UTC'))
user_tz = c.current_user.timezone if c.current_user and c.current_user.timezone else u'UTC'
now = datetime.now(server_tz).astimezone(timezone(user_tz))
c.thismonth = now.replace(int(year), int(month))
c.prev_month = c.thismonth - monthdelta(1) c.prev_month = c.thismonth - monthdelta(1)
c.next_month = c.thismonth + monthdelta(1) c.next_month = c.thismonth + monthdelta(1)
month_start = datetime(c.thismonth.year, c.thismonth.month, 1, 0, 0, 0, 0) month_start = datetime(c.thismonth.year, c.thismonth.month, 1, 0, 0, 0, 0)

View File

@ -0,0 +1,75 @@
# -*- 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
import time
from scribeengine.lib.base import *
from scribeengine.lib import utils
from scribeengine.model import Page
from scribeengine.model.meta import Session
log = logging.getLogger(__name__)
class PageController(BaseController):
def index(self):
h.redirect_to('/')
def view(self, url):
c.page = Session.query(Page)\
.filter_by(url=url)\
.first()
#.filter_by(status=u'published')\
c.page_title = c.page.title
return render(u'/page/view.mako')
@authenticate(u'Add Pages')
def new(self):
c.page_title = u'New Page'
return render(u'/page/new.mako')
@authenticate(u'Edit My Pages')
def edit(self, id=None):
if id is None:
h.redirect_to(h.url_for(controller=u'page', action=u'new'))
c.page = Session.query(Page).get(id)
c.page_title = u'Edit Page: %s' % c.page.title
return render(u'/post/edit.mako')
@authenticate(u'Edit My Pages')
def edit_POST(self, id=None):
url = utils.generate_url(c.form_values[u'page-title'])
if id is None:
page = Page()
page.user = c.current_user
page.created = datetime.now()
else:
page = Session.query(Page).get(id)
page.modified = datetime.now()
page.title = c.form_values[u'page-title']
page.body = c.form_values[u'page-body']
page.url = url
Session.add(page)
Session.commit()
h.redirect_to('/' + str(page.url))

View File

@ -39,6 +39,7 @@ from pylons.templating import render_mako
from sqlalchemy.sql.expression import asc, desc from sqlalchemy.sql.expression import asc, desc
from formencode import Schema, Invalid from formencode import Schema, Invalid
from monthdelta import monthdelta from monthdelta import monthdelta
from pytz import timezone
from scribeengine.lib import helpers as h from scribeengine.lib import helpers as h
from scribeengine.lib.validation import jsvalidate from scribeengine.lib.validation import jsvalidate
@ -57,9 +58,11 @@ class BaseController(WSGIController):
c.categories = Session.query(Category).order_by(Category.name.asc()).all() c.categories = Session.query(Category).order_by(Category.name.asc()).all()
c.pages = Session.query(Page).all() c.pages = Session.query(Page).all()
c.calendar = Calendar(6) c.calendar = Calendar(6)
c.today = datetime.today() server_tz = timezone(config.get(u'server.timezone', u'UTC'))
user_tz = c.current_user.timezone if c.current_user and c.current_user.timezone else u'UTC'
c.today = datetime.now(server_tz).astimezone(timezone(user_tz))
if not c.thismonth: if not c.thismonth:
c.thismonth = datetime.now() c.thismonth = c.today #datetime.utcnow().astimezone(timezone(c.current_user.timezone))
c.prev_month = c.thismonth - monthdelta(1) c.prev_month = c.thismonth - monthdelta(1)
c.next_month = c.thismonth + monthdelta(1) c.next_month = c.thismonth + monthdelta(1)
month_start = datetime(c.thismonth.year, c.thismonth.month, 1, 0, 0, 0, 0) month_start = datetime(c.thismonth.year, c.thismonth.month, 1, 0, 0, 0, 0)

View File

@ -108,6 +108,12 @@ class User(BaseModel):
if perm == permission: if perm == permission:
return True return True
return False return False
elif isinstance(permission, list):
for role in self.roles:
for perm in role.permissions:
if perm.name in permission:
return True
return False
else: else:
return False return False

View File

@ -103,6 +103,7 @@ users_table = Table(u'users', metadata,
Column(u'first_name', Unicode(100), default=u''), Column(u'first_name', Unicode(100), default=u''),
Column(u'last_name', Unicode(100), default=u''), Column(u'last_name', Unicode(100), default=u''),
Column(u'homepage', Unicode(200), default=u''), Column(u'homepage', Unicode(200), default=u''),
Column(u'timezone', Unicode(200), default=u'UTC'),
Column(u'activation_key', Unicode(40), default=None) Column(u'activation_key', Unicode(40), default=None)
) )

View File

@ -393,6 +393,19 @@ fieldset {
color: #fff; color: #fff;
} }
.form-select {
color: #fff;
background-color: #1f1f1f;
font-size: 1.2em;
border: 1px solid #454545;
padding: 3px 5px;
}
.form-select option {
background-color: #1f1f1f;
color: #fff;
}
.form-text { .form-text {
font-size: 1.5em; font-size: 1.5em;
padding: 4px 6px; padding: 4px 6px;
@ -423,6 +436,22 @@ fieldset {
margin-top: 0.2em; margin-top: 0.2em;
} }
fieldset.form-details {
background-color: #111;
margin: 4em 0 1.5em;
padding: 10px;
}
fieldset.form-details legend {
color: #fff;
font-size: 1.5em;
margin: -1.7em 0 0 0;
}
fieldset.form-details .form-text {
width: 573px;
}
#register-now { #register-now {
margin-left: 1em; margin-left: 1em;
} }

View File

@ -0,0 +1,48 @@
<%inherit file="/base.mako"/>
<%include file="/flash.mako"/>
<div class="post">
<h2 class="title">My Account</h2>
<p>This is your account page. Update your details by changing them below and clicking the Save button.</p>
<%include file="/errors.mako"/>
<form id="account-account" action="${h.url_for(controller=u'account', action=u'index')}" method="post">
<fieldset class="form-details">
<legend>Required Details</legend>
<div class="form-item">
<label for="account-nick">Nick:</label>
<input type="text" name="account-nick" id="account-nick" class="form-text" value="${c.current_user.nick}" />
</div>
<div class="form-item">
<label for="account-email">E-mail:</label>
<input type="text" name="account-email" id="account-email" class="form-text" value="${c.current_user.email}" />
</div>
</fieldset>
<fieldset class="form-details">
<legend>Personal Details</legend>
<div class="form-item">
<label for="account-first_name">First Name:</label>
<input type="text" name="account-first_name" id="account-first_name" class="form-text" value="${c.current_user.first_name}" />
</div>
<div class="form-item">
<label for="account-last_name">Last Name:</label>
<input type="text" name="account-last_name" id="account-last_name" class="form-text" value="${c.current_user.last_name}" />
</div>
<div class="form-item">
<label for="account-timezone">Timezone:</label>
<select name="account-timezone" id="account-timezone" class="form-select">
% for timezone in c.timezones:
% if c.current_user.timezone == timezone:
<option value="${timezone}" selected="selected">${timezone}</option>
% else:
<option value="${timezone}">${timezone}</option>
% endif
% endfor
</select>
</div>
</fieldset>
<fieldset>
<div class="form-item">
<input type="submit" name="account-action" value="Save" class="form-button"/>
</div>
</fieldset>
</form>
</div>

View File

@ -1,9 +1,9 @@
<%inherit file="/base.mako"/> <%inherit file="/base.mako"/>
<%include file="/flash.mako"/> <%include file="/flash.mako"/>
<div class="post"> <div class="post">
<h2 class="title">New password</h2> <h2 class="title">Change password</h2>
<%include file="/errors.mako"/> <%include file="/errors.mako"/>
<form id="account-password" action="${h.url_for(controller=u'account', action=u'password', id=c.user.id)}" method="post"> <form id="account-password" action="${h.url_for(controller=u'account', action=u'password')}" method="post">
<fieldset> <fieldset>
<div class="form-item"> <div class="form-item">
<label for="password-password">Password:</label> <label for="password-password">Password:</label>

View File

@ -0,0 +1,21 @@
<%inherit file="/base.mako"/>
<%include file="/flash.mako"/>
<div class="post">
<h2 class="title">Reset password</h2>
<%include file="/errors.mako"/>
<form id="account-resetpassword" action="${h.url_for(controller=u'account', action=u'resetpassword', id=c.user.id)}" method="post">
<fieldset>
<div class="form-item">
<label for="password-password">Password:</label>
<input type="password" name="password-password" id="password-password" class="form-text" />
</div>
<div class="form-item">
<label for="password-confirm">Confirm password:</label>
<input type="password" name="password-confirm" id="password-confirm" class="form-text" />
</div>
<div class="form-item">
<input type="submit" name="password-action" value="Change password" class="form-button"/>
</div>
</fieldset>
</form>
</div>

View File

@ -29,17 +29,10 @@
<ul> <ul>
<li><a href="${h.url_for('/')}">Home</a></li> <li><a href="${h.url_for('/')}">Home</a></li>
% for page in c.pages: % for page in c.pages:
<li><a href="${page.url}">${page.name}</a></li> <li><a href="${page.url}">${page.title}</a></li>
% endfor % endfor
% if c.current_user: % if c.current_user:
% if c.current_user.has_permission('Add Posts'):
<li><a href="${h.url_for(controller=u'post', action=u'new')}">New Post</a></li>
<li><a href="${h.url_for(controller=u'post', action=u'draft')}">Draft Posts</a></li>
% endif
<li><a href="${h.url_for(controller=u'account', action=u'logout')}">Logout</a></li>
<li>Logged in as <em>${c.current_user.nick}</em></li> <li>Logged in as <em>${c.current_user.nick}</em></li>
% else:
<li><a href="${h.url_for(controller=u'account', action=u'login')}">Login</a></li>
% endif % endif
</ul> </ul>
</div> </div>

View File

@ -4,7 +4,7 @@ You have just reset your password on ${c.blog_title}, but before you continue,
you will need to activate your account. You can do this by simply clicking on you will need to activate your account. You can do this by simply clicking on
the link below, or copying and pasting it into your browser. the link below, or copying and pasting it into your browser.
${c.blog_host}${h.url_for(controller=u'account', action=u'password', id=c.user.id, code=c.user.activation_key)} ${c.blog_host}${h.url_for(controller=u'account', action=u'resetpassword', id=c.user.id, code=c.user.activation_key)}
Kind regards, Kind regards,

View File

@ -0,0 +1,21 @@
<%inherit file="/base.mako"/>
<%include file="/flash.mako"/>
<div class="page">
<h2 class="title">New Page</h2>
<%include file="/errors.mako"/>
<form id="page-new" action="${h.url_for('/page/edit')}" method="post">
<fieldset>
<div class="form-item">
<!-- <label for="page-title">Title:</label> -->
<input type="text" name="page-title" id="page-title" class="form-text" />
</div>
<div class="form-item">
<!-- <label for="page-body">Body:</label> -->
<textarea name="page-body" id="page-body" class="form-textarea"></textarea>
</div>
<div class="form-item">
<input type="submit" name="page-action" value="Save" class="form-button"/>
</div>
</fieldset>
</form>
</div>

View File

@ -0,0 +1,8 @@
<%inherit file="/base.mako"/>
<%include file="/flash.mako"/>
<div class="page">
<h2 class="title">${c.page.title}</h2>
<div class="entry">
${h.literal(c.page.body)}
</div>
</div>

View File

@ -25,6 +25,39 @@
</ul> </ul>
</li> </li>
% endif % endif
<li>
<h2>Meta</h2>
<ul>
% if c.current_user:
<li>
Account
<ul>
<li><a href="${h.url_for(controller=u'account')}">My Details</a></li>
<li><a href="${h.url_for(controller=u'account', action=u'password')}">My Password</a></li>
</ul>
</li>
% if c.current_user.has_permission([u'Add Posts', u'Add Pages', u'Edit My Posts', u'Edit My Pages']):
<li>
Content
<ul>
% if c.current_user.has_permission(u'Add Posts'):
<li><a href="${h.url_for(controller=u'post', action=u'new')}">New Post</a></li>
% endif
% if c.current_user.has_permission(u'Add Pages'):
<li><a href="${h.url_for(controller=u'page', action=u'new')}">New Page</a></li>
% endif
% if c.current_user.has_permission(u'Edit My Posts'):
<li><a href="${h.url_for(controller=u'post', action=u'draft')}">Draft Posts</a></li>
% endif
</ul>
</li>
% endif
<li><a href="${h.url_for(controller=u'account', action=u'logout')}">Logout</a></li>
% else:
<li><a href="${h.url_for(controller=u'account', action=u'login')}">Login</a></li>
% endif
</ul>
</li>
</ul> </ul>
<div style="clear: both; height: 40px;">&nbsp;</div> <div style="clear: both; height: 40px;">&nbsp;</div>
</div> </div>

View File

@ -0,0 +1,7 @@
from scribeengine.tests import *
class TestPageController(TestController):
def test_index(self):
response = self.app.get(url(controller='page', action='index'))
# Test response...

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
try: try:
from setuptools import setup, find_packages from setuptools import setup, find_packages
except ImportError: except ImportError:
@ -28,7 +29,8 @@ setup(
"Pylons==0.9.7", "Pylons==0.9.7",
"SQLAlchemy>=0.5,<0.6", "SQLAlchemy>=0.5,<0.6",
"TurboMail>=3.0,<3.1", "TurboMail>=3.0,<3.1",
"MonthDelta>=0.9,<0.10" "MonthDelta>=0.9,<0.10",
"pytz"
], ],
setup_requires=["PasteScript>=1.6.3"], setup_requires=["PasteScript>=1.6.3"],
packages=find_packages(exclude=['ez_setup']), packages=find_packages(exclude=['ez_setup']),