Commits

Chris Miles committed 9c20da2

Replaced the chronr package with a fresh build based on Pylons using the BlastOff template. This is still a skeleton app and doesn't have all the chronr controllers, model, templates or logic yet.

Comments (0)

Files changed (120)

+syntax: glob
+
+*.pyc
+chronr.egg-info/
+build/
+dist/

chronr/MANIFEST.in

+include chronr/config/deployment.ini_tmpl
+recursive-include chronr/public *
+recursive-include chronr/templates *

chronr/README.txt

-chronr
-======
+This file is for you to describe the chronr application. Typically
+you would include information such as the information below:
 
-This is a TurboGears (http://www.turbogears.org) project. It can be
-started by running the start-chronr.py script.
+Installation and Setup
+======================
 
+Install ``chronr`` using easy_install::
 
-Unit Tests
-----------
+    easy_install {egg}
 
-Run all the unit tests with::
+Make a config file as follows::
 
-  $ python setup.py test
+    paster make-config chronr config.ini
+
+Tweak the config file as appropriate and then setup the application::
+
+    paster setup-app config.ini
+
+Then you are ready to go.

chronr/chronr/commands.py

-# -*- coding: UTF-8 -*-
-"""This module contains functions called from console script entry points."""
-
-import os
-import sys
-
-from os.path import dirname, exists, join
-
-import pkg_resources
-pkg_resources.require("TurboGears")
-
-import turbogears
-import cherrypy
-
-from release import version
-
-import tg_pri_flash.flash   # replace TG flash with TGPriFlash
-
-cherrypy.lowercase_api = True
-
-class ConfigurationError(Exception):
-    pass
-
-def start():
-    """Start the CherryPy application server."""
-
-    setupdir = dirname(dirname(__file__))
-    curdir = os.getcwd()
-
-    # First look on the command line for a desired config file,
-    # if it's not on the command line, then look for 'setup.py'
-    # in the current directory. If there, load configuration
-    # from a file called 'dev.cfg'. If it's not there, the project 
-    # is probably installed and we'll look first for a file called
-    # 'prod.cfg' in the current directory and then for a default
-    # config file called 'default.cfg' packaged in the egg.
-    if len(sys.argv) > 1:
-        configfile = sys.argv[1]
-    elif exists(join(setupdir, "setup.py")):
-        configfile = join(setupdir, "dev.cfg")
-    elif exists(join(curdir, "prod.cfg")):
-        configfile = join(curdir, "prod.cfg")
-    else:
-        try:
-            configfile = pkg_resources.resource_filename(
-              pkg_resources.Requirement.parse("chronr"), 
-                "config/default.cfg")
-        except pkg_resources.DistributionNotFound:
-            raise ConfigurationError("Could not find default configuration.")
-
-    turbogears.update_config(configfile=configfile,
-        modulename="chronr.config")
-    
-    def add_custom_stdvars(vars):
-        return vars.update({"version": version})
-    
-    turbogears.view.variable_providers.append(add_custom_stdvars)
-    
-    from chronr.controllers.controllers import Root
-
-    turbogears.start_server(Root())

chronr/chronr/config/app.cfg

-[global]
-# The settings in this file should not vary depending on the deployment
-# environment. dev.cfg and prod.cfg are the locations for
-# the different deployment settings. Settings in this file will
-# be overridden by settings in those other files.
-
-# The commented out values below are the defaults
-
-# VIEW
-
-# which view (template engine) to use if one is not specified in the
-# template name
-tg.defaultview = "genshi"
-
-# The following genshi settings determine the settings used by the genshi serializer.
-
-# One of (html|html-strict|html-transitional|xhtml|xhtml-strict|html5|json)
-# genshi.default_doctype = "html"
-
-# genshi.default_encoding = "utf-8"
-
-# genshi.auto_reload = "yes"
-
-# One of (xml|xhtml|html|text)
-# genshi.default_format = "html"
-
-# genshi.variable_lookup
-
-# lenient or strict
-# genshi.lookup_errors = "lenient"
-
-# The maximum number of templates that the loader will cache in memory.
-# genshi.max_cache_size = 25
-
-# file-system path names to be use to search for templates.
-# genshi.search_path = ''
-
-# The sitetemplate is used for overall styling of a site that
-# includes multiple TurboGears applications
-# tg.sitetemplate="<packagename.templates.templatename>"
-
-# Allow every exposed function to be called as json,
-# tg.allow_json = False
-
-# Suppress the inclusion of the shipped MochiKit version, which is rather outdated.
-# Attention: setting this to True and listing 'turbogears.mochikit' in 'tg.include_widgets'
-# is a contradiction. This option will overrule the default-inclusion to prevent version
-# mismatch bugs.
-# tg.mochikit_suppress = True
-
-# List of Widgets to include on every page.
-# for example ['turbogears.mochikit']
-# tg.include_widgets = []
-
-# Set to True if the scheduler should be started
-# tg.scheduler = False
-
-# Set to True to allow paginate decorator redirects when page number gets
-# out of bound. Useful for getting the real page id in the url
-# paginate.redirect_on_out_of_range = True
-
-# Set to True to allow paginate decorator redirects when last page is requested.
-# This is useful for getting the real last page id in the url
-# paginate.redirect_on_last_page = True
-
-# Set session or cookie
-# session_filter.on = True
-
-# Set internationalization
-# i18n.run_template_filter = True
-
-# VISIT TRACKING
-# Each visit to your application will be assigned a unique visit ID tracked via
-# a cookie sent to the visitor's browser.
-# --------------
-
-# Enable Visit tracking
-visit.on=True
-
-# Number of minutes a visit may be idle before it expires.
-visit.timeout=240
-
-# The name of the cookie to transmit to the visitor's browser.
-# visit.cookie.name="tg-visit"
-
-# Domain name to specify when setting the cookie (must begin with . according to
-# RFC 2109). The default (None) should work for most cases and will default to
-# the machine to which the request was made. NOTE: localhost is NEVER a valid
-# value and will NOT WORK.
-# visit.cookie.domain=None
-
-# Specific path for the cookie
-# visit.cookie.path="/"
-
-# The name of the VisitManager plugin to use for visitor tracking.
-visit.manager="sqlalchemy"
-
-# Database class to use for visit tracking
-visit.saprovider.model = "chronr.model.Visit"
-identity.saprovider.model.visit = "chronr.model.VisitIdentity"
-
-# IDENTITY
-# General configuration of the TurboGears Identity management module
-# --------
-
-# Switch to turn on or off the Identity management module
-identity.on=True
-
-# [REQUIRED] URL to which CherryPy will internally redirect when an access
-# control check fails. If Identity management is turned on, a value for this
-# option must be specified.
-identity.failure_url="/login"
-
-identity.provider='sqlalchemy'
-
-# The names of the fields on the login form containing the visitor's user ID
-# and password. In addition, the submit button is specified simply so its
-# existence may be stripped out prior to passing the form data to the target
-# controller.
-# identity.form.user_name="user_name"
-# identity.form.password="password"
-# identity.form.submit="login"
-
-# What sources should the identity provider consider when determining the
-# identity associated with a request? Comma separated list of identity sources.
-# Valid sources: form, visit, http_auth
-# identity.source="form,http_auth,visit"
-
-# SqlAlchemyIdentityProvider
-# Configuration options for the default IdentityProvider
-# -------------------------
-
-# The classes you wish to use for your Identity model. Remember to not use reserved
-# SQL keywords for class names (at least unless you specify a different table
-# name using sqlmeta).
-identity.saprovider.model.user="chronr.model.User"
-identity.saprovider.model.group="chronr.model.Group"
-identity.saprovider.model.permission="chronr.model.Permission"
-
-# The password encryption algorithm used when comparing passwords against what's
-# stored in the database. Valid values are 'md5' or 'sha1'. If you do not
-# specify an encryption algorithm, passwords are expected to be clear text.
-# The SqlAlchemyProvider *will* encrypt passwords supplied as part of your login
-# form.  If you set the password through the password property, like:
-# my_user.password = 'secret'
-# the password will be encrypted in the database, provided identity is up and
-# running, or you have loaded the configuration specifying what encryption to
-# use (in situations where identity may not yet be running, like tests).
-
-# identity.saprovider.encryption_algorithm=None
-
-# compress the data sends to the web browser
-# [/]
-# gzip_filter.on = True
-# gzip_filter.mime_types = ["application/x-javascript", "text/javascript", "text/html", "text/css", "text/plain"]
-
-[/static]
-static_filter.on = True
-static_filter.dir = "%(top_level_dir)s/static"
-
-[/favicon.ico]
-static_filter.on = True
-static_filter.file = "%(top_level_dir)s/static/images/favicon.ico"

chronr/chronr/config/deployment.ini_tmpl

+#
+# chronr - 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:chronr
+full_stack = true
+static_files = true
+
+cache_dir = %(here)s/data
+beaker.session.key = chronr
+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

chronr/chronr/config/environment.py

+"""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
+
+import chronr.lib.app_globals as app_globals
+import chronr.lib.helpers
+from chronr.config.routing import make_map
+from chronr.model import init_model
+
+def load_environment(global_conf, app_conf):
+    """Configure the Pylons environment via the ``pylons.config``
+    object
+    """
+    # Pylons paths
+    root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    paths = dict(root=root,
+                 controllers=os.path.join(root, 'controllers'),
+                 static_files=os.path.join(root, 'public'),
+                 templates=[os.path.join(root, 'templates')])
+
+    # Initialize config with the basic options
+    config.init_app(global_conf, app_conf, package='chronr', paths=paths)
+
+    config['routes.map'] = make_map()
+    config['pylons.app_globals'] = app_globals.Globals()
+    config['pylons.h'] = chronr.lib.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'])
+
+    # Setup the SQLAlchemy database engine
+    engine = engine_from_config(config, 'sqlalchemy.')
+    init_model(engine, auto_schema_update=config.get('schemabot.update_database', False))
+
+    # CONFIGURATION OPTIONS HERE (note: all config options will override
+    # any Pylons config options)

chronr/chronr/config/log.cfg

-# LOGGING
-# Logging is often deployment specific, but some handlers and
-# formatters can be defined here.
-
-[logging]
-[[formatters]]
-[[[message_only]]]
-format='*(message)s'
-
-[[[full_content]]]
-format='*(asctime)s *(name)s *(levelname)s *(message)s'
-
-[[handlers]]
-[[[debug_out]]]
-class='StreamHandler'
-level='DEBUG'
-args='(sys.stdout,)'
-formatter='full_content'
-
-[[[access_out]]]
-class='StreamHandler'
-level='INFO'
-args='(sys.stdout,)'
-formatter='message_only'
-
-[[[error_out]]]
-class='StreamHandler'
-level='ERROR'
-args='(sys.stdout,)'

chronr/chronr/config/middleware.py

+"""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 repoze.who.config import make_middleware_with_config as make_who_with_config
+import tw.api   # ToscaWidgets
+
+from chronr.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)
+    
+    # ToscaWidgets middleware
+    app = tw.api.make_middleware(app, {
+        'toscawidgets.framework': 'pylons',
+        'toscawidgets.framework.default_view': 'mako',
+    })
+    
+    # Repoze.who middleware
+    app = make_who_with_config(app, global_conf, app_conf['who.config_file'], app_conf['who.log_file'], app_conf['who.log_level'])
+
+    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

