Commits

Nicolas Dietrich committed d58be34 Merge

merge default into geo

Comments (0)

Files changed (142)

 test-data/index
 site/*
 test-site/*
+adhocracy/*_theme
+[main]
+host = https://www.transifex.net
+
+[adhocracy.adhocracy]
+file_filter = adhocracy/i18n/<lang>/LC_MESSAGES/adhocracy.po
+source_lang = en
+source_file = adhocracy/i18n/adhocracy.pot
+
+
 1.2beta2dev
 -----------
 
+- add the possibility to overwrite templates in <adhocracy.site>/templates 
+  directory (like it's possible for static resources and static pages already.
+  New templates there need a server restart to be picked up.
+  [csenger]
 - delgateables can have category badgets [joka]
 - instances can have badgets, they are only valid inside that instance [joka]
 - added option to set the smtp port [joka]

INSTALL.txt

-The following installation instructions are kept up-to-date at http://trac.adhocracy.cc/wiki/InstallationInstructions
-
-= Installation Instructions = 
-
-Installing Adhocracy is a somewhat complicated process. The administrator should have at least some experience in dealing with Python packages, Unix shell commands, server configuration (for deployment purposes) and optionally, building packages from mercurial checkouts. 
-
-== Preflight Entertainment == 
-
-First, make sure the dependencies in http://trac.adhocracy.cc/wiki/SoftwareDependencies were satisfied. 
-
-The guide assumes that you're installing Adhocracy at `/opt/adhocracy` and that you've created a dedicated user called `adhocracy`. These assumptions are, of course, not neccessary and would probably be impractical when in a testing or development environment. 
-
-=== virtualenv ===
-
-Adhocracy installs about five metric tons of Python packages. In order to keep your Python install clean, lean and mean, you will want to use a [http://pypi.python.org/pypi/virtualenv virtualenv]. To get going, 
-
- * either install the virtualenv package found in your system's package manager (on Debian based systems, run  `apt-get install virtualenv`) and do
-
-$ su adhocracy
-$ virtualenv /opt/adhocracy/env --setuptools
-[gibberish]
-$ source /opt/adhocracy/env/bin/activate
-
- * or, if your system doesn't have virtualenv pre-packaged, try this: 
-
-$ easy_install virtualenv
-[gibberish]
-$ su adhocracy 
-$ python virtualenv.py /opt/adhocracy/env --setuptools
-[more gibberish]
-$ source /opt/adhocracy/env/bin/activate
-
-Whenever you enter the virtualenv, distutils will only install packages to the virtualenv's library instead of your real site-packages directory.
-
-Make sure you don't forget the --setuptools parameter as some distributions package virtualenv with a default of distribute which unfortunately has a bug (http://bitbucket.org/tarek/distribute/issue/100/easy_install-u-distribute-errors-out-with) which makes it unusable in a virtualenv.
-
-=== Domain Names ===
-
-Adhocracy uses a scheme in which a single installation can serve as a host for many user groups. These user groups would then access a selection of distinct `instances` which share little but the user registration data. 
-
-In order to identify the active instance, Adhocracy uses domain names. Therefore, if Adhocracy is installed at a domain name like `example.com`, instances would be accessed via `instance.example.com`. 
-
-This means that in a production environment, you will want a [http://en.wikipedia.org/wiki/Wildcard_DNS_record wild card DNS record] to point to the Adhocracy server. For testing purposes, we usually set up a fake domain in our local `/etc/hosts` file: 
-
-127.0.0.1 adhocracy.lan test.adhocracy.lan another.adhocracy.lan 
-
-For simpler setups, a single-instance Adhocracy mode is available. You can enable it by setting the `adhocracy.instance` setting in your config file (see below). Note that the required singleton instance will be created upon executing the `setup-app` command described later on. 
-
-== Getting Adhocracy == 
-
-**If you're a developer (or just would like to checkout the source code from the mercurial repository), read DevelopmentHints instead of this part.**
-
-Adhocracy's source code is hosted on PyPI, so download the version of your choice from http://pypi.python.org/pypi/adhocracy/ and unpack it. Then, run setup by typing
-
-$ python setup.py install
-
-== Configuration == 
-
-First off, create a new database user and an empty database. Make sure to set UTF-8/Unicode as the default encoding wherever necessary. 
-
-Adhocracy is configured using an .ini file. The default configuration is stored in a template called `development.ini`. To begin your configuration, create a copy of the file that you will work on: 
-
-$ cd /opt/adhocracy
-$ cp development.ini example.ini
-
-Inside the .ini you will find a large number of configuration options. Make sure to at least edit those with an ''INSTALL'' comment. Make especially sure to insert a site specific session secret (`beaker.session.secret`) and to set the `debug` flag to `false` in production environments. 
-
-Once you have configured your .ini, run the following command to initialize the database schema and create a couple of default entities. 
-
-$ cd /opt/adhocracy
-$ paster setup-app example.ini
-
-Among other things, this will create:
- * a default user called '''admin''' with the password '''password'''.  
- * an Adhocracy instance named "Test Instance"
- * a site directory (in `adhocracy.site.dir`) and some template documents (see below). The site directory will be referenced as `$SITE` in the rest of this document (or "example", just to mess with you).
-
-== Running Adhocracy ==
-
-For information on how to execute Adhocracy, see the following pages: 
- * http://trac.adhocracy.cc/wiki/QueueProcessing is required both for development and deployment
- * http://trac.adhocracy.cc/wiki/RunningAdhocracy for development
- * http://trac.adhocracy.cc/wiki/DeploymentSetup for production sites
 to a running instance of memcache.
 
 If no memcache is configured or available, Adhocracy should still 
-function, but displaying proposals that have a lot of votes can take 
-a long time. 
+function, but displaying proposals that have a lot of votes or comments
+can take a long time.
 
 It also uses solr for searches and rabbitmq to schedule asyncron 
 tasks. Both are mandatory.
 
-Please consult INSTALL.txt for details.
+
+Installing Adhocracy is a somewhat complicated process. To have a reproducable 
+and fast way to set up development and production environments we use 
+`zc.buildout`_. You can download our buildout configuration
+at https://bitbucket.org/liqd/adhocracy.buildout
+The README of adhocray.buildout has extensive information about the setup process.
+

adhocracy/client.py

 __license__ = 'BSD'
 
 import base64
+import json
 import logging
 import urllib
 import urllib2
         self.reset()
         url = self.get_location('page', entity_id=page.get('id'),
                                 variant=variant, format=None)
-        self.open_url(url, method='PUT', data=proposal)
+        self.open_url(url, method='PUT', data=page)
         return self.last_message
 
     def page_delete(self, id):
     #    return self.last_message
 
     def __dumpstr(self, data):
-        try:  # since python 2.6
-            import json
-        except ImportError:
-            import simplejson as json
         return json.dumps(data)
 
     def __loadstr(self, string):
-        try:  # since python 2.6
-            import json
-        except ImportError:
-            import simplejson as json
         return json.loads(string)
 
 

adhocracy/config/environment.py

 """Pylons environment configuration"""
 import os
+import time
+import traceback
 
 from mako.lookup import TemplateLookup
-from pylons import config
+from paste.deploy.converters import asbool
+from pylons import config, tmpl_context as c
 from pylons.error import handle_mako_error
 from sqlalchemy import engine_from_config
+from sqlalchemy.interfaces import ConnectionProxy
 
 import adhocracy.lib.app_globals as app_globals
 import adhocracy.lib.helpers
 from adhocracy.model import init_model
 from adhocracy.lib.search import init_search
 from adhocracy.lib.democracy import init_democracy
+from adhocracy.lib.util import create_site_subdirectory
 from adhocracy.lib import init_site
 
 
     object
     """
     # Pylons paths
+    conf_copy = global_conf.copy()
+    conf_copy.update(app_conf)
+    site_templates = create_site_subdirectory('templates', app_conf=conf_copy)
     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, 'static'),
-                 templates=[os.path.join(root, 'templates')])
+                 templates=[site_templates,
+                            os.path.join(root, 'templates')])
 
     # Initialize config with the basic options
     config.init_app(global_conf, app_conf, package='adhocracy', paths=paths)
         imports=['from webhelpers.html import escape'])
 
     # Setup the SQLAlchemy database engine
-    engine = engine_from_config(config, 'sqlalchemy.')
+    connectionproxy = None
+    if asbool(config.get('adhocracy.debug.sql', False)):
+        connectionproxy = TimerProxy()
+    engine = engine_from_config(config, 'sqlalchemy.', proxy=connectionproxy)
     init_model(engine)
 
     # CONFIGURATION OPTIONS HERE (note: all config options will override
     if with_db:
         init_search()
     init_democracy()
+
+
+class TimerProxy(ConnectionProxy):
+    '''
+    A timing proxy with code borrowed from spline and
+    pyramid_debugtoolbar. This will work for sqlalchemy 0.6,
+    but not 0.7. pyramid_debugtoolbar works for 0.7.
+    '''
+
+    def cursor_execute(self, execute, cursor, statement, parameters, context,
+                       executemany):
+        start_time = time.time()
+
+        try:
+            return execute(cursor, statement, parameters, context)
+        finally:
+            duration = time.time() - start_time
+
+            # Find who spawned this query.  Rewind up the stack until we
+            # escape from sqlalchemy code -- including this file, which
+            # contains proxy stuff
+            caller = '(unknown)'
+            for frame_file, frame_line, frame_func, frame_code in \
+                reversed(traceback.extract_stack()):
+
+                if __file__.startswith(frame_file) \
+                    or '/sqlalchemy/' in frame_file:
+
+                    continue
+
+                # OK, this is it
+                caller = "{0}:{1} in {2}".format(
+                    frame_file, frame_line, frame_func)
+                break
+
+            # save interesting information for presentation later
+            try:
+                if not c.pdtb_sqla_queries:
+                    c.pdtb_sqla_queries = []
+                queries = c.pdtb_sqla_queries
+                query_data = {
+                    'duration': duration,
+                    'statement': statement,
+                    'parameters': parameters,
+                    'context': context,
+                    'caller': caller,
+                }
+                queries.append(query_data)
+            except TypeError:
+                # happens when sql is emitted before pylons has started
+                # or outside of a request
+                pass

adhocracy/config/routing.py

                                          'ask_delete': 'GET',
                                          'widget': 'GET'})
 
-    map.connect('/badge', controller='badgeglobal', action='index',
+    map.connect('/badge', controller='badge', action='index',
                 conditions=dict(method=['GET']))
-    map.connect('/badge/add', controller='badgeglobal', action='add',
-                conditions=dict(method=['GET']))
-    map.connect('/badge/add', controller='badgeglobal', action='create',
+    map.connect('/badge/{badge_type}/add', controller='badge',
+                action='add', conditions=dict(method=['GET']))
+    map.connect('/badge/{badge_type}/add', controller='badge',
+                action='create', conditions=dict(method=['POST']))
+    map.connect('/badge/edit/{id}', controller='badge',
+                action="edit", conditions=dict(method=['GET']))
+    map.connect('/badge/edit/{id}',
+                controller='badge', action="update",
                 conditions=dict(method=['POST']))
-    map.connect('/badge/edit/{id}', controller='badgeglobal', action="edit",
-                conditions=dict(method=['GET']))
-    map.connect('/badge/edit/{id}', controller='badgeglobal', action="update",
-                conditions=dict(method=['POST']))
-    map.connect('/instance/{instance_key}/badge', controller='badgeinstance',
-                action='index', conditions=dict(method=['GET']))
-    map.connect('/instance/{instance_key}/badge/add', controller='badgeinstance',
-                action='add', conditions=dict(method=['GET']))
-    map.connect('/instance/{instance_key}/badge/add', controller='badgeinstance',
-                action='create', conditions=dict(method=['POST']))
-    map.connect('/instance/{instance_key}/badge/edit/{id}',
-            controller='badgeinstance',
-                action="edit", conditions=dict(method=['GET']))
-    map.connect('/instance/{instance_key}/badge/edit/{id}',
-            controller='badgeinstance',
-                action="update", conditions=dict(method=['POST']))
 
     # not using REST since tags may contain dots, thus failing format
     # detection.

adhocracy/contrib/__init__.py

-import oauthtwitter
+import oauthtwitter

adhocracy/controllers/admin.py

     @RequireInternalRequest()
     @ActionProtector(has_permission("global.admin"))
     def user_import(self):
+
         if request.method == "POST":
             try:
                 self.form_result = UserImportForm().to_python(request.params)
                 names.append(name)
                 user = model.User.create(name, email,
                                          display_name=display_name)
-                user.reset_code = random_token()
+                user.activation_code = user.IMPORT_MARKER + random_token()
+                password = random_token()
+                user_info['password'] = password
+                user.password = password
                 model.meta.Session.add(user)
                 model.meta.Session.commit()
                 users.append(user)
                 created.append(user.user_name)
-                url = base_url(
-                    c.instance,
-                    path="/user/%s/reset?c=%s" % (user.user_name,
-                                                  user.reset_code))
+                url = base_url(c.instance,
+                               path="/user/%s/activate?c=%s" % (
+                                   user.user_name,
+                                   user.activation_code))
+
                 user_info['url'] = url
                 body = form_result['email_template'].format(**user_info)
-                to_user(user, form_result['email_subject'], body, decorate_body=False)
+                to_user(user, form_result['email_subject'], body,
+                        decorate_body=False)
                 mailed.append(user.user_name)
                 if c.instance:
                     membership = model.Membership(user, c.instance,
 
     @ActionProtector(has_permission("global.admin"))
     def user_import_form(self, errors=None):
+        c.placeholders = {'required': [],
+                          'optional': []}
+        c.placeholders['required'].append(
+            {'name': '{user_name}',
+             'description': _('The name with which the user can log in.')})
+        c.placeholders['required'].append(
+            {'name': '{password}',
+             'description': _('The initial password for the user.')})
+        c.placeholders['required'].append(
+            {'name': '{url}',
+             'description': _('An URL for the user to activate his account.')})
+        c.placeholders['optional'].append(
+            {'name': '{display_name}',
+             'description': _('The name that will be displayed to other '
+                              'users.')})
+        c.placeholders['optional'].append(
+            {'name': '{email}',
+             'description': _('The email address of the user.')})
         return formencode.htmlfill.render(render("/admin/import_form.html"),
                                           defaults=dict(request.params),
                                           errors=errors,

adhocracy/controllers/badge.py

+import formencode
+from formencode import Any, All, htmlfill, validators
+from pylons import request, tmpl_context as c
+from pylons.controllers.util import redirect
+from pylons.decorators import validate
+from pylons.i18n import _
+from repoze.what.plugins.pylonshq import ActionProtector
+from repoze.what.predicates import Any as WhatAnyPredicate
+
+from adhocracy.forms.common import ValidGroup, ValidHTMLColor, ContainsChar
+from adhocracy.forms.common import ValidBadgeInstance
+from adhocracy.model import Badge, CategoryBadge, DelegateableBadge, UserBadge
+from adhocracy.model import Group, Instance, meta
+from adhocracy.lib import helpers as h
+from adhocracy.lib.auth.authorization import has, has_permission
+from adhocracy.lib.auth.csrf import RequireInternalRequest
+from adhocracy.lib.base import BaseController
+from adhocracy.lib.templating import render
+
+
+class BadgeForm(formencode.Schema):
+    allow_extra_fields = True
+    title = All(validators.String(max=40, not_empty=True),
+                ContainsChar())
+    description = validators.String(max=255)
+    color = ValidHTMLColor()
+    instance = ValidBadgeInstance()
+
+
+class UserBadgeForm(BadgeForm):
+    group = Any(validators.Empty, ValidGroup())
+    display_group = validators.StringBoolean(if_missing=False)
+
+
+AnyAdmin = WhatAnyPredicate(has_permission('global.admin'),
+                            has_permission('instance.admin'))
+
+
+class BadgeController(BaseController):
+    """Badge controller base class"""
+
+    def _available_badges(self):
+        '''
+        Return the badges that are editable by a user.
+        '''
+        c.groups = [{'permission': 'global.admin',
+                     'label': _('In all instances')}]
+        if c.instance:
+            c.groups.append(
+                {'permission': 'instance.admin',
+                 'label': _('In instance "%s"') % c.instance.label})
+        badges = {}
+        if has('global.admin'):
+            badges['global.admin'] = {
+                'user': UserBadge.all(instance=None),
+                'delegateable': DelegateableBadge.all(instance=None),
+                'category': CategoryBadge.all(instance=None)}
+        if has('instance.admin') and c.instance is not None:
+            badges['instance.admin'] = {
+                'user': UserBadge.all(instance=c.instance),
+                'delegateable': DelegateableBadge.all(instance=c.instance),
+                'category': CategoryBadge.all(instance=c.instance)}
+        return badges
+
+    @property
+    def base_url(self):
+        return h.site.base_url(instance=c.instance, path='/badge')
+
+    def index(self, format='html'):
+        c.badges = self._available_badges()
+        return render("/badge/index.html")
+
+    def _redirect_not_found(self, id):
+        h.flash(_("We cannot find the badge with the id %s") % str(id),
+                'error')
+        redirect(self.base_url)
+
+    def render_form(self):
+        c.instances = Instance.all()
+        return render("/badge/form.html")
+
+    def add(self, badge_type=None, errors=None):
+        if badge_type is not None:
+            c.badge_type = badge_type
+        c.form_type = 'add'
+        c.groups = meta.Session.query(Group).order_by(Group.group_name).all()
+        return htmlfill.render(self.render_form(),
+                               defaults=dict(request.params),
+                               errors=errors)
+
+    def dispatch(self, action, badge_type, id=None):
+        '''
+        dispatch to a suiteable "create" or "edit" action
+
+        Methods are named <action>_<badge_type>_badge().
+        '''
+        assert action in ['create', 'update']
+        if badge_type not in ['user', 'delegateable', 'category']:
+            raise AssertionError('Unknown badge_type: %s' % badge_type)
+
+        c.badge_type = badge_type
+        c.form_type = action
+
+        methodname = "%s_%s_badge" % (action, badge_type)
+        method = getattr(self, methodname, None)
+        if method is None:
+            raise AttributeError(
+                'Method not found for action "%s", badge_type: %s' %
+                (action, badge_type))
+        if id is not None:
+            return method(id)
+        else:
+            return method()
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    def create(self, badge_type):
+        return self.dispatch('create', badge_type)
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    @validate(schema=UserBadgeForm(), form='add')
+    def create_user_badge(self):
+        title, color, description, instance = self._get_common_fields(
+            self.form_result)
+        group = self.form_result.get('group')
+        display_group = self.form_result.get('display_group')
+        UserBadge.create(title, color, description, group, display_group,
+                         instance)
+        # commit cause redirect() raises an exception
+        meta.Session.commit()
+        redirect(self.base_url)
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    @validate(schema=BadgeForm(), form='add')
+    def create_delegateable_badge(self):
+        title, color, description, instance = self._get_common_fields(
+            self.form_result)
+        DelegateableBadge.create(title, color, description, instance)
+        # commit cause redirect() raises an exception
+        meta.Session.commit()
+        redirect(self.base_url)
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    @validate(schema=BadgeForm(), form='add')
+    def create_category_badge(self):
+        title, color, description, instance = self._get_common_fields(
+            self.form_result)
+        CategoryBadge.create(title, color, description, instance)
+        # commit cause redirect() raises an exception
+        meta.Session.commit()
+        redirect(self.base_url)
+
+    def _get_common_fields(self, form_result):
+        return (form_result.get('title').strip(),
+                form_result.get('color').strip(),
+                form_result.get('description').strip(),
+                form_result.get('instance'))
+
+    def get_badge_type(self, badge):
+        return badge.polymorphic_identity
+
+    @ActionProtector(AnyAdmin)
+    def edit(self, id, errors=None):
+        badge = Badge.by_id(id)
+        if badge is None:
+            self._redirect_not_found(id)
+        if badge.instance != c.instance and not has('global.admin'):
+            self._redirect_not_found(id)
+        c.badge_type = self.get_badge_type(badge)
+        c.form_type = 'update'
+        instance_default = badge.instance.key if badge.instance else ''
+        defaults = dict(title=badge.title,
+                        description=badge.description,
+                        color=badge.color,
+                        display_group=badge.display_group,
+                        instance=instance_default)
+        if isinstance(badge, UserBadge):
+            c.groups = meta.Session.query(Group).order_by(Group.group_name)
+            defaults['group'] = badge.group and badge.group.code or ''
+        return htmlfill.render(self.render_form(),
+                               errors=errors,
+                               defaults=defaults)
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    def update(self, id):
+        badge = Badge.by_id(id)
+        if badge is None:
+            self._redirect_not_found(id)
+        if badge.instance != c.instance and not has('global.admin'):
+            self._redirect_not_found(id)
+        c.badge_type = self.get_badge_type(badge)
+        return self.dispatch('update', c.badge_type, id=id)
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    @validate(schema=UserBadgeForm(), form='edit')
+    def update_user_badge(self, id):
+        badge = Badge.by_id(id)
+        title, color, description, instance = self._get_common_fields(
+            self.form_result)
+        group = self.form_result.get('group')
+        display_group = self.form_result.get('display_group')
+
+        badge.group = group
+        badge.title = title
+        badge.color = color
+        badge.description = description
+        badge.instance = instance
+        badge.display_group = display_group
+        meta.Session.commit()
+        h.flash(_("Badge changed successfully"), 'success')
+        redirect(self.base_url)
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    @validate(schema=BadgeForm(), form='edit')
+    def update_delegateable_badge(self, id):
+        badge = Badge.by_id(id)
+        title, color, description, instance = self._get_common_fields(
+            self.form_result)
+
+        badge.title = title
+        badge.color = color
+        badge.description = description
+        badge.instance = instance
+        meta.Session.commit()
+        h.flash(_("Badge changed successfully"), 'success')
+        redirect(self.base_url)
+
+    @ActionProtector(AnyAdmin)
+    @RequireInternalRequest()
+    @validate(schema=BadgeForm(), form='edit')
+    def update_category_badge(self, id):
+        badge = Badge.by_id(id)
+        title, color, description, instance = self._get_common_fields(
+            self.form_result)
+
+        badge.title = title
+        badge.color = color
+        badge.description = description
+        badge.instance = instance
+        meta.Session.commit()
+        h.flash(_("Badge changed successfully"), 'success')
+        redirect(self.base_url)

adhocracy/controllers/badgeglobal.py

-import formencode
-from formencode import Any, All, htmlfill, validators
-from formencode.schema import SimpleFormValidator
-from pylons import request, tmpl_context as c
-from pylons.controllers.util import redirect
-from pylons.decorators import validate
-from pylons.i18n import _
-from repoze.what.plugins.pylonshq import ControllerProtector, ActionProtector
-
-from adhocracy.forms.common import ValidGroup, ValidHTMLColor, ContainsChar
-from adhocracy.model import Badge, Group, meta
-from adhocracy.lib import helpers as h
-from adhocracy.lib.auth.authorization import has_permission
-from adhocracy.lib.auth.csrf import RequireInternalRequest
-from adhocracy.lib.base import BaseController
-from adhocracy.lib.templating import render
-
-
-
-#FIX: Translations
-#FIX: HTML/CSS Error message ist wrong
-def badgeforminvariant(value_dict, state, validator):
-    is_delegateable = value_dict.get('badge_delegateable', "off")
-    is_category = value_dict.get('badge_delegateable_category', "off")
-    if is_category == is_delegateable == "on":
-        return {'state': 'You must not select both "Badge proposal category" and "Badge proposal"'}
-BadgeFormInvariant = SimpleFormValidator(badgeforminvariant)
-
-class BadgeForm(formencode.Schema):
-    allow_extra_fields = True
-    pre_validators = [BadgeFormInvariant,]
-    title = All(validators.String(max=40, not_empty=True),
-                ContainsChar())
-    description = validators.String(max=255)
-    color = ValidHTMLColor()
-    group = Any(validators.Empty, ValidGroup())
-    display_group = validators.StringBoolean(if_missing=False)
-    badge_delegateable = validators.StringBoolean(if_missing=False)
-    badge_delegateable_category = validators.StringBoolean(if_missing=False)
-
-
-class BadgeBaseController(BaseController):
-    """Badge controller base class"""
-
-    @property
-    def base_url(self):
-        return h.site.base_url(instance=c.instance, path='/badge')
-
-    def index(self, format='html'):
-        #require.user.manage()
-        c.badges_users = Badge.all_user(c.instance)
-        c.badges_delegateables = Badge.all_delegateable(c.instance)
-        c.badges_delegateable_categories = Badge.all_delegateable_categories(c.instance)
-        return render("/badge/index.html")
-
-    def _redirect_not_found(self, id):
-        h.flash(_("We cannot find the badge with the id %s") % str(id),
-                'error')
-        redirect(self.base_url)
-
-    def add(self, errors=None):
-        c.form_title = c.save_button = _("Add Badge")
-        c.action_url = self.base_url + '/add'
-        c.groups = meta.Session.query(Group).order_by(Group.group_name).all()
-        return htmlfill.render(render("/badge/form.html"),
-                               defaults=dict(request.params),
-                               errors=errors)
-
-    @RequireInternalRequest()
-    @validate(schema=BadgeForm(), form='add')
-    def create(self):
-        title = self.form_result.get('title').strip()
-        description = self.form_result.get('description').strip()
-        color = self.form_result.get('color').strip()
-        group = self.form_result.get('group')
-        display_group = self.form_result.get('display_group')
-        badge_delegateable = \
-                bool(self.form_result.get('badge_delegateable', ''))
-        badge_delegateable_category = \
-                bool(self.form_result.get('badge_delegateable_category', ''))
-        badge = Badge.create(title, color, description, group, display_group,
-                badge_delegateable, badge_delegateable_category, c.instance )
-        redirect(self.base_url)
-
-    def edit(self, id, errors=None):
-        c.form_title = c.save_button = _("Edit Badge")
-        c.action_url = self.base_url + '/edit/%s' % id
-        c.groups = meta.Session.query(Group).order_by(Group.group_name).all()
-        badge = Badge.by_id(id)
-        if badge is None:
-            self._redirect_not_found(id)
-        group_default = badge.group and badge.group.code or ''
-        defaults = dict(title=badge.title,
-                        description=badge.description,
-                        color=badge.color,
-                        group=group_default,
-                        display_group=badge.display_group,
-                        badge_delegateable=badge.badge_delegateable,
-                        badge_delegateable_category=badge.badge_delegateable_category,
-                        )
-
-        return htmlfill.render(render("/badge/form.html"),
-                               errors=errors,
-                               defaults=defaults)
-
-    @RequireInternalRequest()
-    @validate(schema=BadgeForm(), form='edit')
-    def update(self, id):
-        badge = Badge.by_id(id)
-        if badge is None:
-            self._redirect_not_found(id)
-
-        title = self.form_result.get('title').strip()
-        description = self.form_result.get('description').strip()
-        color = self.form_result.get('color').strip()
-        group = self.form_result.get('group')
-        display_group = self.form_result.get('display_group')
-
-        if group:
-            badge.group = group
-        else:
-            badge.group = None
-        badge.title = title
-        badge.color = color
-        badge.description = description
-        badge.display_group = display_group
-        meta.Session.commit()
-        h.flash(_("Badge changed successfully"), 'success')
-        redirect(self.base_url)
-
-
-@ControllerProtector(has_permission("global.admin"))
-class BadgeglobalController(BadgeBaseController):
-    """Badge controller with security checking"""
-

adhocracy/controllers/badgeinstance.py

-from pylons import tmpl_context as c
-from repoze.what.plugins.pylonshq import ControllerProtector
-from adhocracy.controllers.badgeglobal import BadgeBaseController
-from adhocracy.lib.auth.authorization import has_permission
-from adhocracy.lib import helpers as h
-
-
-@ControllerProtector(has_permission("instance.admin"))
-class BadgeinstanceController(BadgeBaseController):
-    """Badge controller to allow instance admins
-       to change instance badges.
-    """
-
-    @property
-    def base_url(self):
-        return h.entity_url(c.instance) + "/badge"
-

adhocracy/controllers/debug.py

+from pylons import config, request, tmpl_context as c
+
+from adhocracy.lib.base import BaseController
+from adhocracy.lib.helpers import json_loads
+from adhocracy.lib.templating import render
+from adhocracy.model.meta import engine
+
+
+class DebugController(BaseController):
+
+    def explain(self):
+        if not config.get('adhocracy.debug.sql'):
+            raise ValueError('Not in debugging mode')
+        statement = request.params.get('statement')
+        if not statement.lower().startswith('select'):
+            raise ValueError('We explain only select statements')
+        parameters = request.params.get('parameters')
+        c.parameters = json_loads(parameters)
+
+        # collect explain results
+        if engine.name.startswith('sqlite'):
+            explain_query = 'EXPLAIN QUERY PLAN %s' % statement
+        else:
+            explain_query = 'EXPLAIN %s' % statement
+
+        explain_result = engine.execute(explain_query, c.parameters)
+        data_result = engine.execute(statement, c.parameters)
+        c.results = []
+        for (title, result) in (('Explain', explain_result),
+                                ('Data', data_result)):
+            c.results.append({'title': title,
+                              'result': result.fetchall(),
+                              'headers': result.keys()})
+
+        # other data to display
+        c.statement = statement
+        c.duration = float(request.params['duration'])
+
+        return render('/debug/explain.html')

adhocracy/controllers/geo.py

         q = q.filter(Region.admin_level == admin_level)
 
         if BBOX_FILTER_TYPE == USE_POSTGIS:
-            q = q.filter(Region.boundary.intersects(func.setsrid(func.box2d('BOX(%f %f, %f %f)'%(tuple(bbox))), 4326)))
+            q = q.filter(Region.boundary.intersects(func.ST_setsrid(func.box2d('BOX(%f %f, %f %f)'%(tuple(bbox))), 4326)))
 
         if SIMPLIFY_TYPE == USE_POSTGIS:
             # NYI

adhocracy/controllers/instance.py

 from adhocracy.lib import event, helpers as h, logo, pager, sorting, tiles
 from adhocracy.lib.auth import csrf, require
 from adhocracy.lib.base import BaseController
-from adhocracy.lib.event.stats import instance_activity
 from adhocracy.lib.templating import (render, render_json, render_png,
                                       ret_abort, ret_success, render_geojson)
 from adhocracy.lib.util import get_entity_or_abort

adhocracy/controllers/message.py

         to_user(c.page_user, subject, message, headers=headers)
 
         h.flash(_("Your message has been sent. Thanks."), 'success')
-        redirect(h.entity_url(c.page_user))
+        redirect(h.entity_url(c.page_user, instance=c.instance))

adhocracy/controllers/milestone.py

+from datetime import date, datetime, time
 import logging
 
 import formencode
 class MilestoneCreateForm(MilestoneNewForm):
     title = validators.String(max=2000, min=4, not_empty=True)
     text = validators.String(max=60000, min=4, not_empty=True)
+    category = forms.ValidCategoryBadge(if_missing=None, if_empty=None)
     time = forms.ValidDate()
 
 
 class MilestoneUpdateForm(MilestoneEditForm):
     title = validators.String(max=2000, min=4, not_empty=True)
     text = validators.String(max=60000, min=4, not_empty=True)
+    category = forms.ValidCategoryBadge(if_missing=None, if_empty=None)
     time = forms.ValidDate()
 
 
     def index(self, format="html"):
         require.milestone.index()
 
-        c.milestones = model.Milestone.all(instance=c.instance)
-        broken = [m for m in c.milestones if m.time is None]
+        milestones = model.Milestone.all(instance=c.instance)
+        broken = [m for m in milestones if m.time is None]
         for milestone in broken:
-            log.warning('Time of Milestone is None: %s' % h.entity_url(milestone))
-        c.milestones = [m for m in c.milestones if m.time is not None]
-        c.milestones_pager = pager.milestones(c.milestones)
+            log.warning('Time of Milestone is None: %s' %
+                        h.entity_url(milestone))
+        milestones = [m for m in milestones if m.time is not None]
+        today = datetime.combine(date.today(), time(0, 0))
+        past_milestones = [m for m in milestones if m.time < today]
+        c.show_past_milestones = len(past_milestones)
+        c.past_milestones_pager = pager.milestones(past_milestones)
+
+        current_milestones = [m for m in milestones if m not in
+                              past_milestones]
+        c.show_current_milestones = len(current_milestones)
+        c.current_milestones_pager = pager.milestones(current_milestones)
 
         if format == 'json':
             return render_json(c.milestones_pager)
               post_only=False, on_get=True)
     def new(self, errors=None):
         require.milestone.create()
+        c.categories = model.CategoryBadge.all(instance=c.instance)
         defaults = dict(request.params)
         defaults['watch'] = defaults.get('watch', True)
         return htmlfill.render(render("/milestone/new.html"),
     @csrf.RequireInternalRequest(methods=['POST'])
     def create(self, format='html'):
         require.milestone.create()
+        c.categories = model.CategoryBadge.all(instance=c.instance)
         try:
             self.form_result = MilestoneCreateForm().to_python(request.params)
         except Invalid, i:
             return self.new(errors=i.unpack_errors())
+
+        category = self.form_result.get('category')
         milestone = model.Milestone.create(c.instance, c.user,
-                                         self.form_result.get("title"),
-                                         self.form_result.get('text'),
-                                         self.form_result.get('time'))
+                                           self.form_result.get("title"),
+                                           self.form_result.get('text'),
+                                           self.form_result.get('time'),
+                                           category=category)
+
         model.meta.Session.commit()
         watchlist.check_watch(milestone)
         #event.emit(event.T_PROPOSAL_CREATE, c.user, instance=c.instance,
     @validate(schema=MilestoneEditForm(), form="bad_request",
               post_only=False, on_get=True)
     def edit(self, id, errors={}):
+        c.categories = model.CategoryBadge.all(instance=c.instance)
         c.milestone = get_entity_or_abort(model.Milestone, id)
         require.milestone.edit(c.milestone)
+        defaults = {'category': (str(c.milestone.category.id) if
+                                 c.milestone.category else None)}
+        defaults.update(dict(request.params))
         return htmlfill.render(render("/milestone/edit.html"),
-                               defaults=dict(request.params),
+                               defaults=defaults,
                                errors=errors, force_defaults=False)
 
     @RequireInstance
 
         c.milestone.title = self.form_result.get('title')
         c.milestone.text = self.form_result.get('text')
+        c.milestone.category = self.form_result.get('category')
         c.milestone.time = self.form_result.get('time')
-        model.meta.Session.add(c.milestone)
         model.meta.Session.commit()
         watchlist.check_watch(c.milestone)
         #event.emit(event.T_PROPOSAL_EDIT, c.user, instance=c.instance,
             return render_json(c.milestone)
 
         c.tile = tiles.milestone.MilestoneTile(c.milestone)
-        proposals = model.Proposal.by_milestone(c.milestone,
-                instance=c.instance)
-        c.proposals_pager = pager.proposals(proposals, size=10)
-        pages_q = model.meta.Session.query(model.Page)
-        pages_q = pages_q.filter(model.Page.instance==c.instance)
-        pages_q = pages_q.filter(model.Page.function==model.Page.NORM)
-        pages_q = pages_q.filter(model.Page.milestone==c.milestone)
-        c.pages_pager = pager.pages(pages_q.all(), size=10)
+
+        # proposals .. directly assigned
+        by_milestone = model.Proposal.by_milestone(c.milestone,
+                                                   instance=c.instance)
+        # proposals .. with the same category
+        by_category = []
+        if c.milestone.category:
+            by_category = [d for d in c.milestone.category.delegateables
+                           if isinstance(d, model.Proposal)]
+        proposals = list(set(by_milestone + by_category))
+        c.proposals_pager = pager.proposals(proposals, size=20,
+                                            enable_sorts=False)
+        c.show_proposals_pager = len(proposals)
+
+        # pages
+        pages = model.Page.by_milestone(c.milestone,
+                                        instance=c.instance,
+                                        include_deleted=False,
+                                        functions=[model.Page.NORM])
+        c.pages_pager = pager.pages(pages, size=20, enable_sorts=False)
+        c.show_pages_pager = len(pages) and c.instance.use_norms
+
         self._common_metadata(c.milestone)
         c.tutorial_intro = _('tutorial_milestone_details_tab')
         c.tutorial = 'milestone_show'
         h.add_meta("dc.title",
                    text.meta_escape(milestone.title, markdown=False))
         h.add_meta("dc.date",
-                   milestone.time and milestone.time.strftime("%Y-%m-%d") or '')
+                   (milestone.time and milestone.time.strftime("%Y-%m-%d") or
+                    ''))
         h.add_meta("dc.author",
                    text.meta_escape(milestone.creator.name, markdown=False))
-

adhocracy/controllers/openidauth.py

 import logging
-from datetime import datetime
 
 import formencode
 from formencode import validators
 
 from adhocracy import forms, model
 from adhocracy.lib import event, helpers as h
-from adhocracy.lib.auth import require
+from adhocracy.lib.auth import login_user, require
 from adhocracy.lib.auth.csrf import RequireInternalRequest
 from adhocracy.lib.base import BaseController
 from adhocracy.lib.openidstore import create_consumer
 
     def _login(self, user):
         """
-        Raw login giving severe headaches to repoze.who, repoze.what and any
-        bystanding squirrels.
+        log the user in and redirect him to a sane place.
         """
-        identity = {
-            'userdata': '',
-            'repoze.who.userid': str(user.user_name),
-            'timestamp': int(datetime.utcnow().strftime("%s")),
-            'user': user,
-                    }
-        # set up repoze.what
-        who_plugins = request.environ['repoze.who.plugins']
-        authorization_md = who_plugins['authorization_md']
-        authorization_md.add_metadata(request.environ, identity)
-        auth_tkt = request.environ['repoze.who.plugins']['auth_tkt']
-        header = auth_tkt.remember(request.environ, identity)
-        response.headerlist.extend(header)
+        login_user(user, request)
         if c.instance and not user.is_member(c.instance):
             redirect(h.base_url(c.instance,
                      path="/instance/join/%s?%s" % (c.instance.key,
             redirect('/register')
 
     def xrds(self):
-        response.headers['Content-Type'] = "application/xrds+xml; charset=utf-8"
+        response.headers['Content-Type'] = ("application/xrds+xml; "
+                                            "charset=utf-8")
         return render('/openid/xrds.xml')

adhocracy/controllers/page.py

-try:
-    import json
-except ImportError:
-    import simplejson as json
+import json
 import logging
 from operator import itemgetter
 
         require.page.index()
         pages = model.Page.all(instance=c.instance,
                                functions=model.Page.LISTED)
+        if request.params.get('pages_sort', '4') == '4':
+            # crude hack to get only top level pages cause the pager
+            # cannot handle this and we can not pass arguments to the tile
+            # WARNING: This will break if the index of the sort changes.
+            c.is_hierarchical = True
+            pages = [page for page in pages if page.parent == None]
         c.pages_pager = pager.pages(pages)
 
         if format == 'json':

adhocracy/controllers/proposal.py

 import logging
 
 import formencode
-from formencode import Any, htmlfill, Invalid, validators
+from formencode import htmlfill, Invalid, validators
 
 from pylons import request, tmpl_context as c
 from pylons.controllers.util import redirect
     milestone = forms.MaybeMilestone(if_empty=None,
             if_missing=None)
     page = formencode.foreach.ForEach(PageInclusionForm())
-    category = formencode.foreach.ForEach(forms.ValidBadge())
+    category = formencode.foreach.ForEach(forms.ValidCategoryBadge())
     geotag = validators.String(not_empty=False)
 
 
                                  if_missing=False)
     milestone = forms.MaybeMilestone(if_empty=None,
             if_missing=None)
-    category = formencode.foreach.ForEach(forms.ValidBadge())
+    category = formencode.foreach.ForEach(forms.ValidCategoryBadge())
 
 class ProposalGeotagUpdateForm(formencode.Schema):
     geotag = validators.String(not_empty=False)
 
 class DelegateableBadgesForm(formencode.Schema):
     allow_extra_fields = True
-    badge = formencode.foreach.ForEach(forms.ValidBadge())
+    badge = formencode.foreach.ForEach(forms.ValidDelegateableBadge())
 
 
 class ProposalController(BaseController):
             return render_json(c.proposals_pager)
 
         c.tile = tiles.instance.InstanceTile(c.instance)
-        c.badges = model.Badge.all_delegateable()
-        c.instance_badges = model.Badge.all_delegateable(instance=c.instance)
         c.tutorial_intro = _('tutorial_proposal_overview_tab')
         c.tutorial = 'proposal_index'
         return render("/proposal/index.html")
         require.proposal.create()
         c.pages = []
         c.exclude_pages = []
-        c.categories = model.Badge.all_delegateable_categories(c.instance)
+        c.categories = model.CategoryBadge.all(instance=c.instance)
         if 'page' in request.params:
             page = model.Page.find(request.params.get('page'))
             if page and page.function == model.Page.NORM:
             return self.new(errors=i.unpack_errors())
 
         pages = self.form_result.get('page', [])
-        badge = self.form_result.get('category')
         if c.instance.require_selection and len(pages) < 1:
             h.flash(
                 _('Please select norm and propose a change to it.'),
         description.parents = [proposal]
         model.meta.Session.flush()
         proposal.description = description
-        if badge:
-            model.DelegateableBadge(proposal, badge[0], c.user)
+
+        categories = self.form_result.get('category')
+        category = categories[0] if categories else None
+        proposal.set_category(category, c.user)
+
         for page in pages:
             page_text = page.get('text', '')
             page = page.get('id')
         c.text_rows = text.text_rows(c.proposal.description.head)
 
         # all available categories
-        c.categories = model.Badge.all_delegateable_categories(c.instance)
+        c.categories = model.CategoryBadge.all(instance=c.instance)
 
-        # categories for this proposal (single category not assured in db model)
-        categories = [b for b in c.proposal.badges if b.badge_delegateable_category]
-        c.category = categories[0] if len(categories)==1 else None
+        # categories for this proposal
+        # (single category not assured in db model)
+        c.category = c.proposal.category
 
         force_defaults = False
         if errors:
         c.proposal.milestone = self.form_result.get('milestone')
         model.meta.Session.add(c.proposal)
 
-        [c.proposal.badges.remove(b) for b in c.proposal.badges if b.badge_delegateable_category]
-
-        badge = self.form_result.get('category')
-        if len(badge)==1:
-            model.DelegateableBadge(c.proposal, badge[0], c.user)
+        # change the category
+        categories = self.form_result.get('category')
+        category = categories[0] if categories else None
+        c.proposal.set_category(category, c.user)
 
         if self._can_edit_wiki(c.proposal, c.user):
             wiki = self.form_result.get('wiki')
             return True
         return False
 
-    @ActionProtector(authorization.has_permission("global.admin"))
-    def badges(self, id, errors=None):
-        c.badges = model.Badge.all_delegateable() \
-                   + model.Badge.all_delegateable(c.instance)
+    @classmethod
+    def _editable_badges(cls, proposal):
+        '''
+        Return the badges editable that can be assigned by the current
+        user.
+        '''
+        badges = []
+        if can.badge.edit_instance():
+            badges.extend(model.DelegateableBadge.all(instance=c.instance))
+        if can.badge.edit_global():
+            badges.extend(model.DelegateableBadge.all(instance=None))
+        badges = sorted(badges, key=lambda badge: badge.title)
+        return badges
+
+    @ActionProtector(authorization.has_permission("instance.admin"))
+    def badges(self, id, errors=None, format='html'):
         c.proposal = get_entity_or_abort(model.Proposal, id)
+        c.badges = self._editable_badges(c.proposal)
         defaults = {'badge': [str(badge.id) for badge in c.proposal.badges]}
+        if format == 'ajax':
+            checked = [badge.id for badge in c.proposal.badges]
+            json = {'title': c.proposal.title,
+                    'badges': [{
+                        'id': badge.id,
+                        'description': badge.description,
+                        'title': badge.title,
+                        'checked': badge.id in checked} for badge in c.badges]}
+            return render_json(json)
+
         return formencode.htmlfill.render(
             render("/proposal/badges.html"),
             defaults=defaults)
 
     @RequireInternalRequest()
     @validate(schema=DelegateableBadgesForm(), form='badges')
-    @ActionProtector(authorization.has_permission("global.admin"))
-    def update_badges(self, id):
+    @ActionProtector(authorization.has_permission("instance.admin"))
+    @csrf.RequireInternalRequest(methods=['POST'])
+    def update_badges(self, id, format='html'):
         proposal = get_entity_or_abort(model.Proposal, id)
+        editable_badges = self._editable_badges(proposal)
         badges = self.form_result.get('badge')
         redirect_to_proposals = self.form_result.get('redirect_to_proposals')
-        creator = c.user
 
         added = []
         removed = []
+
         for badge in proposal.badges:
+            if badge not in editable_badges:
+                # the user can not edit the badge, so we don't remove it
+                continue
             if badge not in badges:
                 removed.append(badge)
                 proposal.badges.remove(badge)
 
         for badge in badges:
             if badge not in proposal.badges:
-                model.DelegateableBadge(proposal, badge, creator)
+                badge.assign(proposal, c.user)
                 added.append(badge)
 
+        # FIXME: needs commit() cause we do an redirect() which raises
+        # an Exception.
         model.meta.Session.commit()
         post_update(proposal, model.update.UPDATE)
+        if format == 'ajax':
+            obj = {'html': render_def('/badge/tiles.html', 'badges',
+                                      badges=proposal.badges)}
+            return render_json(obj)
         if redirect_to_proposals:
             redirect("/proposal")
         redirect(h.entity_url(proposal))

adhocracy/controllers/search.py

 from pylons.decorators import validate
 from pylons.i18n import _
 
-from adhocracy.forms.search import SearchQueryForm
 from adhocracy.lib import search as libsearch, sorting, tiles
 from adhocracy.lib.auth import require
 from adhocracy.lib.base import BaseController
 log = logging.getLogger(__name__)
 
 
+class SearchQueryForm(formencode.Schema):
+    allow_extra_fields = True
+    serp_q = formencode.validators.String(max=255, min=1, if_empty="",
+                                          if_missing="", not_empty=False)
+
+
 class SearchController(BaseController):
 
     def search_form(self):

adhocracy/controllers/selection.py

-try:
-    import json
-except ImportError:
-    import simplejson as json
+import json
 import logging
 
 import formencode

adhocracy/controllers/twitteroauth.py

         c.user.twitter.delete()
         model.meta.Session.commit()
         redirect(h.entity_url(c.user, member='edit'))
-

adhocracy/controllers/user.py

 from adhocracy import i18n
 from adhocracy.lib import democracy, event, helpers as h, pager
 from adhocracy.lib import  sorting, search as libsearch, tiles, text
-from adhocracy.lib.auth import require
+from adhocracy.lib.auth import require, login_user
 from adhocracy.lib.auth.authorization import has_permission
 from adhocracy.lib.auth.csrf import RequireInternalRequest
 from adhocracy.lib.base import BaseController
 
 class UserBadgesForm(formencode.Schema):
     allow_extra_fields = True
-    badge = ForEach(forms.ValidBadge())
+    badge = ForEach(forms.ValidUserBadge())
 
 
 class UserController(BaseController):
                             'but cannot authenticate him: '
                             '%s (%s)' % (credentials['login'], user))
 
-    @ActionProtector(has_permission("user.edit"))
     def edit(self, id):
         c.page_user = get_entity_or_abort(model.User, id,
                                           instance_filter=False)
         return render("/user/edit.html")
 
     @RequireInternalRequest(methods=['POST'])
-    @ActionProtector(has_permission("user.edit"))
     @validate(schema=UserUpdateForm(), form="edit", post_only=True)
     def update(self, id):
         c.page_user = get_entity_or_abort(model.User, id,
         url = h.base_url(c.instance,
                          path="/user/%s/reset?c=%s" % (c.page_user.user_name,
                                                        c.page_user.reset_code))
-        body = _("you have requested that your password be reset. In order "
-                 "to confirm the validity of your claim, please open the "
-                 "link below in your browser:") + "\r\n\r\n  " + url
+        body = (
+            _("you have requested that your password be reset. In order "
+              "to confirm the validity of your claim, please open the "
+              "link below in your browser:") +
+            "\r\n\r\n  " + url + "\n\n" +
+            _("Your user name to login is: %s") % c.page_user.user_name)
+
         libmail.to_user(c.page_user, _("Reset your password"), body)
         return render("/user/reset_pending.html")
 
             c.page_user.password = new_password
             model.meta.Session.add(c.page_user)
             model.meta.Session.commit()
-            body = (_("your password has been reset. It is now:") +
-                    "\r\n\r\n  " + new_password + "\r\n\r\n" +
-                    _("Please login and change the password in your user "
-                      "settings."))
+            body = (
+                _("your password has been reset. It is now:") +
+                "\r\n\r\n  " + new_password + "\r\n\r\n" +
+                _("Please login and change the password in your user "
+                  "settings.") + "\n\n" +
+                _("Your user name to login is: %s") % c.page_user.user_name
+            )
             libmail.to_user(c.page_user, _("Your new password"), body)
             h.flash(_("Success. You have been sent an email with your new "
                       "password."), 'success')
     def activate(self, id):
         c.page_user = get_entity_or_abort(model.User, id,
                                           instance_filter=False)
-        #require.user.edit(c.page_user)
-        try:
-            if c.page_user.activation_code != self.form_result.get('c'):
-                raise ValueError()
-            c.page_user.activation_code = None
-            model.meta.Session.commit()
-            h.flash(_("Your email has been confirmed."), 'success')
-        except Exception:
-            log.exception("Invalid activation code")
+        code = self.form_result.get('c')
+
+        if c.page_user.activation_code is None:
+            h.flash(_(u'Thank you, The address is already activated.'))
+            redirect(h.entity_url(c.page_user))
+        elif c.page_user.activation_code != code:
             h.flash(_("The activation code is invalid. Please have it "
                       "resent."), 'error')
+            redirect(h.entity_url(c.page_user))
+
+        c.page_user.activation_code = None
+        if code.startswith(model.User.IMPORT_MARKER):
+            # Users imported by admins
+            login_user(c.page_user, request)
+            h.flash(_("Welcome to %s") % h.site.name(), 'success')
+            if c.instance:
+                redirect(h.entity_url(c.instance))
+            else:
+                redirect(h.base_url(None, path='/instance'))
+        else:
+            redirect(h.entity_url(c.page_user))
+            h.flash(_("Your email has been confirmed."), 'success')
+
         redirect(h.entity_url(c.page_user))
 
     @RequireInternalRequest()
             if 'came_from' in session:
                 c.came_from = session.get('came_from')
                 del session['came_from']
-                if isinstance(c.came_from, unicode):
-                    c.came_from = c.came_from.encode('utf-8')
+                if isinstance(c.came_from, str):
+                    c.came_from = unicode(c.came_from, 'utf-8')
             session.save()
 
         #user object
 
     @ActionProtector(has_permission("global.admin"))
     def badges(self, id, errors=None):
-        c.badges = model.Badge.all_user()
-        c.page_user =  get_entity_or_abort(model.User, id)
+        c.badges = model.UserBadge.all(instance=None)
+        c.page_user = get_entity_or_abort(model.User, id)
         instances = c.page_user and c.page_user.instances or []
-        c.instance_badges = [{"label":i.label,
-                              "badges":model.Badge.all_user(instance=i)}\
-                                      for i in instances]
+        c.instance_badges = [
+            {"label": instance.label,
+             "badges": model.UserBadge.all(instance=instance)} for
+            instance in instances]
         defaults = {'badge': [str(badge.id) for badge in c.page_user.badges]}
         return formencode.htmlfill.render(
             render("/user/badges.html"),
 
         for badge in badges:
             if badge not in user.badges:
-                model.UserBadge(user, badge, creator)
+                badge.assign(user, creator)
                 added.append(badge)
 
         model.meta.Session.flush()
-        model.meta.Session.commit()  # FIXME: does not work without.
+        # FIXME: needs commit() cause we do an redirect() which raises
+        # an Exception.
+        model.meta.Session.commit()
         post_update(user, model.update.UPDATE)
         redirect(h.entity_url(user))
 

adhocracy/forms/__init__.py

 from common import UniqueUsername, UniqueEmail, UniqueInstanceKey
 from common import ContainsChar, ContainsUrlPlaceholder, UsersCSV
 from common import ValidDelegateable, ValidProposal, MaybeMilestone
-from common import ValidGroup, ValidRevision, ValidComment, ValidBadge
+from common import ValidGroup, ValidRevision, ValidComment
+from common import ValidUserBadge, ValidCategoryBadge, ValidDelegateableBadge
 from common import ValidWatch, ValidRef, ValidTagging, ValidTag
 from common import ValidPage, ValidText, ValidPageFunction
 from common import ExistingUserName, VariantName, UnusedTitle

adhocracy/forms/common.py

 from StringIO import StringIO
 
 import formencode
+from pylons import tmpl_context as c
 from pylons.i18n import _
 from webhelpers.html import literal
 
+from adhocracy.lib.auth.authorization import has
+
+
 FORBIDDEN_NAMES = ["www", "static", "mail", "edit", "create", "settings",
                    "join", "leave", "control", "test", "support", "page",
                    "proposal", "wiki", "blog", "proposals", "admin", "dl",
                    "hg", "git", "adhocracy", "user", "openid", "auth", "watch",
                    "poll", "delegation", "event", "comment", "root", "search",
                    "tag", "svn", "trac", "lists", "list", "new", "update",
-                   "variant", "provision", "untag"]
+                   "variant", "provision", "untag", "code"]
 
 
 VALIDUSER = re.compile(r"^[a-zA-Z0-9_\-]{3,255}$")
         return value
 
 
-class ValidBadge(formencode.FancyValidator):
+class ValidBadgeInstance(formencode.FancyValidator):
 
     def _to_python(self, value, state):
-        from adhocracy.model import Badge
-        badge = Badge.by_id(value)
+        from adhocracy.model import Instance
+        if has('global.admin'):
+            if value:
+                instance = Instance.find(value)
+                if instance is None:
+                    raise AssertionError("Could not find instance %s" % value)
+                return instance
+            return None
+        elif has('instance.admin') and c.instance:
+            return c.instance
+        raise formencode.Invalid(
+            _("You're not allowed to edit global badges"),
+            value, state)
+
+
+class ValidUserBadge(formencode.FancyValidator):
+
+    def _to_python(self, value, state):
+        from adhocracy.model import UserBadge
+        badge = UserBadge.by_id(value)
+        if not badge:
+            raise formencode.Invalid(
+                _("No Badge ID '%s' exists") % value,
+                value, state)
+        return badge
+
+
+class ValidDelegateableBadge(formencode.FancyValidator):
+
+    def _to_python(self, value, state):
+        from adhocracy.model import DelegateableBadge
+        badge = DelegateableBadge.by_id(value)
+        if not badge:
+            raise formencode.Invalid(
+                _("No Badge ID '%s' exists") % value,
+                value, state)
+        return badge
+
+
+class ValidCategoryBadge(formencode.FancyValidator):
+
+    def _to_python(self, value, state):
+        from adhocracy.model import CategoryBadge
+        badge = CategoryBadge.by_id(value)
         if not badge:
             raise formencode.Invalid(
                 _("No Badge ID '%s' exists") % value,
         reader = csv.DictReader(StringIO(value), fieldnames=fieldnames)
         try:
             for item in reader:
-                error_list = self._check_item(item, reader.line_num)
+                error_list, cleaned_item = self._check_item(item,
+                                                            reader.line_num)
                 if error_list:
                     errors.append((reader.line_num, error_list))
                 if not errors:
-                    items.append(item)
+                    items.append(cleaned_item)
         except csv.Error, E:
             line_content = value.split('\n')[reader.line_num]
             msg = _('Error "%(error)s" while reading line '
 
     def _check_item(self, item, line):
         error_list = []
-        user_name = item.get(USER_NAME, '')
-        email = item.get(EMAIL, '')
+        user_name = item.get(USER_NAME, '').strip()
+        email = item.get(EMAIL, '').strip()
         for (validator, value) in ((USERNAME_VALIDATOR, user_name),
                                    (EMAIL_VALIDATOR, email)):
             try:
         usernames.append(line)
         if len(emails) > 1 or len(usernames) > 1:
             self.duplicates = True
-        return error_list
+        cleaned_item = item.copy()
+        cleaned_item.update({USER_NAME: user_name,
+                             EMAIL: email})
+        return error_list, cleaned_item
 
 
 class ContainsUrlPlaceholder(formencode.FancyValidator):
 
     def _to_python(self, value, state):
-        if not '{url}' in value:
-            raise formencode.Invalid(_('You need to insert "{url}" into the '
-                                       'email text so we can insert a url for '
-                                       'the user where he can set a password'),
-                                     value, state)
+        required = ['{url}', '{user_name}', '{password}']
+        missing = []
+        for s in required:
+            if s not in value:
+                missing.append(s)
+        if missing != []:
+            raise formencode.Invalid(
+                _('You need to insert the following placeholders into '
+                  'the email text so we can insert enough information '
+                  'for the user: %s') % ', '.join(missing),
+                value, state)
         return value

adhocracy/forms/search.py

-import formencode
-from formencode import validators
-
-
-class SearchQueryForm(formencode.Schema):
-    allow_extra_fields = True
-    serp_q = validators.String(max=255, min=1, if_empty="", if_missing="",
-                               not_empty=False)

adhocracy/i18n/__init__.py

 from pylons import config, tmpl_context as c
 
 
-LOCALES = [babel.Locale('de', 'DE'), 
+LOCALES = [babel.Locale('de', 'DE'),
            babel.Locale('en', 'US'),
            babel.Locale('fr', 'FR'),
            babel.Locale('ru', 'RU')]
     fmt = "<time class='ts' datetime='%(iso)sZ'>%(formatted)s</time>"
     formatted = "%s %s" % (format_date(dt), format_time(dt))
     return fmt % dict(iso=dt.isoformat(), formatted=formatted)
-

adhocracy/i18n/adhocracy.pot

 # Copyright (C) 2012 ORGANIZATION
 # This file is distributed under the same license as the adhocracy project.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-#, fuzzy
+##, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: adhocracy 1.2beta2dev\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2012-04-02 14:30+0200\n"
+"POT-Creation-Date: 2012-05-02 08:48+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.6\n"
 
-#: adhocracy/controllers/abuse.py:45 adhocracy/templates/abuse/new.html:11
 msgid "Thank you for helping."
 msgstr ""
 
-#: adhocracy/controllers/badgeglobal.py:57
+msgid "The name with which the user can log in."
+msgstr ""
+
+msgid "The initial password for the user."
+msgstr ""
+
+msgid "An URL for the user to activate his account."
+msgstr ""
+
+msgid "The name that will be displayed to other users."
+msgstr ""
+
+msgid "The email address of the user."
+msgstr ""
+
 #, python-format
 msgid "We cannot find the badge with the id %s"
 msgstr ""
 
-#: adhocracy/controllers/badgeglobal.py:62
 msgid "Add Badge"
 msgstr ""
 
-#: adhocracy/controllers/badgeglobal.py:86
 msgid "Edit Badge"
 msgstr ""
 
-#: adhocracy/controllers/badgeglobal.py:128
 msgid "Badge changed successfully"
 msgstr ""
 
-#: adhocracy/controllers/comment.py:66 adhocracy/controllers/comment.py:211
-#: adhocracy/controllers/delegation.py:118 adhocracy/controllers/page.py:473
-#: adhocracy/controllers/proposal.py:300 adhocracy/controllers/search.py:49
-#: adhocracy/controllers/tag.py:80 adhocracy/controllers/user.py:548
-#: adhocracy/lib/pager.py:234 adhocracy/lib/pager.py:261 adhocracy/lib/pager.py:270
-#: adhocracy/lib/pager.py:281 adhocracy/lib/pager.py:292 adhocracy/lib/pager.py:299
-#: adhocracy/lib/pager.py:306 adhocracy/lib/pager.py:313 adhocracy/lib/pager.py:966
-#: adhocracy/lib/pager.py:980
 msgid "oldest"
 msgstr ""
 
-#: adhocracy/controllers/comment.py:67 adhocracy/controllers/comment.py:212
-#: adhocracy/controllers/delegation.py:119 adhocracy/controllers/page.py:474
-#: adhocracy/controllers/proposal.py:301 adhocracy/controllers/search.py:50
-#: adhocracy/controllers/tag.py:81 adhocracy/controllers/user.py:549
-#: adhocracy/lib/pager.py:235 adhocracy/lib/pager.py:246 adhocracy/lib/pager.py:260
-#: adhocracy/lib/pager.py:272 adhocracy/lib/pager.py:282 adhocracy/lib/pager.py:293
-#: adhocracy/lib/pager.py:300 adhocracy/lib/pager.py:307 adhocracy/lib/pager.py:314
-#: adhocracy/lib/pager.py:967 adhocracy/lib/pager.py:981 adhocracy/lib/pager.py:995
 msgid "newest"
 msgstr ""
 
-#: adhocracy/controllers/comment.py:113 adhocracy/controllers/comment.py:250
 #, python-format
 msgid "Comment topic has no variant %s"
 msgstr ""
 
-#: adhocracy/controllers/comment.py:201
 msgid "The comment has been deleted."
 msgstr ""
 
-#: adhocracy/controllers/comment.py:230
-msgid ""
-"You're trying to revert to a revision which is not partri of this comments "
-"history"
-msgstr ""
-
-#: adhocracy/controllers/comment.py:240
+msgid "You're trying to revert to a revision which is not partri of this comments history"
+msgstr ""
+
 msgid "The comment has been reverted."
 msgstr ""
 
-#: adhocracy/controllers/comment.py:246
 msgid "Wrong topic"
 msgstr ""
 
-#: adhocracy/controllers/delegation.py:64
 msgid "Invalid delegation recipient"
 msgstr ""
 
-#: adhocracy/controllers/delegation.py:71
 #, python-format
 msgid "You have already delegated voting to %s in %s"
 msgstr ""
 
-#: adhocracy/controllers/delegation.py:94
 #, python-format
 msgid "Cannot access delegation %(id)s"
 msgstr ""
 
-#: adhocracy/controllers/delegation.py:102
 msgid "The delegation is now revoked."
 msgstr ""
 
-#: adhocracy/controllers/error.py:44 adhocracy/templates/error/http.html:2
-#: adhocracy/templates/error/http.html:6
 #, python-format
 msgid "Error %s"
 msgstr ""
 
-#: adhocracy/controllers/instance.py:71
-msgid ""
-"An index of instances run at this site. Select which ones you would like to "
-"join and participate!"
-msgstr ""
-
-#: adhocracy/controllers/instance.py:133
+msgid "An index of instances run at this site. Select which ones you would like to join and participate!"
+msgstr ""
+
 msgid "tutorial_instance_show_intro"
 msgstr ""
 
-#: adhocracy/controllers/instance.py:149
 #, python-format
 msgid "%s News"
 msgstr ""
 
-#: adhocracy/controllers/instance.py:151
 #, python-format
 msgid "News from %s"
 msgstr ""
 
-#: adhocracy/controllers/instance.py:271
 #, python-format
 msgid "The instance %s has been deleted."
 msgstr ""
 
-#: adhocracy/controllers/instance.py:290
 #, python-format
 msgid "Welcome to %(instance)s"
 msgstr ""
 
-#: adhocracy/controllers/instance.py:307
 #, python-format
 msgid "You're not a member of %(instance)s."
 msgstr ""
 
-#: adhocracy/controllers/instance.py:312
 #, python-format
 msgid "You're the founder of %s, cannot leave."
 msgstr ""
 
-#: adhocracy/controllers/instance.py:329
 #, python-format
 msgid "You've left %(instance)s."
 msgstr ""
 
-#: adhocracy/controllers/instance.py:334
 msgid "You cannot manipulate one instance from within another instance."
 msgstr ""
 
-#: adhocracy/controllers/message.py:53
 #, python-format
 msgid "[%s] Message from %s: %s"
 msgstr ""
 
-#: adhocracy/controllers/message.py:57
 msgid "Your message has been sent. Thanks."
 msgstr ""
 
-#: adhocracy/controllers/milestone.py:67
 msgid "tutorial_milestones_tab"
 msgstr ""