Skip to content

Commit

Permalink
Merge branch 'master' into 2489-admin-config-tests
Browse files Browse the repository at this point in the history
Conflicts:
	ckan/templates/admin/config.html
	ckan/tests/controllers/test_admin.py
  • Loading branch information
joetsoi committed Jul 6, 2015
2 parents bba552d + bbdae4e commit 840f335
Show file tree
Hide file tree
Showing 57 changed files with 1,850 additions and 221 deletions.
2 changes: 1 addition & 1 deletion ckan/__init__.py
@@ -1,4 +1,4 @@
__version__ = '2.4a'
__version__ = '2.5.0a'

__description__ = 'CKAN Software'
__long_description__ = \
Expand Down
22 changes: 21 additions & 1 deletion ckan/config/environment.py
Expand Up @@ -233,23 +233,31 @@ def genshi_lookup_attr(cls, obj, key):


# A mapping of config settings that can be overridden by env vars.
# Note: Do not remove the following lines, they are used in the docs
# Start CONFIG_FROM_ENV_VARS
CONFIG_FROM_ENV_VARS = {
'sqlalchemy.url': 'CKAN_SQLALCHEMY_URL',
'ckan.datastore.write_url': 'CKAN_DATASTORE_WRITE_URL',
'ckan.datastore.read_url': 'CKAN_DATASTORE_READ_URL',
'solr_url': 'CKAN_SOLR_URL',
'ckan.site_id': 'CKAN_SITE_ID',
'ckan.site_url': 'CKAN_SITE_URL',
'ckan.storage_path': 'CKAN_STORAGE_PATH',
'ckan.datapusher.url': 'CKAN_DATAPUSHER_URL',
'smtp.server': 'CKAN_SMTP_SERVER',
'smtp.starttls': 'CKAN_SMTP_STARTTLS',
'smtp.user': 'CKAN_SMTP_USER',
'smtp.password': 'CKAN_SMTP_PASSWORD',
'smtp.mail_from': 'CKAN_SMTP_MAIL_FROM'
}
# End CONFIG_FROM_ENV_VARS


