Update dataset_form.html
Copy new_package_form.html from CKAN core over to dataset_form.html,
then add the Musical Genre and Composers vocabs back in.
--- a/README.rst
+++ b/README.rst
@@ -13,6 +13,11 @@
* A custom Package edit form
+* A custom Group edit form
+
+* A plugin that allows for custom forms to be used for datasets based on
+ their "type".
+
* A custom User registration and edition form
* Some simple template customisations
@@ -26,7 +31,6 @@
Then activate it by setting ``ckan.plugins = example`` in your main ``ini``-file.
-
Orientation
===========
@@ -34,7 +38,7 @@
* To understand the nuts and bolts of this file, which is a CKAN
*Extension*, read in conjunction with the "Extension
- documentation": http://docs.ckan.org/en/latest/plugins.html
+ documentation": http://docs.ckan.org/en/latest/extensions.html
* One thing the extension does is set the values of
``extra_public_paths`` and ``extra_template_paths`` in the CKAN
@@ -56,6 +60,21 @@
* The custom package edit form at ``package_form.py`` follows a deprecated
way to make a form (using FormAlchemy). This part of the Example Theme needs
updating. In the meantime, follow the instructions at:
- http://readthedocs.org/docs/ckan/en/latest/forms.html
+ http://docs.ckan.org/en/latest/forms.html
+
+Example Tags With Vocabularies
+==============================
+
+To add example tag vocabulary data to the database, from the ckanext-example directory run:
+
+::
+
+ paster example create-example-vocabs -c <path to your ckan config file>
+
+This data can be removed with
+
+::
+
+ paster example clean -c <path to your ckan config file>
--- /dev/null
+++ b/ckanext/example/commands.py
@@ -1,1 +1,83 @@
+from ckan import model
+from ckan.lib.cli import CkanCommand
+from ckan.logic import get_action, NotFound
+import forms
+import logging
+log = logging.getLogger()
+
+
+class ExampleCommand(CkanCommand):
+ '''
+ CKAN Example Extension
+
+ Usage::
+
+ paster example create-example-vocabs -c <path to config file>
+
+ paster example clean -c <path to config file>
+ - Remove all data created by ckanext-example
+
+ The commands should be run from the ckanext-example directory.
+ '''
+ summary = __doc__.split('\n')[0]
+ usage = __doc__
+
+ def command(self):
+ '''
+ Parse command line arguments and call appropriate method.
+ '''
+ if not self.args or self.args[0] in ['--help', '-h', 'help']:
+ print ExampleCommand.__doc__
+ return
+
+ cmd = self.args[0]
+ self._load_config()
+
+ if cmd == 'create-example-vocabs':
+ self.create_example_vocabs()
+ if cmd == 'clean':
+ self.clean()
+ else:
+ log.error('Command "%s" not recognized' % (cmd,))
+
+ def create_example_vocabs(self):
+ '''
+ Adds example vocabularies to the database if they don't already exist.
+ '''
+ user = get_action('get_site_user')({'model': model, 'ignore_auth': True}, {})
+ context = {'model': model, 'session': model.Session, 'user': user['name']}
+
+ try:
+ data = {'id': forms.GENRE_VOCAB}
+ get_action('vocabulary_show')(context, data)
+ log.info("Example genre vocabulary already exists, skipping.")
+ except NotFound:
+ log.info("Creating vocab %s" % forms.GENRE_VOCAB)
+ data = {'name': forms.GENRE_VOCAB}
+ vocab = get_action('vocabulary_create')(context, data)
+ log.info("Adding tag %s to vocab %s" % ('jazz', forms.GENRE_VOCAB))
+ data = {'name': 'jazz', 'vocabulary_id': vocab['id']}
+ get_action('tag_create')(context, data)
+ log.info("Adding tag %s to vocab %s" % ('soul', forms.GENRE_VOCAB))
+ data = {'name': 'soul', 'vocabulary_id': vocab['id']}
+ get_action('tag_create')(context, data)
+
+ try:
+ data = {'id': forms.COMPOSER_VOCAB}
+ get_action('vocabulary_show')(context, data)
+ log.info("Example composer vocabulary already exists, skipping.")
+ except NotFound:
+ log.info("Creating vocab %s" % forms.COMPOSER_VOCAB)
+ data = {'name': forms.COMPOSER_VOCAB}
+ vocab = get_action('vocabulary_create')(context, data)
+ log.info("Adding tag %s to vocab %s" % ('Bob Mintzer', forms.COMPOSER_VOCAB))
+ data = {'name': 'Bob Mintzer', 'vocabulary_id': vocab['id']}
+ get_action('tag_create')(context, data)
+ log.info("Adding tag %s to vocab %s" % ('Steve Lewis', forms.COMPOSER_VOCAB))
+ data = {'name': 'Steve Lewis', 'vocabulary_id': vocab['id']}
+ get_action('tag_create')(context, data)
+
+ def clean(self):
+ log.error("Clean command not yet implemented")
+
--- a/ckanext/example/controller_plugin.py
+++ /dev/null
@@ -1,223 +1,1 @@
-import logging
-from ckan.lib.base import BaseController, render, c, model, abort, request
-from ckan.lib.base import redirect, _, config, h
-import ckan.logic.action.create as create
-import ckan.logic.action.update as update
-import ckan.logic.action.get as get
-from ckan.logic.converters import date_to_db, date_to_form, convert_to_extras, convert_from_extras
-from ckan.lib.navl.dictization_functions import DataError, flatten_dict, unflatten
-from ckan.logic import NotFound, NotAuthorized, ValidationError
-from ckan.logic import tuplize_dict, clean_dict, parse_params
-from ckan.logic.schema import package_form_schema
-from ckan.plugins import IDatasetForm
-from ckan.plugins import implements, SingletonPlugin
-from ckan.lib.package_saver import PackageSaver
-from ckan.lib.field_types import DateType, DateConvertError
-from ckan.authz import Authorizer
-from ckan.lib.navl.dictization_functions import Invalid
-from ckanext.dgu.forms.package_gov_fields import GeoCoverageType
-from ckan.lib.navl.dictization_functions import validate, missing
-import ckan.logic.validators as val
-import ckan.logic.schema as default_schema
-from ckan.lib.navl.validators import (ignore_missing,
- not_empty,
- empty,
- ignore,
- keep_extras,
- )
-log = logging.getLogger(__name__)
-
-geographic_granularity = [('', ''),
- ('national', 'national'),
- ('regional', 'regional'),
- ('local authority', 'local authority'),
- ('ward', 'ward'),
- ('point', 'point'),
- ('other', 'other - please specify')]
-
-update_frequency = [('', ''),
- ('never', 'never'),
- ('discontinued', 'discontinued'),
- ('annual', 'annual'),
- ('quarterly', 'quarterly'),
- ('monthly', 'monthly'),
- ('other', 'other - please specify')]
-
-temporal_granularity = [("",""),
- ("year","year"),
- ("quarter","quarter"),
- ("month","month"),
- ("week","week"),
- ("day","day"),
- ("hour","hour"),
- ("point","point"),
- ("other","other - please specify")]
-
-
-class ExamplePackageController(SingletonPlugin):
-
- implements(IDatasetForm, inherit=True)
-
- def package_form(self):
- return 'controller/package_plugin.html'
-
- def is_fallback(self):
- """
- Returns true iff this provides the fallback behaviour, when no other
- plugin instance matches a package's type.
-
- As this is not the fallback controller we should return False. If
- we were wanting to act as the fallback, we'd return True
- """
- return False
-
- def package_types(self):
- """
- Returns an iterable of package type strings.
-
- If a request involving a package of one of those types is made, then
- this plugin instance will be delegated to.
-
- There must only be one plugin registered to each package type. Any
- attempts to register more than one plugin instance to a given package
- type will raise an exception at startup.
- """
- return ["example"]
-
- def _setup_template_variables(self, context, data_dict=None):
- c.licences = [('', '')] + model.Package.get_license_options()
- c.geographic_granularity = geographic_granularity
- c.update_frequency = update_frequency
- c.temporal_granularity = temporal_granularity
-
- c.publishers = self.get_publishers()
-
- c.is_sysadmin = Authorizer().is_sysadmin(c.user)
- c.resource_columns = model.Resource.get_columns()
-
- ## This is messy as auths take domain object not data_dict
- pkg = context.get('package') or c.pkg
- if pkg:
- c.auth_for_change_state = Authorizer().am_authorized(
- c, model.Action.CHANGE_STATE, pkg)
-
- def _form_to_db_schema(self):
-
- schema = {
- 'title': [not_empty, unicode],
- 'name': [not_empty, unicode, val.name_validator, val.package_name_validator],
- 'notes': [not_empty, unicode],
-
- 'date_released': [date_to_db, convert_to_extras],
- 'date_updated': [date_to_db, convert_to_extras],
- 'date_update_future': [date_to_db, convert_to_extras],
- 'update_frequency': [use_other, unicode, convert_to_extras],
- 'update_frequency-other': [],
- 'precision': [unicode, convert_to_extras],
- 'geographic_granularity': [use_other, unicode, convert_to_extras],
- 'geographic_granularity-other': [],
- 'geographic_coverage': [ignore_missing, convert_geographic_to_db, convert_to_extras],
- 'temporal_granularity': [use_other, unicode, convert_to_extras],
- 'temporal_granularity-other': [],
- 'temporal_coverage-from': [date_to_db, convert_to_extras],
- 'temporal_coverage-to': [date_to_db, convert_to_extras],
- 'url': [unicode],
- 'taxonomy_url': [unicode, convert_to_extras],
-
- 'resources': default_schema.default_resource_schema(),
-
- 'published_by': [not_empty, unicode, convert_to_extras],
- 'published_via': [ignore_missing, unicode, convert_to_extras],
- 'author': [ignore_missing, unicode],
- 'author_email': [ignore_missing, unicode],
- 'mandate': [ignore_missing, unicode, convert_to_extras],
- 'license_id': [ignore_missing, unicode],
- 'tag_string': [ignore_missing, val.tag_string_convert],
- 'national_statistic': [ignore_missing, convert_to_extras],
- 'state': [val.ignore_not_admin, ignore_missing],
-
- 'log_message': [unicode, val.no_http],
-
- '__extras': [ignore],
- '__junk': [empty],
- }
- return schema
-
- def _db_to_form_schema(data):
- schema = {
- 'date_released': [convert_from_extras, ignore_missing, date_to_form],
- 'date_updated': [convert_from_extras, ignore_missing, date_to_form],
- 'date_update_future': [convert_from_extras, ignore_missing, date_to_form],
- 'update_frequency': [convert_from_extras, ignore_missing, extract_other(update_frequency)],
- 'precision': [convert_from_extras, ignore_missing],
- 'geographic_granularity': [convert_from_extras, ignore_missing, extract_other(geographic_granularity)],
- 'geographic_coverage': [convert_from_extras, ignore_missing, convert_geographic_to_form],
- 'temporal_granularity': [convert_from_extras, ignore_missing, extract_other(temporal_granularity)],
- 'temporal_coverage-from': [convert_from_extras, ignore_missing, date_to_form],
- 'temporal_coverage-to': [convert_from_extras, ignore_missing, date_to_form],
- 'taxonomy_url': [convert_from_extras, ignore_missing],
-
- 'resources': default_schema.default_resource_schema(),
- 'extras': {
- 'key': [],
- 'value': [],
- '__extras': [keep_extras]
- },
- 'tags': {
- '__extras': [keep_extras]
- },
-
- 'published_by': [convert_from_extras, ignore_missing],
- 'published_via': [convert_from_extras, ignore_missing],
- 'mandate': [convert_from_extras, ignore_missing],
- 'national_statistic': [convert_from_extras, ignore_missing],
- '__extras': [keep_extras],
- '__junk': [ignore],
- }
- return schema
-
- def _check_data_dict(self, data_dict):
- return
-
- def get_publishers(self):
- return [('pub1', 'pub2')]
-
-
-def use_other(key, data, errors, context):
-
- other_key = key[-1] + '-other'
- other_value = data.get((other_key,), '').strip()
- if other_value:
- data[key] = other_value
-
-def extract_other(option_list):
-
- def other(key, data, errors, context):
- value = data[key]
- if value in dict(option_list).keys():
- return
- elif value is missing:
- data[key] = ''
- return
- else:
- data[key] = 'other'
- other_key = key[-1] + '-other'
- data[(other_key,)] = value
- return other
-
-def convert_geographic_to_db(value, context):
-
- if isinstance(value, list):
- regions = value
- elif value:
- regions = [value]
- else:
- regions = []
-
- return GeoCoverageType.get_instance().form_to_db(regions)
-
-def convert_geographic_to_form(value, context):
-
- return GeoCoverageType.get_instance().db_to_form(value)
-
--- /dev/null
+++ b/ckanext/example/forms.py
@@ -1,1 +1,265 @@
-
+import os
+import logging
+from pylons import tmpl_context as c
+from ckan.authz import Authorizer
+from ckan.logic.converters import convert_to_extras,\
+ convert_from_extras, convert_to_tags, convert_from_tags, free_tags_only
+from ckan.logic import get_action, NotFound
+from ckan.logic.schema import package_form_schema, group_form_schema
+from ckan.lib.base import c, model
+from ckan.plugins import IDatasetForm, IGroupForm, IConfigurer
+from ckan.plugins import IGenshiStreamFilter
+from ckan.plugins import implements, SingletonPlugin
+from ckan.lib.navl.validators import ignore_missing, keep_extras
+import ckan.lib.plugins
+
+log = logging.getLogger(__name__)
+
+GENRE_VOCAB = u'genre_vocab'
+COMPOSER_VOCAB = u'composer_vocab'
+
+
+class ExampleGroupForm(SingletonPlugin):
+ """This plugin demonstrates how a class packaged as a CKAN
+ extension might extend CKAN behaviour by providing custom forms
+ based on the type of a Group.
+
+ In this case, we implement two extension interfaces to provide custom
+ forms for specific types of group.
+
+ - ``IConfigurer`` allows us to override configuration normally
+ found in the ``ini``-file. Here we use it to specify where the
+ form templates can be found.
+
+ - ``IGroupForm`` allows us to provide a custom form for a dataset
+ based on the 'type' that may be set for a group. Where the
+ 'type' matches one of the values in group_types then this
+ class will be used.
+ """
+ implements(IGroupForm, inherit=True)
+ implements(IConfigurer, inherit=True)
+
+ def update_config(self, config):
+ """
+ This IConfigurer implementation causes CKAN to look in the
+ ```templates``` directory when looking for the group_form()
+ """
+ here = os.path.dirname(__file__)
+ rootdir = os.path.dirname(os.path.dirname(here))
+ template_dir = os.path.join(rootdir, 'ckanext',
+ 'example', 'theme', 'templates')
+ config['extra_template_paths'] = ','.join([template_dir,
+ config.get('extra_template_paths', '')])
+
+ def group_form(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered. e.g. "forms/group_form.html".
+ """
+ return 'forms/group_form.html'
+
+ def group_types(self):
+ """
+ Returns an iterable of group type strings.
+
+ If a request involving a group of one of those types is made, then
+ this plugin instance will be delegated to.
+
+ There must only be one plugin registered to each group type. Any
+ attempts to register more than one plugin instance to a given group
+ type will raise an exception at startup.
+ """
+ return ["testgroup"]
+
+ def is_fallback(self):
+ """
+ Returns true iff this provides the fallback behaviour, when no other
+ plugin instance matches a group's type.
+
+ As this is not the fallback controller we should return False. If
+ we were wanting to act as the fallback, we'd return True
+ """
+ return False
+
+ def form_to_db_schema(self):
+ """
+ Returns the schema for mapping group data from a form to a format
+ suitable for the database.
+ """
+ return group_form_schema()
+
+ def db_to_form_schema(self):
+ """
+ Returns the schema for mapping group data from the database into a
+ format suitable for the form (optional)
+ """
+ return {}
+
+ def check_data_dict(self, data_dict):
+ """
+ Check if the return data is correct.
+
+ raise a DataError if not.
+ """
+
+ def setup_template_variables(self, context, data_dict):
+ """
+ Add variables to c just prior to the template being rendered.
+ """
+
+
+class ExampleDatasetForm(SingletonPlugin, ckan.lib.plugins.DefaultDatasetForm):
+ """This plugin demonstrates how a theme packaged as a CKAN
+ extension might extend CKAN behaviour.
+
+ In this case, we implement three extension interfaces:
+
+ - ``IConfigurer`` allows us to override configuration normally
+ found in the ``ini``-file. Here we use it to specify where the
+ form templates can be found.
+ - ``IDatasetForm`` allows us to provide a custom form for a dataset
+ based on the type_name that may be set for a package. Where the
+ type_name matches one of the values in package_types then this
+ class will be used.
+ """
+ implements(IDatasetForm, inherit=True)
+ implements(IConfigurer, inherit=True)
+ implements(IGenshiStreamFilter, inherit=True)
+
+ def update_config(self, config):
+ """
+ This IConfigurer implementation causes CKAN to look in the
+ ```templates``` directory when looking for the package_form()
+ """
+ here = os.path.dirname(__file__)
+ rootdir = os.path.dirname(os.path.dirname(here))
+ template_dir = os.path.join(rootdir, 'ckanext',
+ 'example', 'theme', 'templates')
+ config['extra_template_paths'] = ','.join([template_dir,
+ config.get('extra_template_paths', '')])
+
+ def package_form(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered. e.g. "package/new_package_form.html".
+ """
+ return 'forms/dataset_form.html'
+
+ def is_fallback(self):
+ """
+ Returns true iff this provides the fallback behaviour, when no other
+ plugin instance matches a package's type.
+
+ As this is not the fallback controller we should return False. If
+ we were wanting to act as the fallback, we'd return True
+ """
+ return True
+
+ def package_types(self):
+ """
+ Returns an iterable of package type strings.
+
+ If a request involving a package of one of those types is made, then
+ this plugin instance will be delegated to.
+
+ There must only be one plugin registered to each package type. Any
+ attempts to register more than one plugin instance to a given package
+ type will raise an exception at startup.
+ """
+ return ["example_dataset_form"]
+
+ def setup_template_variables(self, context, data_dict=None):
+ """
+ Adds variables to c just prior to the template being rendered that can
+ then be used within the form
+ """
+ c.licences = [('', '')] + model.Package.get_license_options()
+ c.publishers = [('Example publisher', 'Example publisher 2')]
+ c.is_sysadmin = Authorizer().is_sysadmin(c.user)
+ c.resource_columns = model.Resource.get_columns()
+ try:
+ c.genre_tags = get_action('tag_list')(context, {'vocabulary_id': GENRE_VOCAB})
+ c.composer_tags = get_action('tag_list')(context, {'vocabulary_id': COMPOSER_VOCAB})
+ except NotFound:
+ c.vocab_tags = None
+ c.composer_tags = None
+
+ ## This is messy as auths take domain object not data_dict
+ pkg = context.get('package') or c.pkg
+ if pkg:
+ c.auth_for_change_state = Authorizer().am_authorized(
+ c, model.Action.CHANGE_STATE, pkg)
+
+ def form_to_db_schema(self):
+ """
+ Returns the schema for mapping package data from a form to a format
+ suitable for the database.
+ """
+ schema = package_form_schema()
+ schema.update({
+ 'published_by': [ignore_missing, unicode, convert_to_extras],
+ 'genre_tags': [ignore_missing, convert_to_tags(GENRE_VOCAB)],
+ 'composer_tags': [ignore_missing, convert_to_tags(COMPOSER_VOCAB)]
+ })
+ return schema
+
+ def db_to_form_schema(self):
+ """
+ Returns the schema for mapping package data from the database into a
+ format suitable for the form (optional)
+ """
+ schema = package_form_schema()
+ schema.update({
+ 'tags': {
+ '__extras': [keep_extras, free_tags_only]
+ },
+ 'genre_tags_selected': [
+ convert_from_tags(GENRE_VOCAB), ignore_missing
+ ],
+ 'composer_tags_selected': [
+ convert_from_tags(COMPOSER_VOCAB), ignore_missing
+ ],
+ 'published_by': [convert_from_extras, ignore_missing],
+ })
+ return schema
+
+ def check_data_dict(self, data_dict):
+ """
+ Check if the return data is correct and raises a DataError if not.
+ """
+ return
+
+ def filter(self, stream):
+ # Add vocab tags to the bottom of the sidebar.
+ from pylons import request
+ from genshi.filters import Transformer
+ from genshi.input import HTML
+ routes = request.environ.get('pylons.routes_dict')
+ context = {'model': model}
+ if routes.get('controller') == 'package' \
+ and routes.get('action') == 'read':
+ for vocab in (GENRE_VOCAB, COMPOSER_VOCAB):
+ try:
+ vocab = get_action('vocabulary_show')(context, {'id': vocab})
+ vocab_tags = [t for t in c.pkg_dict.get('tags', [])
+ if t.get('vocabulary_id') == vocab['id']]
+ except NotFound:
+ vocab_tags = None
+
+ if not vocab_tags:
+ continue
+
+ html = '<li class="sidebar-section">'
+ if vocab['name'] == GENRE_VOCAB:
+ html = html + '<h3>Musical Genre</h3>'
+ elif vocab['name'] == COMPOSER_VOCAB:
+ html = html + '<h3>Composer</h3>'
+ html = html + '<ul class="tags clearfix">'
+ for tag in vocab_tags:
+ html = html + '<li>%s</li>' % tag['name']
+ html = html + "</ul></li>"
+ stream = stream | Transformer(
+ "//div[@id='sidebar']//ul[@class='widget-list']"
+ ).append(HTML(html))
+ return stream
+
--- a/ckanext/example/plugin.py
+++ b/ckanext/example/plugin.py
@@ -1,6 +1,8 @@
import os
from logging import getLogger
+from pylons import request
+from genshi.input import HTML
from genshi.filters.transform import Transformer
from ckan.plugins import implements, SingletonPlugin
@@ -21,9 +23,11 @@
found in the ``ini``-file. Here we use it to specify the site
title, and to tell CKAN to look in this package for templates
and resources that customise the core look and feel.
+
- ``IGenshiStreamFilter`` allows us to filter and transform the
HTML stream just before it is rendered. In this case we use
it to rename "frob" to "foobar"
+
- ``IRoutes`` allows us to add new URLs, or override existing
URLs. In this example we use it to override the default
``/register`` behaviour with a custom controller
@@ -47,8 +51,7 @@
our_public_dir = os.path.join(rootdir, 'ckanext',
'example', 'theme', 'public')
template_dir = os.path.join(rootdir, 'ckanext',
- 'example', 'theme',
- 'templates')
+ 'example', 'theme', 'templates')
# set our local template and resource overrides
config['extra_public_paths'] = ','.join([our_public_dir,
config.get('extra_public_paths', '')])
@@ -68,9 +71,26 @@
This example filter renames 'frob' to 'foobar' (this string is
found in the custom ``home/index.html`` template provided as
part of the package).
+
+ It also adds the chosen JQuery plugin to the page if viewing the
+ dataset edit page (provides a better UX for working with tags with vocabularies)
"""
stream = stream | Transformer('//p[@id="examplething"]/text()')\
.substitute(r'frob', r'foobar')
+
+ routes = request.environ.get('pylons.routes_dict')
+ if routes.get('controller') == 'package' \
+ and routes.get('action') == 'edit':
+ stream = stream | Transformer('head').append(HTML(
+ '<link rel="stylesheet" href="/css/chosen.css" />'
+ ))
+ stream = stream | Transformer('body').append(HTML(
+ '''
+ <script src="/scripts/chosen.jquery.min.js" type="text/javascript"></script>'
+ <script type="text/javascript">$(".chzn-select").chosen();</script>
+ '''
+ ))
+
return stream
def before_map(self, map):
Binary files /dev/null and b/ckanext/example/theme/public/css/chosen-sprite.png differ
--- /dev/null
+++ b/ckanext/example/theme/public/css/chosen.css
@@ -1,1 +1,390 @@
-
+/* @group Base */
+.chzn-container {
+ font-size: 13px;
+ position: relative;
+ display: inline-block;
+ zoom: 1;
+ *display: inline;
+}
+.chzn-container .chzn-drop {
+ background: #fff;
+ border: 1px solid #aaa;
+ border-top: 0;
+ position: absolute;
+ top: 29px;
+ left: 0;
+ -webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15);
+ -moz-box-shadow : 0 4px 5px rgba(0,0,0,.15);
+ -o-box-shadow : 0 4px 5px rgba(0,0,0,.15);
+ box-shadow : 0 4px 5px rgba(0,0,0,.15);
+ z-index: 999;
+}
+/* @end */
+
+/* @group Single Chosen */
+.chzn-container-single .chzn-single {
+ background-color: #ffffff;
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0 );
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
+ background-image: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background-image: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background-image: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background-image: -ms-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background-image: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ -webkit-border-radius: 5px;
+ -moz-border-radius : 5px;
+ border-radius : 5px;
+ -moz-background-clip : padding;
+ -webkit-background-clip: padding-box;
+ background-clip : padding-box;
+ border: 1px solid #aaaaaa;
+ -webkit-box-shadow: 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1);
+ -moz-box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1);
+ box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1);
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ position: relative;
+ height: 23px;
+ line-height: 24px;
+ padding: 0 0 0 8px;
+ color: #444444;
+ text-decoration: none;
+}
+.chzn-container-single .chzn-single span {
+ margin-right: 26px;
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ -o-text-overflow: ellipsis;
+ -ms-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+}
+.chzn-container-single .chzn-single abbr {
+ display: block;
+ position: absolute;
+ right: 26px;
+ top: 6px;
+ width: 12px;
+ height: 13px;