- 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.salt = secretsalt
# Mail server settings
mail.on = false
mail.manager = immediate
mail.transport = smtp
@ -49,6 +50,9 @@ mail.smtp.server = mail.mydomain.com
mail.smtp.username = mymailusername
mail.smtp.password = mymailpassword
# Server-related settings
server.timezone = 'Africa/Johannesburg'
# 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.

View File

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

View File

@ -27,6 +27,7 @@ from urlparse import urlsplit
from datetime import datetime
from formencode.validators import Int
from pytz import all_timezones
from scribeengine.lib.base import *
from scribeengine.lib.validation.client import JSString, JSEmail
@ -39,26 +40,94 @@ log = logging.getLogger(__name__)
class AccountController(BaseController):
@authenticate()
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):
c.page_title = u'Register'
return render(u'/account/register.mako')
@jsvalidate(u'register-form')
@jsvalidate(u'account-register')
def register_jsschema(self):
return {
u'register-email': JSEmail(required=True, message=u'You haven\'t typed in an e-mail address.'),
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.')
u'register-email': JSEmail(required=True,
message=u'You haven\'t typed in an e-mail address.'),
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):
return {
'register-email': Email(not_empty=True, messages={'empty': u'You haven\'t typed in an e-mail address.'}),
'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.'})]
'register-email': Email(not_empty=True,
messages={'empty': u'You haven\'t typed in an e-mail address.'}),
'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):
@ -168,7 +237,10 @@ class AccountController(BaseController):
u'e-mail.', u'success')
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'):
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'))
@ -179,33 +251,35 @@ class AccountController(BaseController):
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.redirect_to(h.url_for(controller=u'account', action=u'login'))
c.page_title = u'Change Password'
return render(u'/account/password.mako')
c.page_title = u'Reset Password'
return render(u'/account/resetpassword.mako')
@jsvalidate(u'account-password')
def password_jsschema(self):
@jsvalidate(u'account-resetpassword')
def resetpassword_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):
def resetpassword_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.'})]
}
def password_POST(self, id=None):
def resetpassword_POST(self, id=None):
user = Session.query(User).get(id)
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'))
user.password = utils.hash_password(c.form_values[u'password-password'])
user.activation_key = None
user.modified = datetime.now()
Session.add(user)
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')
def login(self):

View File

@ -25,6 +25,7 @@ from datetime import datetime
from pprint import pformat
from sqlalchemy.sql import or_
from pytz import timezone
from scribeengine.lib.base import *
from scribeengine.lib import utils
@ -170,7 +171,10 @@ class BlogController(BaseController):
def calendar(self, year, month):
#c.calendar = Calendar(6)
#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.next_month = c.thismonth + monthdelta(1)
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 formencode import Schema, Invalid
from monthdelta import monthdelta
from pytz import timezone
from scribeengine.lib import helpers as h
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.pages = Session.query(Page).all()
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:
c.thismonth = datetime.now()
c.thismonth = c.today #datetime.utcnow().astimezone(timezone(c.current_user.timezone))
c.prev_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)

View File

@ -108,6 +108,12 @@ class User(BaseModel):
if perm == permission:
return True
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:
return False

View File

@ -103,6 +103,7 @@ users_table = Table(u'users', metadata,
Column(u'first_name', Unicode(100), default=u''),
Column(u'last_name', Unicode(100), 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)
)

View File

@ -393,6 +393,19 @@ fieldset {
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 {
font-size: 1.5em;
padding: 4px 6px;
@ -423,6 +436,22 @@ fieldset {
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 {
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"/>
<%include file="/flash.mako"/>
<div class="post">
<h2 class="title">New password</h2>
<h2 class="title">Change password</h2>
<%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>
<div class="form-item">
<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>
<li><a href="${h.url_for('/')}">Home</a></li>
% for page in c.pages:
<li><a href="${page.url}">${page.name}</a></li>
<li><a href="${page.url}">${page.title}</a></li>
% endfor
% 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>
% else:
<li><a href="${h.url_for(controller=u'account', action=u'login')}">Login</a></li>
% endif
</ul>
</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
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,

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>
</li>
% 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>
<div style="clear: both; height: 40px;">&nbsp;</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:
from setuptools import setup, find_packages
except ImportError:
@ -28,7 +29,8 @@ setup(
"Pylons==0.9.7",
"SQLAlchemy>=0.5,<0.6",
"TurboMail>=3.0,<3.1",
"MonthDelta>=0.9,<0.10"
"MonthDelta>=0.9,<0.10",
"pytz"
],
setup_requires=["PasteScript>=1.6.3"],
packages=find_packages(exclude=['ez_setup']),