def update_config():
''' This code needs to be run when the config is changed to take those
changes into account. '''
changes into account. It is called whenever a plugin is loaded as the
plugin might have changed the config values (for instance it might
change ckan.site_url) '''

for plugin in p.PluginImplementations(p.IConfigurer):
# must do update in place as this does not work:
Expand All @@ -274,6 +282,18 @@ def update_config():
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

site_url = config.get('ckan.site_url', '')
if not site_url:
raise RuntimeError(
'ckan.site_url is not configured and it must have a value.'
' Please amend your .ini file.')
if not site_url.lower().startswith('http'):
raise RuntimeError(
'ckan.site_url should be a full URL, including the schema '
'(http or https)')

# Remove backslash from site_url if present
config['ckan.site_url'] = config['ckan.site_url'].rstrip('/')

ckan_host = config['ckan.host'] = urlparse(site_url).netloc
if config.get('ckan.site_id') is None:
if ':' in ckan_host:
Expand Down
2 changes: 2 additions & 0 deletions ckan/config/routing.py
Expand Up @@ -174,6 +174,8 @@ def make_map():
m.connect('/util/resource/format_icon',
action='format_icon', conditions=GET)
m.connect('/util/group/autocomplete', action='group_autocomplete')
m.connect('/util/organization/autocomplete', action='organization_autocomplete',
conditions=GET)
m.connect('/util/markdown', action='markdown')
m.connect('/util/dataset/munge_name', action='munge_package_name')
m.connect('/util/dataset/munge_title_to_name',
Expand Down
49 changes: 38 additions & 11 deletions ckan/controllers/admin.py
Expand Up @@ -3,13 +3,18 @@
import ckan.lib.base as base
import ckan.lib.helpers as h
import ckan.lib.app_globals as app_globals
import ckan.lib.navl.dictization_functions as dict_fns
import ckan.model as model
import ckan.logic as logic
import ckan.plugins as plugins
from home import CACHE_PARAMETERS


c = base.c
request = base.request
_ = base._


def get_sysadmins():
q = model.Session.query(model.User).filter(model.User.sysadmin==True)
return q.all()
Expand Down Expand Up @@ -51,14 +56,23 @@ def _get_config_form_items(self):
return items

def reset_config(self):
'''FIXME: This method is probably not doing what people would expect.
It will reset the configuration to values cached when CKAN started.
If these were coming from the database during startup, that's the
ones that will get applied on reset, not the ones in the ini file.
Only after restarting the server and having CKAN reset the values
from the ini file (as the db ones are not there anymore) will these
be used.
'''

if 'cancel' in request.params:
h.redirect_to(controller='admin', action='config')

if request.method == 'POST':
# remove sys info items
for item in self._get_config_form_items():
name = item['name']
app_globals.delete_global(name)
model.delete_system_info(name)
# reset to values in config
app_globals.reset()
h.redirect_to(controller='admin', action='config')
Expand All @@ -70,22 +84,35 @@ def config(self):
items = self._get_config_form_items()
data = request.POST
if 'save' in data:
# update config from form
for item in items:
name = item['name']
if name in data:
app_globals.set_global(name, data[name])
app_globals.reset()
try:
# really?
data_dict = logic.clean_dict(
dict_fns.unflatten(
logic.tuplize_dict(
logic.parse_params(
request.POST, ignore_keys=CACHE_PARAMETERS))))

del data_dict['save']

data = logic.get_action('config_option_update')(
{'user': c.user}, data_dict)
except logic.ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
vars = {'data': data, 'errors': errors,
'error_summary': error_summary, 'form_items': items}
return base.render('admin/config.html', extra_vars=vars)

h.redirect_to(controller='admin', action='config')

schema = logic.schema.update_configuration_schema()
data = {}
for item in items:
name = item['name']
data[name] = config.get(name)
for key in schema:
data[key] = config.get(key)

vars = {'data': data, 'errors': {}, 'form_items': items}
return base.render('admin/config.html',
extra_vars = vars)
extra_vars=vars)

def index(self):
#now pass the list of sysadmins
Expand Down
12 changes: 12 additions & 0 deletions ckan/controllers/api.py
Expand Up @@ -673,6 +673,18 @@ def convert_to_dict(user):
out = map(convert_to_dict, query.all())
return out

@jsonp.jsonpify
def organization_autocomplete(self):
q = request.params.get('q', '')
limit = request.params.get('limit', 20)
organization_list = []

if q:
context = {'user': c.user, 'model': model}
data_dict = {'q': q, 'limit': limit}
organization_list = get_action('organization_autocomplete')(context, data_dict)
return organization_list

def is_slug_valid(self):

def package_exists(val):
Expand Down
4 changes: 2 additions & 2 deletions ckan/controllers/group.py
Expand Up @@ -625,9 +625,9 @@ def delete(self, id):
try:
if request.method == 'POST':
self._action('group_delete')(context, {'id': id})
if self.group_type == 'organization':
if group_type == 'organization':
h.flash_notice(_('Organization has been deleted.'))
elif self.group_type == 'group':
elif group_type == 'group':
h.flash_notice(_('Group has been deleted.'))
else:
h.flash_notice(_('%s has been deleted.')
Expand Down
17 changes: 15 additions & 2 deletions ckan/controllers/user.py
Expand Up @@ -12,6 +12,7 @@
import ckan.lib.captcha as captcha
import ckan.lib.mailer as mailer
import ckan.lib.navl.dictization_functions as dictization_functions
import ckan.lib.authenticator as authenticator
import ckan.plugins as p

from ckan.common import _, c, g, request, response
Expand All @@ -27,6 +28,7 @@
NotFound = logic.NotFound
NotAuthorized = logic.NotAuthorized
ValidationError = logic.ValidationError
UsernamePasswordError = logic.UsernamePasswordError

DataError = dictization_functions.DataError
unflatten = dictization_functions.unflatten
Expand Down Expand Up @@ -136,8 +138,7 @@ def me(self, locale=None):
h.redirect_to(locale=locale, controller='user', action='login',
id=None)
user_ref = c.userobj.get_reference_preferred_for_uri()
h.redirect_to(locale=locale, controller='user', action='dashboard',
id=user_ref)
h.redirect_to(locale=locale, controller='user', action='dashboard')

def register(self, data=None, errors=None, error_summary=None):
context = {'model': model, 'session': model.Session, 'user': c.user,
Expand Down Expand Up @@ -324,6 +325,14 @@ def _save_edit(self, id, context):
context['message'] = data_dict.get('log_message', '')
data_dict['id'] = id

if data_dict['password1'] and data_dict['password2']:
identity = {'login': c.user,
'password': data_dict['old_password']}
auth = authenticator.UsernamePasswordAuthenticator()

if auth.authenticate(request.environ, identity) != c.user:
raise UsernamePasswordError

# MOAN: Do I really have to do this here?
if 'activity_streams_email_notifications' not in data_dict:
data_dict['activity_streams_email_notifications'] = False
Expand All @@ -341,6 +350,10 @@ def _save_edit(self, id, context):
errors = e.error_dict
error_summary = e.error_summary
return self.edit(id, data_dict, errors, error_summary)
except UsernamePasswordError:
errors = {'oldpassword': [_('Password entered was incorrect')]}
error_summary = {_('Old Password'): _('incorrect password')}
return self.edit(id, data_dict, errors, error_summary)

def login(self, error=None):
# Do any plugin login stuff
Expand Down
105 changes: 58 additions & 47 deletions ckan/lib/app_globals.py
Expand Up @@ -10,6 +10,8 @@

import ckan
import ckan.model as model
import ckan.logic as logic


log = logging.getLogger(__name__)

Expand All @@ -20,19 +22,17 @@
# 'config_key': 'globals_key',
}

# these config settings will get updated from system_info
auto_update = [
'ckan.site_title',
'ckan.site_logo',
'ckan.site_url',
'ckan.site_description',
'ckan.site_about',
'ckan.site_intro_text',
'ckan.site_custom_css',
'ckan.homepage_style',
]

config_details = {

# This mapping is only used to define the configuration options (from the
# `config` object) that should be copied to the `app_globals` (`g`) object.
app_globals_from_config_details = {
'ckan.site_title': {},
'ckan.site_logo': {},
'ckan.site_url': {},
'ckan.site_description': {},
'ckan.site_about': {},
'ckan.site_intro_text': {},
'ckan.site_custom_css': {},
'ckan.favicon': {}, # default gets set in config.environment.py
'ckan.template_head_end': {},
'ckan.template_footer_end': {},
Expand Down Expand Up @@ -84,18 +84,40 @@ def set_main_css(css_file):
app_globals.main_css = str(new_css)


def set_global(key, value):
''' helper function for getting value from database or config file '''
model.set_system_info(key, value)
setattr(app_globals, get_globals_key(key), value)
model.set_system_info('ckan.config_update', str(time.time()))
# update the config
config[key] = value
log.info('config `%s` set to `%s`' % (key, value))
def set_app_global(key, value):
'''
Set a new key on the app_globals (g) object
It will process the value according to the options on
app_globals_from_config_details (if any)
'''
key, value = process_app_global(key, value)
setattr(app_globals, key, value)


def process_app_global(key, value):
'''
Tweak a key, value pair meant to be set on the app_globals (g) object
According to the options on app_globals_from_config_details (if any)
'''
options = app_globals_from_config_details.get(key)
key = get_globals_key(key)
if options:
if 'name' in options:
key = options['name']
value = value or options.get('default', '')

data_type = options.get('type')
if data_type == 'bool':
value = asbool(value)
elif data_type == 'int':
value = int(value)
elif data_type == 'split':
value = value.split()

return key, value

def delete_global(key):
model.delete_system_info(key)
log.info('config `%s` deleted' % (key))

def get_globals_key(key):
# create our globals key
Expand All @@ -106,6 +128,9 @@ def get_globals_key(key):
return mappings[key]
elif key.startswith('ckan.'):
return key[5:]
else:
return key


def reset():
''' set updatable values from config '''
Expand Down Expand Up @@ -133,13 +158,16 @@ def get_config_value(key, default=''):
log.debug('config `%s` set to `%s` from config' % (key, value))
else:
value = default
setattr(app_globals, get_globals_key(key), value)

set_app_global(key, value)

# update the config
config[key] = value
return value

# update the config settings in auto update
for key in auto_update:
schema = logic.schema.update_configuration_schema()
for key in schema.keys():
get_config_value(key)

# cusom styling
Expand All @@ -158,8 +186,6 @@ def get_config_value(key, default=''):
app_globals.header_class = 'header-text-logo-tagline'




class _Globals(object):

''' Globals acts as a container for objects available throughout the
Expand Down Expand Up @@ -193,25 +219,10 @@ def _init(self):
else:
self.ckan_doc_version = 'latest'

# process the config_details to set globals
for name, options in config_details.items():
if 'name' in options:
key = options['name']
elif name.startswith('ckan.'):
key = name[5:]
else:
key = name
value = config.get(name, options.get('default', ''))

data_type = options.get('type')
if data_type == 'bool':
value = asbool(value)
elif data_type == 'int':
value = int(value)
elif data_type == 'split':
value = value.split()

setattr(self, key, value)
# process the config details to set globals
for key in app_globals_from_config_details.keys():
new_key, value = process_app_global(key, config.get(key) or '')
setattr(self, new_key, value)


app_globals = _Globals()
Expand Down

0 comments on commit 840f335

Please sign in to comment.