chronr/chronr/config/registration.cfg

-[global]
-
-# Below is a list of group names that the user will be added to once 
-# their email address has been validated.  It is your responsibility 
-# to ensure that these groups exist in your database. 
-
-# Example:
-# registration.verified_user.groups = ['validated']
-
-registration.verified_user.groups =[]
-
-# You have the option of immediately placing newly registered (and 
-# unvalidated) users in the Users group, and adding them to 
-# a list of groups.  When they validate, they will be taken out 
-# of the unverified groups and put into verified_user groups.
-# Example:
-# registration.unverified_user.groups = ['limited']
-#
-# Again, it is your responsibility to ensure these groups exist in
-# the database.
-#
-# If you don't want unverified people placed in the Users group,
-# just leave this list empty.
-
-registration.unverified_user.groups = []
-
-# Your site's password reset policy.  Options are 'send_current'
-# and 'reset'.  'send_current' sends the user their current password,
-# 'reset' sends them a new (reset) password.  Note that if you have 
-# identity's 'encryption_algorithm' set to encrypt passwords, the only
-# valid option is 'reset'
-
-registration.lost_password_policy = 'reset'
-
-# Mail related fields. You will need to enter appropriate values for 
-# your environment.  If you have different mail environments in 
-# development and production, you should probably separate these out 
-# into dev.cfg and prod.cfg
-
-# Note that if you have TurboMail installed, these values will be ignored
-# and TurboMail will be used to send email messages.  TurboMail is 
-# strongly suggested for production applications, as the registration
-# code is only single threaded will not exit certain controllers until
-# the email is sent.
-
-registration.mail.smtp_server = 'localhost'  #e.g. 'mail.example.com'
-registration.mail.smtp_server_port = 25
-registration.mail.smtp_server.username = None
-registration.mail.smtp_server.password = None
-
-
-# This email address is used as the From address for all emails, as well
-# as a point of contact for problems.
-registration.mail.admin_email = 'chronr.com - Do Not Reply <do_not_reply@chronr.com>'
-
-# The Subject line that will be on the new user validation email.
-registration.mail.new.subject = 'chronr.com - New User Registration'
-
-# The Subject line that will be on the lost password email
-registration.mail.lost_password.subject = "chronr.com Password Information"
-
-# The Subject line that will be on the validation email for changed email addresses
-registration.mail.changed_email.subject = "chronr.com - Your email address change"

