Skip to content

Commit

Permalink
Merge branch 'master' of github.com:okfn/ckan
Browse files Browse the repository at this point in the history
  • Loading branch information
tobes committed Jul 1, 2013
2 parents cac55c6 + ccd12d3 commit d1b8b4e
Show file tree
Hide file tree
Showing 15 changed files with 275 additions and 83 deletions.
74 changes: 47 additions & 27 deletions ckan/lib/formatters.py
Expand Up @@ -68,35 +68,55 @@ def _month_dec():
_month_sept, _month_oct, _month_nov, _month_dec]


def localised_nice_date(datetime_):
''' Returns a friendly localised unicode representation of a datetime. '''
now = datetime.datetime.now()
date_diff = now - datetime_
days = date_diff.days
if days < 1 and now > datetime_:
# less than one day
seconds = date_diff.seconds
if seconds < 3600:
# less than one hour
if seconds < 60:
return _('Just now')
def localised_nice_date(datetime_, show_date=False, with_hours=False):
''' Returns a friendly localised unicode representation of a datetime.
:param datetime_: The date to format
:type datetime_: datetime
:param show_date: Show date not 2 days ago etc
:type show_date: bool
:param with_hours: should the `hours:mins` be shown for dates
:type with_hours: bool
:rtype: sting
'''
if not show_date:
now = datetime.datetime.now()
date_diff = now - datetime_
days = date_diff.days
if days < 1 and now > datetime_:
# less than one day
seconds = date_diff.seconds
if seconds < 3600:
# less than one hour
if seconds < 60:
return _('Just now')
else:
return ungettext('{mins} minute ago', '{mins} minutes ago',
seconds / 60).format(mins=seconds / 60)
else:
return ungettext('{mins} minute ago', '{mins} minutes ago',
seconds / 60).format(mins=seconds / 60)
else:
return ungettext('{hours} hour ago', '{hours} hours ago',
seconds / 3600).format(hours=seconds / 3600)
# more than one day
if days < 31:
return ungettext('{days} day ago', '{days} days ago',
days).format(days=days)
return ungettext('{hours} hour ago', '{hours} hours ago',
seconds / 3600).format(hours=seconds / 3600)
# more than one day
if days < 31:
return ungettext('{days} day ago', '{days} days ago',
days).format(days=days)
# actual date
month = datetime_.month
day = datetime_.day
year = datetime_.year
month_name = _MONTH_FUNCTIONS[month - 1]()
return _('{month} {day}, {year}').format(month=month_name, day=day,
year=year)
details = {
'min': datetime_.minute,
'hour': datetime_.hour,
'day': datetime_.day,
'year': datetime_.year,
'month': _MONTH_FUNCTIONS[datetime_.month - 1](),
}
if with_hours:
return (
# NOTE: This is for translating dates like `April 24, 2013, 10:45`
_('{month} {day}, {year}, {hour:02}:{min:02}').format(**details))
else:
return (
# NOTE: This is for translating dates like `April 24, 2013`
_('{month} {day}, {year}').format(**details))


def localised_number(number):
Expand Down
31 changes: 20 additions & 11 deletions ckan/lib/helpers.py
Expand Up @@ -780,26 +780,35 @@ def _range(self, regexp_match):