chronr/chronr/config/routing.py

+"""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')
+
+    map.connect('/', controller='home', action='index')
+    map.connect('/accounts', controller='accounts', action='index')
+    
+    map.connect('/{controller}/{action}')
+    map.connect('/{controller}/{action}/{id}')
+
+    return map

chronr/chronr/controllers/account.py

+import logging
+
+from pylons import request, response, session, tmpl_context as c
+from pylons.controllers.util import abort, redirect_to
+
+from chronr.lib.base import BaseController, render
+
+log = logging.getLogger(__name__)
+
+class AccountController(BaseController):
+
+    def login(self):
+        identity = request.environ.get('repoze.who.identity')
+        if identity is not None:
+            came_from = request.params.get('came_from', None)
+            if came_from:
+                redirect_to(str(came_from))
+        
+        return render('/account/login.mako')
+    

chronr/chronr/controllers/controllers.py

-# ---- Imports ----
-
-# - TurboGears modules -
-from turbogears import controllers, expose
-import pkg_resources
-try:
-    pkg_resources.require("SQLAlchemy>=0.3.10")
-except pkg_resources.DistributionNotFound:
-    import sys
-    print >> sys.stderr, """You are required to install SQLAlchemy but appear not to have done so. 
-Please run your projects setup.py or run `easy_install SQLAlchemy`.
-
-"""
-    sys.exit(1)
-from turbogears import identity, redirect
-from cherrypy import request, response, NotFound
-
-# - Project modules -
-from event_controllers import EventsController, EventsResource
-from chronr.registration.controllers import UserRegistration
-from tags_controllers import TaggedController
-from people_controllers import PeopleController
-from chronr.model import Event
-from chronr.widgets.event_widgets import EventSummary, group_events
-
-# from chronr import json
-
-# import logging
-# log = logging.getLogger("chronr.controllers")
-
-# ---- Forms / Widgets ----
-
-event_summary_widget = EventSummary()
-
-
-class Root(controllers.RootController):
-    
-    events = EventsController()
-    register = UserRegistration()
-    tagged = TaggedController()
-    people = PeopleController()
-    
-    @expose(template="chronr.templates.welcome")
-    def index(self):
-        events = Event.get_next(20)
-        grouped_events = group_events(events)
-        
-        return dict(
-            event_summary_widget = event_summary_widget,
-            grouped_events = grouped_events,
-        )
-    
-    @expose(template="chronr.templates.about")
-    def about(self):
-        return dict()
-    
-    @expose()
-    def event(self):
-        # legacy
-        raise redirect('/events')
-    
-    @expose(template="chronr.templates.login")
-    def login(self, forward_url=None, previous_url=None, *args, **kw):
-        
-        if not identity.current.anonymous \
-            and identity.was_login_attempted() \
-            and not identity.get_identity_errors():
-            raise redirect(forward_url)
-        
-        forward_url=None
-        previous_url= request.path
-        
-        if identity.was_login_attempted():
-            msg=_("The credentials you supplied were not correct or "
-                   "did not grant access to this resource.")
-        elif identity.get_identity_errors():
-            msg=_("You must provide your credentials before accessing "
-                   "this resource.")
-        else:
-            msg=_("Please log in.")
-            forward_url= request.headers.get("Referer", "/")
-            
-        response.status=403
-        return dict(message=msg, previous_url=previous_url, logging_in=True,
-                    original_parameters=request.params,
-                    forward_url=forward_url)
-    
-    @expose()
-    def logout(self):
-        identity.current.logout()
-        raise redirect("/")
-    
-
-
-setattr(Root, 'new-event', Root.events.resource._new_event)
-setattr(Root, 'edit-event', Root.events.resource._edit_event)
-setattr(Root, 'delete-event', Root.events.resource._delete_event)
-setattr(Root, 'edit-tags', Root.events.resource.resource.tagged.resource._edit_tags)

chronr/chronr/controllers/error.py

+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 chronr.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'))

chronr/chronr/controllers/event_controllers.py

-# encoding: utf-8
-
-'''event_controllers
-'''
-
-__author__ = 'Chris Miles'
-__copyright__ = '(c) Chris Miles 2008. All rights reserved.'
-__id__ = '$Id$'
-__url__ = '$URL$'
-
-
-# ---- Imports ----
-
-# - Python modules -
-from datetime import datetime
-import logging
-try:
-    from elementtree import ElementTree as ET
-except ImportError:
-    from xml.etree import ElementTree as ET     # Python 2.5+
-
-# - TurboGears modules -
-from turbogears import controllers, expose, redirect, validators, widgets
-from turbogears import identity, validate, error_handler, flash
-from turbogears.database import session
-
-# - CherryPy modules -
-from cherrypy import HTTPError, NotFound, request
-
-# - TGPriFlash modules -
-from tg_pri_flash.flash import FLASH_ALERT, FLASH_INFO, FLASH_WARNING
-
-# - Project modules -
-from chronr.widgets.event_widgets import EventList, group_events, create_event_form, edit_event_form, delete_event_form, edit_event_tags_form
-from chronr.model import Event, Tag, User, TagAssociation
-from chronr import texttime
-from chronr.tzutil import tz_to_utc, utc_to_tz
-from restful import RESTResource, RESTController
-
-
-# ---- Constants ----
-
-PAGINATE_LIMIT = 25
-
-
-# ---- Globals ----
-log = logging.getLogger("chronr.controllers.event_controllers")
-
-
-# ---- Forms / Widgets ----
-
-
-event_list_widget = EventList()
-
-
-# ---- Controllers ----
-
-class EventsFollowingResource(RESTResource):
-    ''' /events/<id>/following
-    '''
-    @expose()
-    @identity.require(identity.not_anonymous())
-    def post(self, id):
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        event.followers.append(identity.current.user)
-        
-        session.flush()
-        flash('Now following this event', FLASH_INFO)
-        redirect("/events/%s" %event.id)
-    
-    @expose()
-    @identity.require(identity.not_anonymous())
-    def delete(self, id):
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        event.followers.remove(identity.current.user)
-        
-        session.flush()
-        flash('No longer following this event', FLASH_INFO)
-        redirect("/events/%s" %event.id)
-    
-
-
-class EventsFollowingController(RESTController):
-    resource = EventsFollowingResource()
-
-
-class EventsTaggedResource(RESTResource):
-    ''' /events/<id>/tagged
-    '''
-    _require_value = True
-    
-    # Display a form for editing tags for an event
-    # Published by Root controller as /edit-tags
-    # POSTs to /events/<id>/tagged
-    @expose(template="chronr.templates.tags_edit")
-    @identity.require(identity.not_anonymous())
-    def _edit_tags(self, id=None):
-        '''Form to edit an event's tags.
-        '''
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        value = {}
-        value['id'] = id
-        value['tags'] = ', '.join([t.tag.name for t in event.tags if (t.user_id == identity.current.user.user_id)])
-        
-        log.debug("value: %s"%value)
-        return dict(
-            action = "/events/%s/tagged" %id,
-            form = edit_event_tags_form,
-            options = {},
-            submit_text = 'Save Tags',
-            value = value,
-        )
-    
-    @expose()
-    @identity.require(identity.not_anonymous())
-    @validate(form=edit_event_tags_form)
-    @error_handler(_edit_tags)
-    def post(self, id, tags=None):
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        # remove all existing tags for this user
-        Tag.remove_for_user_and_event(identity.current.user.user_id, event.id)
-        
-        taglist = [t.strip().lower()[:32] for t in tags.split(',')]
-        event.add_tags_for_user(taglist, identity.current.user.user_id)
-        
-        session.flush()
-        flash('Event saved', FLASH_INFO)
-        redirect("/events/%s" %event.id)
-    
-
-
-class EventsTaggedController(RESTController):
-    resource = EventsTaggedResource()
-
-
-class EventsItemResource(RESTResource):
-    ''' /events/<id>
-    '''
-    following = EventsFollowingController()
-    tagged = EventsTaggedController()
-    _require_value = True
-    
-    @expose("chronr.templates.event_show")
-    def get(self, event_id, *path):
-        query = session.query(Event)
-        event = query.get(event_id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        utcnow = datetime.utcnow()
-        
-        if utcnow > event.duedate:
-            remaining_time_text = texttime.stringify(utcnow - event.duedate)
-            event_in_past = True
-        else:
-            remaining_time_text = texttime.stringify(event.duedate - utcnow)
-            event_in_past = False
-        
-        # Fetch list of tag names for the Event.
-        tags = [t.name for t in Tag.tags_for_event(event.id)]
-        
-        if identity.current.user:
-            usertags = [t for t in event.tags if t.user.user_id == identity.current.user.user_id]
-        else:
-            usertags = None
-        
-        return dict(
-            event = event,
-            event_in_past = event_in_past,
-            remaining_time_text = remaining_time_text,
-            tags = tags,
-            usertags = usertags,
-        )
-    
-
-
-
-class EventsResource(RESTResource):
-    resource = EventsItemResource()
-    
-    @expose("chronr.templates.event_list")
-    # @expose(allow_json=True)
-    @expose("json", as_format="json", accept_format="application/json")
-    # @expose("json", as_format="json", accept_format="application/x-javascript")
-    def get(self, *path):
-        events = Event.get_next(20)
-        grouped_events = group_events(events)
-        
-        return dict(
-            event_list_widget = event_list_widget,
-            grouped_events = grouped_events,
-        )
-    
-    # Display a form for creating an event
-    # This is published by Root controller as /new-event
-    # POSTs to /events
-    @expose(template="chronr.templates.event_new")
-    @identity.require(identity.not_anonymous())
-    def _new_event(self):
-        '''Form to add a new event.
-        '''
-        return dict(
-            action = 'events',
-            form = create_event_form,
-            options = {},
-            submit_text = 'Create Event',
-            value = {},
-        )
-    
-    # Display a form for editing an event
-    # Published by Root controller as /edit-event
-    # PUTs to /events (simulated over POST)
-    @expose(template="chronr.templates.event_edit")
-    @identity.require(identity.not_anonymous())
-    def _edit_event(self, id=None):
-        '''Form to edit an event.
-        '''
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        # Verify current user owns event
-        if event.user_id != identity.current.user.user_id:
-            flash('Cannot edit event you don\'t own.', FLASH_ALERT)
-            redirect("/events/%s" %id)
-        
-        value = {}
-        value['id'] = id
-        value['title'] = event.title
-        value['duedate'] = event.duedate.strftime(r"%Y/%m/%d %H:%M")
-        value['timezone'] = event.timezone
-        value['allday'] = event.allday
-        value['duration'] = event.duration
-        value['description'] = event.description
-        value['private'] = event.private
-        
-        value['tags'] = ', '.join([t.tag.name for t in event.tags if (t.user_id == identity.current.user.user_id)])
-        
-        value['_method'] = 'put'    # simulate PUT over POST
-        
-        log.debug("value: %s"%value)
-        log.debug("duedate: %s"%str(value['duedate']))
-        return dict(
-            action = '/events',
-            form = edit_event_form,
-            options = {},
-            submit_text = 'Save',
-            value = value,
-        )
-    
-    # Display a form for deleting an event
-    # Published by Root controller as /delete-event
-    # DELETEs to /events (simulated over POST)
-    @expose(template="chronr.templates.event_delete")
-    @identity.require(identity.not_anonymous())
-    def _delete_event(self, id=None):
-        '''Form to delete an event.
-        '''
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        # Verify current user owns event
-        if event.user_id != identity.current.user.user_id:
-            flash('Cannot delete event you don\'t own.', FLASH_ALERT)
-            redirect("/events/%s" %id)
-        
-        value = {}
-        value['id'] = id
-        
-        value['_method'] = 'delete'    # simulate DELETE over POST
-        
-        return dict(
-            action = '/events',
-            form = delete_event_form,
-            options = {},
-            submit_text = 'Delete',
-            title = event.title,
-            value = value,
-        )
-    
-    @expose()
-    @identity.require(identity.not_anonymous())
-    @validate(form=create_event_form)
-    @error_handler(_new_event)
-    def post(self, title=None, duedate=None, timezone=None, allday=None, duration=None, description=None, private=None, tags=None):
-        log.debug("duedate: %s"%duedate)
-        duedate_utc_normal = tz_to_utc(duedate, timezone)
-        log.debug("duedate_utc_normal: %s"%duedate_utc_normal)
-        
-        e = Event(
-            title = title,
-            duedate = duedate_utc_normal,
-            timezone = timezone,
-            allday = allday,
-            duration = duration,
-            description = description,
-            private = private,
-            user_id=identity.current.user.user_id,
-        )
-        
-        taglist = [t.strip().lower()[:32] for t in tags.split(',')]
-        for tag in taglist:
-            t = Tag.get_existing_or_new(tag)
-            u = User.get_by_id(identity.current.user.user_id)
-            
-            # associate tag with the event
-            assoc = TagAssociation()
-            assoc.tag = t
-            assoc.user = u
-            e.tags.append(assoc)
-        
-        session.flush()
-        flash('Event created', FLASH_INFO)
-        redirect("/events/%s" %e.id)
-    
-    @expose()
-    @identity.require(identity.not_anonymous())
-    @validate(form=edit_event_form)
-    @error_handler(_edit_event)
-    def put(self, id=None, title=None, duedate=None, timezone=None, allday=None, duration=None, description=None, private=None, tags=None, _method=None):
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        # Verify current user owns event
-        if event.user_id != identity.current.user.user_id:
-            flash('Cannot edit event you don\'t own.', FLASH_ALERT)
-            redirect("/events/%s" %id)
-        
-        duedate_utc_normal = tz_to_utc(duedate, timezone).replace(tzinfo=None)
-        
-        event.title = title
-        event.duedate = duedate_utc_normal
-        event.timezone = timezone
-        event.allday = allday
-        event.duration = duration
-        event.description = description
-        event.private = private
-        
-        # remove all existing tags for this user
-        Tag.remove_for_user_and_event(identity.current.user.user_id, event.id)
-        
-        taglist = [t.strip().lower()[:32] for t in tags.split(',')]
-        event.add_tags_for_user(taglist, identity.current.user.user_id)
-        
-        session.flush()
-        flash('Event saved', FLASH_INFO)
-        redirect("/events/%s" %event.id)
-    
-    @expose()
-    @identity.require(identity.not_anonymous())
-    @validate(form=delete_event_form)
-    @error_handler(_delete_event)
-    def delete(self, id=None, confirm=None, _method=None):
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        # Verify current user owns event
-        if event.user_id != identity.current.user.user_id:
-            flash('Cannot delete event you don\'t own.', FLASH_ALERT)
-            redirect("/events/%s" %id)
-        
-        if not confirm:
-            flash('Delete aborted', FLASH_WARNING)
-            redirect("/events/%s" %id)
-        
-        session.delete(event)
-        session.flush()
-        flash('Event deleted', FLASH_INFO)
-        redirect("/events")
-    
-
-
-class EventsController(RESTController):
-    resource = EventsResource()
-
-
-# class EventsController(controllers.Controller):
-#     
-#     resource = EventsResource()
-#     
-#     @expose()
-#     def default(self, *path, **kwargs):
-#         return self.resource.delegate(*path, **kwargs)
-#     
-    # @expose(template="chronr.templates.test", allow_json=True)
-    # def test(self):
-    #     return dict(foo='bar', zip=2)
-    
-
-
-# --- OLD CODE ---
-
-class OLDEventController(controllers.Controller):
-    
-    @expose(template="chronr.templates.event_list")
-    def index(self):
-        events = Event.get_next(20)
-        grouped_events = group_events(events)
-        
-        return dict(
-            event_list_widget = event_list_widget,
-            grouped_events = grouped_events,
-        )
-    
-    # @expose(template="chronr.templates.event_new")
-    # @identity.require(identity.not_anonymous())
-    # def new(self):
-    #     '''Form to add a new event.
-    #     '''
-    #     return dict(
-    #         action = 'create',
-    #         form = create_event_form,
-    #         options = {},
-    #         submit_text = 'Create Event',
-    #         value = {},
-    #     )
-    
-    @expose()
-    @identity.require(identity.not_anonymous())
-    @validate(form=create_event_form)
-    # @error_handler(new)
-    def create(self, title=None, duedate=None, timezone=None, allday=None, duration=None, description=None, private=None, tags=None):
-        log.debug("duedate: %s"%duedate)
-        duedate_utc_normal = tz_to_utc(duedate, timezone)
-        log.debug("duedate_utc_normal: %s"%duedate_utc_normal)
-        
-        e = Event(
-            title = title,
-            duedate = duedate_utc_normal,
-            timezone = timezone,
-            allday = allday,
-            duration = duration,
-            description = description,
-            private = private,
-            user_id=identity.current.user.user_id,
-        )
-        
-        taglist = [t.strip().lower()[:32] for t in tags.split(',')]
-        for tag in taglist:
-            t = Tag.get_existing_or_new(tag)
-            u = User.get_by_id(identity.current.user.user_id)
-            
-            # associate tag with the event
-            assoc = TagAssociation()
-            assoc.tag = t
-            assoc.user = u
-            e.tags.append(assoc)
-        
-        session.flush()
-        flash('Event created', FLASH_INFO)
-        redirect("/events/%s" %e.id)
-    
-    @expose(template='chronr.templates.event_show')
-    def show(self, id):
-        query = session.query(Event)
-        event = query.get(id)
-        
-        if event is None:
-            raise NotFound() # 404
-        
-        duedate_local = utc_to_tz(event.duedate, event.timezone)
-        
-        utcnow = datetime.utcnow()
-        
-        if utcnow > event.duedate:
-            remaining_time_text = texttime.stringify(utcnow - event.duedate)
-            event_in_past = True
-        else:
-            remaining_time_text = texttime.stringify(event.duedate - utcnow)
-            event_in_past = False
-        
-        # Fetch list of tag names for the Event.
-        tags = [t.name for t in Tag.tags_for_event(event.id)]
-        
-        if identity.current.user:
-            usertags = [t for t in event.tags if t.user.user_id == identity.current.user.user_id]
-        else:
-            usertags = None
-        
-        return dict(
-            duedate_local = duedate_local,
-            event = event,
-            event_in_past = event_in_past,
-            remaining_time_text = remaining_time_text,
-            tags = tags,
-            usertags = usertags,
-        )
-    
-    # @expose(template="chronr.templates.event_edit")
-    # @identity.require(identity.not_anonymous())
-    # def edit(self, id=None):
-    #     '''Form to edit an event.
-    #     '''
-    #     query = session.query(Event)
-    #     event = query.get(id)
-    #     
-    #     if event is None:
-    #         raise NotFound() # 404
-    #     
-    #     # Verify current user owns event
-    #     if event.user_id != identity.current.user.user_id:
-    #         flash('Cannot edit event you don\'t own.', FLASH_ALERT)
-    #         redirect("/events/%s" %id)
-    #     
-    #     value = {}
-    #     value['id'] = id
-    #     value['title'] = event.title
-    #     value['duedate'] = event.duedate.strftime(r"%Y/%m/%d %H:%M")
-    #     value['timezone'] = event.timezone
-    #     value['allday'] = event.allday
-    #     value['duration'] = event.duration
-    #     value['description'] = event.description
-    #     value['private'] = event.private
-    #     
-    #     value['tags'] = ', '.join([t.tag.name for t in event.tags if (t.user_id == identity.current.user.user_id)])
-    #     
-    #     log.debug("value: %s"%value)
-    #     log.debug("duedate: %s"%str(value['duedate']))
-    #     return dict(
-    #         action = 'save',
-    #         form = edit_event_form,
-    #         options = {},
-    #         submit_text = 'Save',
-    #         value = value,
-    #     )
-    
-    # @expose()
-    # @identity.require(identity.not_anonymous())
-    # @validate(form=edit_event_form)
-    # @error_handler(edit)
-    # def save(self, id=None, title=None, duedate=None, timezone=None, allday=None, duration=None, description=None, private=None, tags=None):
-    #     query = session.query(Event)
-    #     event = query.get(id)
-    #     
-    #     if event is None:
-    #         raise NotFound() # 404
-    #     
-    #     # Verify current user owns event
-    #     if event.user_id != identity.current.user.user_id:
-    #         flash('Cannot edit event you don\'t own.', FLASH_ALERT)
-    #         redirect("/events/%s" %id)
-    #     
-    #     duedate_utc_normal = tz_to_utc(duedate, timezone).replace(tzinfo=None)
-    #     print "duedate_utc_normal:",duedate_utc_normal
-    #     
-    #     event.title = title
-    #     event.duedate = duedate_utc_normal
-    #     event.timezone = timezone
-    #     event.allday = allday
-    #     event.duration = duration
-    #     event.description = description
-    #     event.private = private
-    #     
-    #     # remove all existing tags for this user
-    #     Tag.remove_for_user_and_event(identity.current.user.user_id, event.id)
-    #     
-    #     taglist = [t.strip().lower()[:32] for t in tags.split(',')]
-    #     event.add_tags_for_user(taglist, identity.current.user.user_id)
-    #     
-    #     session.flush()
-    #     flash('Event saved', FLASH_INFO)
-    #     redirect("/events/%s" %event.id)
-    
-    # @expose(template="chronr.templates.event_edit_tags")
-    # @identity.require(identity.not_anonymous())
-    # def edit_tags(self, id=None):
-    #     '''Form to edit an event's tags.
-    #     '''
-    #     query = session.query(Event)
-    #     event = query.get(id)
-    #     
-    #     if event is None:
-    #         raise NotFound() # 404
-    #     
-    #     value = {}
-    #     value['id'] = id
-    #     value['tags'] = ', '.join([t.tag.name for t in event.tags if (t.user_id == identity.current.user.user_id)])
-    #     
-    #     log.debug("value: %s"%value)
-    #     return dict(
-    #         action = 'save_tags',
-    #         form = edit_event_tags_form,
-    #         options = {},
-    #         submit_text = 'Save Tags',
-    #         value = value,
-    #     )
-    # 
-    # @expose()
-    # @identity.require(identity.not_anonymous())
-    # @validate(form=edit_event_tags_form)
-    # @error_handler(edit_tags)
-    # def save_tags(self, id=None, tags=None):
-    #     query = session.query(Event)
-    #     event = query.get(id)
-    #     
-    #     if event is None:
-    #         raise NotFound() # 404
-    #     
-    #     # remove all existing tags for this user
-    #     Tag.remove_for_user_and_event(identity.current.user.user_id, event.id)
-    #     
-    #     taglist = [t.strip().lower()[:32] for t in tags.split(',')]
-    #     event.add_tags_for_user(taglist, identity.current.user.user_id)
-    #     
-    #     session.flush()
-    #     flash('Event saved', FLASH_INFO)
-    #     redirect("/events/%s" %event.id)
-    
-

chronr/chronr/controllers/home.py

+import logging
+
+from pylons import request, response, session, tmpl_context as c
+from pylons.controllers.util import abort, redirect_to
+
+from chronr.lib.base import BaseController, render
+
+log = logging.getLogger(__name__)
+
+class HomeController(BaseController):
+    def index(self):
+        return render('/home.mako')
+    

chronr/chronr/controllers/people_controllers.py

-# encoding: utf-8
-
-'''user_controllers
-'''
-
-__author__ = 'Chris Miles'
-__copyright__ = '(c) Chris Miles 2008. All rights reserved.'
-__id__ = '$Id$'
-__url__ = '$URL$'
-
-
-# ---- Imports ----
-
-# - Python modules -
-from datetime import datetime
-import logging
-import os
-try:
-    from elementtree import ElementTree as ET
-except ImportError:
-    from xml.etree import ElementTree as ET     # Python 2.5+
-
-# - TurboGears modules -
-from turbogears import controllers, expose, redirect, validators, widgets
-# from turbogears import identity, validate, error_handler, flash
-from turbogears import paginate, flash, url
-from turbogears.database import session
-from turbogears.widgets import PaginateDataGrid
-
-# - CherryPy modules -
-from cherrypy import NotFound
-
-# - TGPriFlash modules -
-from tg_pri_flash.flash import FLASH_ALERT, FLASH_INFO, FLASH_WARNING
-
-# - SQLAchemy modules -
-from sqlalchemy import or_, and_
-
-# - Project modules -
-from chronr.model import Event, User, Tag, TagAssociation
-from chronr.widgets.event_widgets import EventList, group_events
-from chronr import texttime
-from restful import RESTResource, RESTController
-from tags_controllers import parse_tag_path
-
-
-# ---- Constants ----
-
-PAGINATE_LIMIT = 10
-
-
-# ---- Globals ----
-log = logging.getLogger("chronr.controllers")
-
-
-# ---- Forms / Widgets ----
-
-event_list_widget = EventList()
-
-
-
-# - Datagrid Widgets -
-
-# generate the link inside the datagrid
-class MakeLink(object):
-    def __init__(self, baseurl, id, title):
-        self.baseurl = baseurl
-        self.id = id
-        self.title = title
-    
-    def __call__(self, obj):
-        url = controllers.url(self.baseurl + '/' + str(obj.__getattribute__(self.id)))
-        link = ET.Element('a', href=url, style='text-decoration: underline' )
-        link.text = obj.__getattribute__(self.title)
-        return link
-    
-
-
-user_list_grid = PaginateDataGrid(
-    fields=[
-        PaginateDataGrid.Column(
-                name = 'user_name',
-                getter = MakeLink('/people', 'user_name', 'user_name'),
-                title = 'Username',
-        ),
-        PaginateDataGrid.Column(
-                name = 'name',
-                getter = 'display_name',
-                title = 'Name',
-        ),
-    ]
-)
-
-
-def time_remaining(event):
-    return texttime.stringify(event.duedate - datetime.utcnow())
-
-
-event_list_grid = PaginateDataGrid(
-    fields=[
-        PaginateDataGrid.Column(
-                name = 'timeremaining',
-                getter = time_remaining,
-                title = 'Due In',
-        ),
-        PaginateDataGrid.Column(
-                name = 'title',
-                getter = MakeLink('/events', 'id', 'title'),
-                title = 'Title',
-        ),
-        PaginateDataGrid.Column(
-                name = 'duedate',
-                getter = 'duedate',
-                title = 'Due Date (UTC)',
-        ),
-    ]
-)
-
-
-
-# ---- RESTful Resources & Controllers ----
-
-class UserFollowingTaggedItemsResource(RESTResource):
-    ''' /people/<username>/following/tagged/<tag1>/<tag2>;<tag3>
-    '''
-    swallow_path = True
-    
-    @expose("chronr.templates.user_following_tags_show")
-    @expose(allow_json=True)
-    def get(self, username, *vpath):
-        log.debug("TagsController.default vpath: %s" %str(vpath))
-        
-        u = User.by_user_name(username)
-        if u is None:
-            flash(u"No such user '%s'" %username, FLASH_ALERT)
-            redirect(url('/people'))
-        
-        qfilter, tag_desc = parse_tag_path(vpath)
-        
-        if qfilter is None:
-            events = []
-        else:
-            qfilter = and_(TagAssociation.c.user_id == u.user_id, qfilter)
-            events = Event.get_next(30, username=username, following=True, qfilter=qfilter)
-        
-        grouped_events = group_events(events)
-        
-        return dict(
-            # event_grid = event_list_grid,
-            event_list_widget = event_list_widget,
-            # events = events,
-            grouped_events = grouped_events,
-            tags = tag_desc,
-            user = u,
-        )
-    
-
-class UserFollowingTaggedResource(RESTResource):
-    ''' /people/<username>/following/tagged
-    '''
-    resource = UserFollowingTaggedItemsResource()
-    
-    @expose("chronr.templates.user_following_tagged_index")
-    def get(self, username, *path):
-        u = User.by_user_name(username)
-        if u is None:
-            flash(u"No such user '%s'" %username, FLASH_ALERT)
-            redirect(url('/people'))
-        
-        return dict(user=u)
-    
-
-class UserFollowingTaggedController(RESTController):
-    '''
-    '''
-    resource = UserFollowingTaggedResource()
-
-
-class UserFollowingResource(RESTResource):
-    ''' /people/<username>/following
-    '''
-    tagged = UserFollowingTaggedController()
-    
-    @expose("chronr.templates.user_following_list")
-    @expose("json", as_format="json", accept_format="application/json")
-    def get(self, username, *path):
-        u = User.by_user_name(username)
-        if u is None:
-            flash(u"No such user '%s'" %username, FLASH_ALERT)
-            redirect(url('/people'))
-        
-        events = Event.get_next(20, username=username, following=True)
-        grouped_events = group_events(events)
-        
-        return dict(
-            event_list_widget = event_list_widget,
-            grouped_events = grouped_events,
-            user = u,
-        )
-    
-
-class UserFollowingController(RESTController):
-    '''
-    '''
-    resource = UserFollowingResource()
-
-
-class UserTaggedItemsResource(RESTResource):
-    ''' /people/<username>/tagged/<tag1>/<tag2>;<tag3>
-    '''
-    swallow_path = True
-    
-    @expose("chronr.templates.user_tags_show")
-    @expose(allow_json=True)
-    def get(self, username, *vpath):
-        log.debug("TagsController.default vpath: %s" %str(vpath))
-        
-        u = User.by_user_name(username)
-        if u is None:
-            flash(u"No such user '%s'" %username, FLASH_ALERT)
-            redirect(url('/people'))
-        
-        qfilter, tag_desc = parse_tag_path(vpath)
-        
-        if qfilter is None:
-            events = []
-        else:
-            qfilter = and_(TagAssociation.c.user_id == u.user_id, qfilter)
-            events = Event.get_next(30, qfilter=qfilter)
-        
-        grouped_events = group_events(events)
-        
-        return dict(
-            # event_grid = event_list_grid,
-            event_list_widget = event_list_widget,
-            # events = events,
-            grouped_events = grouped_events,
-            tags = tag_desc,
-            user = u,
-        )
-    
-
-class UserTaggedResource(RESTResource):
-    ''' /people/<username>/tagged
-    '''
-    resource = UserTaggedItemsResource()
-    
-    @expose("chronr.templates.user_tagged_index")
-    def get(self, username, *path):
-        u = User.by_user_name(username)
-        if u is None:
-            flash(u"No such user '%s'" %username, FLASH_ALERT)
-            redirect(url('/people'))
-        
-        return dict(user=u)
-    
-
-class UserTaggedController(RESTController):
-    '''
-    '''
-    resource = UserTaggedResource()
-
-class UserEventsResource(RESTResource):
-    ''' /people/<username>/events
-    '''
-    _require_value = True
-    
-    @expose("chronr.templates.user_event_list")
-    @expose("json", as_format="json", accept_format="application/json")
-    def get(self, username, *path):
-        events = Event.get_next(20, username=username)
-        grouped_events = group_events(events)
-        
-        return dict(
-            event_list_widget = event_list_widget,
-            grouped_events = grouped_events,
-            username = username,
-        )
-    
-
-class UserEventsController(RESTController):
-    ''' /people/<username>/events
-    '''
-    resource = UserEventsResource()
-
-class UserResource(RESTResource):
-    ''' /people/<username>
-    '''
-    events = UserEventsController()
-    tagged = UserTaggedController()
-    following = UserFollowingController()
-    
-    _require_value = True
-    
-    @expose(template="chronr.templates.user_show")
-    def get(self, username, *path):
-        u = User.by_user_name(username)
-        if u is None:
-            flash(u"No such user '%s'" %username, FLASH_ALERT)
-            redirect(url('/people'))
-        
-        events = Event.get_next(10, username=username)
-        grouped_events = group_events(events)
-        
-        user_tags = Tag.tags_for_user(u.user_id)
-        
-        return dict(
-            event_list_widget = event_list_widget,
-            following_count = u.num_following(),
-            grouped_events = grouped_events,
-            user = u,
-            user_tags = user_tags,
-        )
-    
-
-
-class PeopleResource(RESTResource):
-    ''' /people
-    '''
-    resource = UserResource()
-    
-    @expose("chronr.templates.user_list")
-    @paginate('users', limit=PAGINATE_LIMIT, allow_limit_override=True, max_pages=10)
-    def get(self, *path):
-        query = session.query(User)
-        return dict(
-            grid = user_list_grid,
-            users = query,
-        )
-    
-
-class PeopleController(RESTController):
-    resource = PeopleResource()

chronr/chronr/controllers/restful.py

-# encoding: utf-8
-
-'''restful
-'''
-
-__author__ = 'Chris Miles'
-__copyright__ = '(c) Chris Miles 2008. All rights reserved.'
-__id__ = '$Id$'
-__url__ = '$URL$'
-
-
-# ---- Imports ----
-
-# - Python modules -
-import logging
-
-# - TurboGears modules -
-from turbogears import controllers, expose
-
-# - CherryPy modules -
-from cherrypy import HTTPError, NotFound, request
-
-
-# ---- Globals ----
-
-log = logging.getLogger("chronr.controllers.event_controllers")
-
-
-# ---- Classes ----
-
-class RESTResource(object):
-    def delegate(self, *path, **kwargs):
-        method = request.method.lower()
-        # if method == 'post' and kwargs.get('_method', None) is not None:
-        if kwargs.get('_method', None) is not None:
-            # allow faking the method, to help browsers along
-            method = kwargs['_method'].lower()
-        
-        log.debug("%s: method=%s parents=%s path=%s kwargs=%s", self.__class__.__name__, method, getattr(request, 'rest_parents', []), path, kwargs)
-        
-        if len(path) > 0 and hasattr(self, path[0]) and isinstance(getattr(self, path[0]), RESTController):
-            attribute, subpath = path[0], path[1:]
-            return getattr(self, attribute).default(*subpath, **kwargs)
-        elif path == () \
-                    or (hasattr(self, 'swallow_path') and self.swallow_path) \
-                    or (hasattr(self, '_require_value') and self._require_value and len(path) == 1):
-            if hasattr(self, method):
-                resource_method = getattr(self, method)
-                if hasattr(resource_method, 'exposed') and resource_method.exposed:
-                    if '_method' in kwargs.keys():
-                        # lose faked method param if it was included
-                        del kwargs['_method']
-                    return resource_method(*(getattr(request, 'rest_parents', []) + list(path)), **kwargs)
-        elif hasattr(self, 'resource'):
-            # Store resource parent values in CP request obj
-            if hasattr(request, 'rest_parents'):
-                request.rest_parents.append(path[0])
-            else:
-                request.rest_parents = [path[0]]
-            return self.resource.delegate(*path[1:], **kwargs)
-        else:
-            raise NotFound()
-        
-        # TODO: add header "Allow: GET, POST" with allowed methods
-        raise HTTPError(405, "Method Not Allowed")
-    
-
-
-
-class RESTController(controllers.Controller):
-    '''Subclass RESTController to create a controller that
-    delegates requests to a RESTResource.
-    
-    Add a `resource` attribute whose value is an instance of
-    a RESTResource subclass.
-    
-    Example:
-    
-        class EventsController(RESTController):
-            resource = EventsResource()
-    '''
-    
-    @expose()
-    def default(self, *path, **kwargs):
-        return self.resource.delegate(*path, **kwargs)
-    

chronr/chronr/controllers/tags_controllers.py