def render_datetime(datetime_, date_format=None, with_hours=False):
'''Render a datetime object or timestamp string as a pretty string
(Y-m-d H:m).
'''Render a datetime object or timestamp string as a localised date or
in the requested format.
If timestamp is badly formatted, then a blank string is returned.
:param datetime_: the date
:type datetime_: datetime or ISO string format
:param date_format: a date format
:type date_format: string
:param with_hours: should the `hours:mins` be shown
:type with_hours: bool
:rtype: string
'''
if not date_format:
date_format = '%b %d, %Y'
if with_hours:
date_format += ', %H:%M'
if isinstance(datetime_, datetime.datetime):
return datetime_.strftime(date_format)
elif isinstance(datetime_, basestring):
if isinstance(datetime_, basestring):
try:
datetime_ = date_str_to_datetime(datetime_)
except TypeError:
return ''
except ValueError:
return ''
return datetime_.strftime(date_format)
else:
# check we are now a datetime
if not isinstance(datetime_, datetime.datetime):
return ''
# if date_format was supplied we use it
if date_format:
return datetime_.strftime(date_format)
# the localised date
return formatters.localised_nice_date(datetime_, show_date=True,
with_hours=with_hours)


def date_str_to_datetime(date_str):
Expand Down
2 changes: 2 additions & 0 deletions ckan/logic/auth/get.py
Expand Up @@ -100,6 +100,8 @@ def package_show(context, data_dict):
auth = new_authz.is_authorized('package_update',
context, data_dict)
authorized = auth.get('success')
elif package.owner_org is None and package.state == 'active':
return {'success': True}
else:
# anyone can see a public package
if not package.private and package.state == 'active':
Expand Down
7 changes: 5 additions & 2 deletions ckan/logic/schema.py
Expand Up @@ -48,7 +48,9 @@
user_name_exists,
role_exists,
url_validator,
list_of_strings)
datasets_with_no_organization_cannot_be_private,
list_of_strings,
)
from ckan.logic.converters import (convert_user_name_or_id_to_id,
convert_package_name_or_id_to_id,
convert_group_name_or_id_to_id,)
Expand Down Expand Up @@ -137,7 +139,8 @@ def default_create_package_schema():
'type': [ignore_missing, unicode],
'owner_org': [owner_org_validator, unicode],
'log_message': [ignore_missing, unicode, no_http],
'private': [ignore_missing, boolean_validator],
'private': [ignore_missing, boolean_validator,
datasets_with_no_organization_cannot_be_private],
'__extras': [ignore],
'__junk': [empty],
'resources': default_resource_schema(),
Expand Down
8 changes: 8 additions & 0 deletions ckan/logic/validators.py
Expand Up @@ -632,10 +632,18 @@ def role_exists(role, context):
return role


def datasets_with_no_organization_cannot_be_private(key, data, errors,
context):
if data[key] is True and data.get(('owner_org',)) is None:
errors[key].append(
_("Datasets with no organization can't be private."))


def list_of_strings(key, data, errors, context):
value = data.get(key)
if not isinstance(value, list):
raise Invalid(_('Not a list'))
for x in value:
if not isinstance(x, basestring):
raise Invalid('%s: %s' % (_('Not a string'), x))

32 changes: 32 additions & 0 deletions ckan/public/base/javascript/modules/dataset-visibility.js
@@ -0,0 +1,32 @@
/* Dataset visibility toggler
* When no organization is selected in the org dropdown then set visibility to
* public always and disable dropdown
*/
this.ckan.module('dataset-visibility', function ($, _) {
return {
currentValue: false,
options: {
organizations: $('#field-organizations'),
visibility: $('#field-private'),
currentValue: null
},
initialize: function() {
$.proxyAll(this, /_on/);
this.options.currentValue = this.options.visibility.val();
this.options.organizations.on('change', this._onOrganizationChange);
this._onOrganizationChange();
},
_onOrganizationChange: function() {
var value = this.options.organizations.val();
if (value) {
this.options.visibility
.prop('disabled', false)
.val(this.options.currentValue);
} else {
this.options.visibility
.prop('disabled', true)
.val('False');
}
}
};
});
1 change: 1 addition & 0 deletions ckan/public/base/javascript/resource.config
Expand Up @@ -36,6 +36,7 @@ ckan =
modules/activity-stream.js
modules/dashboard.js
modules/table-toggle-more.js
modules/dataset-visibility.js
modules/media-grid.js

main =
Expand Down
52 changes: 40 additions & 12 deletions ckan/templates/package/snippets/package_basic_fields.html
Expand Up @@ -52,23 +52,51 @@
{% if data.group_id %}
<input type="hidden" name="groups__0__id" value="{{ data.group_id }}" />
{% endif %}
{% set existing_org = data.owner_org or data.group_id %}
{% if h.check_access('sysadmin') or data.get('state', 'draft').startswith('draft') or data.get('state', 'none') == 'none' %}
{% set organizations_available = h.organizations_available('create_dataset') %}
{% if organizations_available %}

{% set dataset_is_draft = data.get('state', 'draft').startswith('draft') or data.get('state', 'none') == 'none' %}
{% set dataset_has_organization = data.owner_org or data.group_id %}
{% set organizations_available = h.organizations_available('create_dataset') %}
{% set user_is_sysadmin = h.check_access('sysadmin') %}
{% set show_organizations_selector = organizations_available and (user_is_sysadmin or dataset_is_draft) %}
{% set show_visibility_selector = dataset_has_organization or (organizations_available and (user_is_sysadmin or dataset_is_draft)) %}

{% if show_organizations_selector and show_visibility_selector %}
<div data-module="dataset-visibility">
{% endif %}

{% if show_organizations_selector %}
<div class="control-group">
<label for="field-organizations" class="control-label">{{ _('Organization') }}</label>
<div class="controls">
<select id="field-organizations" name="owner_org" data-module="autocomplete">
<option value="" {% if not selected_org and data.id %} selected="selected" {% endif %}>{{ _('Select an organization...') }}</option>
{% for organization in organizations_available %}
{# get out first org from users list only if there is not an existing org #}
{% set selected_org = (existing_org and existing_org == organization.id) or (not existing_org and not data.id and organization.id == organizations_available[0].id) %}
<option value="{{ organization.id }}" {% if selected_org %} selected="selected" {% endif %}>{{ organization.name }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}

{% if show_visibility_selector %}
{% block package_metadata_fields_visibility %}
<div class="control-group">
<label for="field-organizations" class="control-label">{{ _('Organization') }}</label>
<label for="field-private" class="control-label">{{ _('Visibility') }}</label>
<div class="controls">
<select id="field-organizations" name="owner_org" data-module="autocomplete">
<option value="" {% if not selected_org and data.id %} selected="selected" {% endif %}>{{ _('Select an organization...') }}</option>
{% for organization in organizations_available %}
{# get out first org from users list only if there is not an existing org #}
{% set selected_org = (existing_org and existing_org == organization.id) or (not existing_org and not data.id and organization.id == organizations_available[0].id) %}
<option value="{{ organization.id }}" {% if selected_org %} selected="selected" {% endif %}>{{ organization.name }}</option>
<select id="field-private" name="private">
{% for option in [(true, _('Private')), (false, _('Public'))] %}
<option value="{{ option[0] }}" {% if option[0] == data.private %}selected="selected"{% endif %}>{{ option[1] }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
{% endblock %}
{% endif %}

{% if show_organizations_selector and show_visibility_selector %}
</div>
{% endif %}

{% endblock %}
15 changes: 0 additions & 15 deletions ckan/templates/package/snippets/package_metadata_fields.html
Expand Up @@ -2,21 +2,6 @@

{% block package_metadata_fields %}

{% block package_metadata_fields_visibility %}
{% if data.owner_org %}
<div class="control-group">
<label for="field-private" class="control-label">{{ _('Visibility') }}</label>
<div class="controls">
<select id="field-private" name="private" data-module="autocomplete">
{% for option in [(true, _('Private')), (false, _('Public'))] %}
<option value="{{ option[0] }}" {% if option[0] == data.private %}selected="selected"{% endif %}>{{ option[1] }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
{% endblock %}

{% block package_metadata_author %}
{{ form.input('author', label=_('Author'), id='field-author', placeholder=_('Joe Bloggs'), value=data.author, error=errors.author, classes=['control-medium']) }}

Expand Down
32 changes: 32 additions & 0 deletions ckan/tests/functional/api/model/test_package.py
Expand Up @@ -11,6 +11,8 @@
from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase
from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase

import ckan.tests as tests

# Todo: Remove this ckan.model stuff.
import ckan.model as model

Expand Down Expand Up @@ -776,6 +778,36 @@ def test_package_revisions(self):
revisions = res.json
assert len(revisions) == 3, len(revisions)

def test_create_private_package_with_no_organization(self):
'''Test that private packages with no organization cannot be created.
'''
testsysadmin = model.User.by_name('testsysadmin')
result = tests.call_action_api(self.app, 'package_create', name='test',
private=True, apikey=testsysadmin.apikey, status=409)
assert result == {'__type': 'Validation Error',
'private': ["Datasets with no organization can't be private."]}

def test_create_public_package_with_no_organization(self):
'''Test that public packages with no organization can be created.'''
testsysadmin = model.User.by_name('testsysadmin')
tests.call_action_api(self.app, 'package_create', name='test',
private=False, apikey=testsysadmin.apikey)

def test_make_package_with_no_organization_private(self):
'''Test that private packages with no organization cannot be created
by package_update.
'''
testsysadmin = model.User.by_name('testsysadmin')
package = tests.call_action_api(self.app, 'package_create',
name='test_2', private=False, apikey=testsysadmin.apikey)
package['private'] = True
result = tests.call_action_api(self.app, 'package_update',
apikey=testsysadmin.apikey, status=409, **package)
assert result == {'__type': 'Validation Error',
'private': ["Datasets with no organization can't be private."]}


class TestPackagesVersion1(Version1TestCase, PackagesTestCase):
def test_06_create_pkg_using_download_url(self):
Expand Down

0 comments on commit d1b8b4e

Please sign in to comment.