Skip to content

Commit

Permalink
Merge pull request #215 from ox-it/organisation-descendants
Browse files Browse the repository at this point in the history
Organisation descendants
  • Loading branch information
ahaith committed Mar 13, 2015
2 parents 255bc75 + bb8601b commit 274c5ae
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 15 deletions.
23 changes: 22 additions & 1 deletion talks/api/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import operator
from rest_framework.exceptions import ParseError
from talks.core.utils import parse_date
from talks.events.datasources import DEPARTMENT_DESCENDANT_DATA_SOURCE
from talks.events.models import ROLES_SPEAKER, Event, EventGroup


Expand All @@ -27,7 +28,7 @@ def events_search(request):
list_parameters = {
'speaker': lambda speakers: Q(personevent__role=ROLES_SPEAKER, personevent__person__slug__in=speakers),
'venue': lambda venues: Q(location__in=venues),
'organising_department': lambda depts: Q(department_organiser__in=depts),
'organising_department': lambda depts: Q(department_organiser__in=get_all_department_ids(depts, True)),
'topic': lambda topics: Q(topics__uri__in=topics)
}

Expand All @@ -42,6 +43,26 @@ def events_search(request):
return events


def get_all_department_ids(departments, include_suborgs):
if not include_suborgs:
return departments

# build a new list which includes the originals plus all descendants
try:
descendants_response = DEPARTMENT_DESCENDANT_DATA_SOURCE.get_object_list(departments)
except Exception:
print "Error retrieving sub-departments, returning departments only"
return departments

all_ids = []
for result in descendants_response:
# For each of the orgs being queried, extract the list of suborg ids, and add the original id too
result_ids = map((lambda descendant: descendant['id']), result['descendants'])
result_ids.append(result['id'])
all_ids.extend(result_ids)
return all_ids


def get_event_by_slug(slug):
"""Get an event by its slug
:param slug: Event.slug
Expand Down
16 changes: 12 additions & 4 deletions talks/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
TOPIC_1429860_MOCK_RESPONSE = {"_links":{"self":{"href":"/search?uri=http://id.worldcat.org/fast/1429860"}},"_embedded":{"concepts":[{"uri":"http://id.worldcat.org/fast/1429860","prefLabel":"Biodiversity","altLabels":["Biotic diversity","Diversification, Biological","Diversity, Biotic","Biological diversity","Diversity, Biological","Biological diversification"],"related":[{"label":"Biology","uri":"http://id.worldcat.org/fast/832383"},{"label":"Ecological heterogeneity","uri":"http://id.worldcat.org/fast/901453"}]}]}}
LOC_40002001_MOCK_RESPONSE = {"_embedded": {"pois": [{"_embedded": {"files": [{"location": "oxpoints/40002001/depiction/original/primary.jpg","primary": True,"type": "depiction","url": "//mox-static-files.oucs.ox.ac.uk/oxpoints/40002001/depiction/original/primary.jpg"},{"location": "oxpoints/40002001/depiction/original/primary.jpg","type": "depiction","url": "//mox-static-files.oucs.ox.ac.uk/oxpoints/40002001/depiction/original/primary.jpg"}]},"_links": {"child": [{"href": "/places/oxpoints:23233603"},{"href": "/places/oxpoints:23233671","title": "11-13 Banbury Road","type": ["/university/building"],"type_name": ["Building"]},{"href": "/places/oxpoints:23233670","title": "7-9 Banbury Road","type": ["/university/building"],"type_name": ["Building"]},{"href": "/places/oxpoints:23233669","title": "15-19 Banbury Road","type": ["/university/building"],"type_name": ["Building"]}],"parent": {"href": "/places/oxpoints:31337175","title": "IT Services","type": ["/university/department"],"type_name": ["Department"]},"self": {"href": "/places/oxpoints:40002001"}},"address": "7-19 Banbury Road OX2 6NN","alternative_names": ["IT Services, Banbury Road"],"distance": 0,"id": "oxpoints:40002001","identifiers": ["osm:99933769-way","oxpoints:40002001"],"lat": "51.76001","lon": "-1.26035","name": "7-19 Banbury Road","name_sort": "7-19 Banbury Road","shape": "POLYGON ((-1.2604547 51.7597247,-1.2604524 51.759703600000002,-1.2606225 51.759693400000003,-1.2606263 51.759717899999998,-1.2606718 51.759715200000002,-1.2606742 51.759729900000004,-1.260875 51.759717299999998,-1.2609002 51.759870900000003,-1.2609514 51.7598677,-1.2609628 51.759937299999997,-1.2609819 51.759936099999997,-1.2610376 51.760275399999998,-1.2606854 51.760297899999998,-1.260475 51.760310799999999,-1.2604334 51.7600865,-1.2605216 51.760081800000002,-1.2605182 51.760061299999997,-1.2605157 51.760043799999998,-1.2604056 51.760051799999999,-1.2603867 51.759929399999997,-1.2604979 51.759923100000002,-1.2604921 51.7598805,-1.2604867 51.759852799999997,-1.2603628 51.759858199999996,-1.2603454 51.759729800000002,-1.2604547 51.7597247))","type": ["/university/site"],"type_name": ["Site"]}]},"_links": {"self": {"href": "/places/oxpoints:40002001%2C"}},"count": 1}
DEP_23232503_MOCK_RESPONSE = {"_embedded": {"pois": [{"_links": {"child": [{"href": "/places/oxpoints:23232548"}],"parent": {"href": "/places/oxpoints:23232546","title": "Department of Chemistry","type": ["/university/department"],"type_name": ["Department"]},"primary_place": {"href": "/places/oxpoints:23232548"},"self": {"href": "/places/oxpoints:23232503"}},"address": "off South Parks Road OX1 3TA","alternative_names": ["Bayley Group"],"distance": 0,"id": "oxpoints:23232503","identifiers": ["oxpoints:23232503","finance:DQ"],"lat": "51.757797","lon": "-1.253332","name": "Chemical Biology","name_sort": "Chemical Biology","shape": "POLYGON ((-1.252908 51.758493199999997 0,-1.2525447 51.758077800000002 0,-1.252753 51.758007999999997 0,-1.2527069 51.757955299999999 0,-1.2532898 51.757759999999998 0,-1.2533847 51.7578685 0,-1.2532655 51.757908399999998 0,-1.2532846 51.757930299999998 0,-1.2533138 51.757963699999998 0,-1.2533718 51.757944299999998 0,-1.253638 51.758248700000003 0,-1.252908 51.758493199999997 0))","social_twitter": ["https://www.twitter.com/bayley_lab"],"type": ["/university/department"],"type_name": ["Department"],"website": "http://bayley.chem.ox.ac.uk/"}]},"_links": {"self": {"href": "/places/oxpoints:23232503%2C"}},"count": 1}
DEP_23232503_SUBORGS_MOCK_RESPONSE = {"descendants": [],"id": "oxpoints:23232503"}

DEP_23232546_SUBORGS_MOCK_RESPONSE = {"descendants": [{"id": "oxpoints:23232604","title": "Inorganic Chemistry Laboratory"},{"id": "oxpoints:51830315","title": "Theory and Modelling in Chemical Sciences CDT"},{"id": "oxpoints:52835523","title": "Chemistry Research Laboratory"},{"id": "oxpoints:23232687","title": "Physical and Theoretical Chemistry Laboratory"},{"id": "oxpoints:58224544","title": "Organic Chemistry"},{"id": "oxpoints:23232503","title": "Chemical Biology"},{"id": "oxpoints:23232843","title": "Synthesis for Biology and Medicine CDT"}],"id": "oxpoints:23232546"}

MOCK_URL_RESPONSES = {
settings.API_OX_PLACES_URL + 'oxpoints:40002001,': LOC_40002001_MOCK_RESPONSE,
settings.API_OX_PLACES_URL + 'oxpoints:23232503,': DEP_23232503_MOCK_RESPONSE,
settings.API_OX_PLACES_URL + 'oxpoints:23232503/organisation-descendants,': DEP_23232503_SUBORGS_MOCK_RESPONSE,
settings.API_OX_PLACES_URL + 'oxpoints:23232546/organisation-descendants': DEP_23232546_SUBORGS_MOCK_RESPONSE,
settings.TOPICS_URL + 'get?uri=http%3A%2F%2Fid.worldcat.org%2Ffast%2F1429860': TOPIC_1429860_MOCK_RESPONSE,
}

Expand Down Expand Up @@ -51,7 +50,8 @@ def setUp(self):
self.group1_slug = "talks-conference"
self.speaker1_slug = "james-bond"
self.location1 = "oxpoints:40002001"
self.department1 = "oxpoints:23232503"
self.department1 = "oxpoints:23232503" # Chemical Biology
self.super_department = "oxpoints:23232546" # Department of Chemistry
self.topic1_uri = "http://id.worldcat.org/fast/1429860"
# create some sample events and series
person1 = factories.PersonFactory.create(
Expand Down Expand Up @@ -196,3 +196,11 @@ def test_search_topic(self, requests_get):
self.assertContains(response, "_links")
self.assertContains(response, "_embedded")
self.assertContains(response, "Biodiversity")

@mock.patch('requests.get', side_effect=mocked_requests_get)
def test_search_sub_organisations(self, requests_get):
response = self.client.get('/api/talks/search?from=01/01/01&organising_department=' + self.super_department)
self.assertEquals(response.status_code, 200)
self.assertContains(response, "_links")
self.assertContains(response, "_embedded")
self.assertContains(response, self.event1_slug)
14 changes: 14 additions & 0 deletions talks/events/datasources.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,17 @@ def __init__(self, **kwargs):
display_key='email',
serializer=serializers.UserSerializer
)

# not doing what we want as we want to treat the response as one document...
DEPARTMENT_DESCENDANT_DATA_SOURCE = typeahead.DataSource(
'department_descendant',
url=settings.API_OX_PLACES_URL + "suggest?q=%QUERY",
get_prefetch_url=lambda values: settings.API_OX_PLACES_URL + values.pop() + "/organisation-descendants",
id_key='id',
display_key='title',
response_expression='response',
as_list=True
)

def get_descendants(org_id):
url = settings.API_OX_PLACES_URL + org_id + "/organisation-descendants"
8 changes: 4 additions & 4 deletions talks/events/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def test_fetched_from_remote(self, requests_get, cache, get_objects_from_respons
cache.get_many.assert_called_once_with([fetched_id])
cache.set_many.assert_called_once_with({fetched_id: fetched_object})
requests_get.assert_called_once_with(mock.sentinel.url)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None, False)
self.assertEquals(result, {fetched_id: fetched_object})

def test_fetched_from_cache(self, requests_get, cache, get_objects_from_response):
Expand Down Expand Up @@ -151,7 +151,7 @@ def test_remote_not_found_in_response(self, requests_get, cache, get_objects_fro
assert_not_called(cache.set_many)
assert_not_called(cache.set_many)
requests_get.assert_called_once_with(mock.sentinel.url)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None, False)
self.assertEquals(result, {})

def test_remote_404(self, requests_get, cache, get_objects_from_response):
Expand Down Expand Up @@ -186,7 +186,7 @@ def test_fetched_from_remote_and_cache(self, requests_get, cache, get_objects_fr
cache.get_many.assert_called_once_with([cached_id, fetched_id])
cache.set_many.assert_called_once_with({fetched_id: fetched_object})
requests_get.assert_called_once_with(mock.sentinel.url)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None, False)
self.assertEquals(result, {
cached_id: cached_object,
fetched_id: fetched_object
Expand All @@ -207,7 +207,7 @@ def test_remote_returns_more_than_requested(self, requests_get, cache, get_objec
cache.get_many.assert_called_once_with([fetched_id])
cache.set_many.assert_called_once_with({fetched_id: fetched_object})
requests_get.assert_called_once_with(mock.sentinel.url)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None)
get_objects_from_response.assert_called_once_with(requests_get.return_value, None, False)
self.assertEquals(result, {fetched_id: fetched_object})


Expand Down
13 changes: 10 additions & 3 deletions talks/events/typeahead.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def value_from_datadict(self, data, files, name):
return data.get(name, None)


def get_objects_from_response(response, expression=None):
def get_objects_from_response(response, expression=None, as_list=False):
"""
Return list of dicts representing json decoded objects. Supports only json response.
Expand All @@ -73,6 +73,10 @@ def get_objects_from_response(response, expression=None):
while properties:
prop = properties.pop(0)
json = json[prop]

# Convert to a list if necessary
if not isinstance(json, list) and as_list:
return [json]
return json


Expand All @@ -83,7 +87,7 @@ class DataSource(object):
"""

def __init__(self, cache_key=None, url=None, get_prefetch_url=None, local=None, id_key=None, display_key=None,
response_expression=None, prefetch_response_expression=None, templates=None):
response_expression=None, prefetch_response_expression=None, templates=None, as_list=False):
"""
:param cache_key: cache name to use
:param url: url for fetching suggestions
Expand All @@ -94,6 +98,7 @@ def __init__(self, cache_key=None, url=None, get_prefetch_url=None, local=None,
:param response_expression: javascript expression retrieving an array of objects from suggestion response
:param prefetch_response_expression: same as `response_expression` but for prefetch response
:param templates: dictionary of template strings to configure typeahead
:param as_list: if true, convert a single result to a list of one
"""
self.cache_key = cache_key
self.url = url
Expand All @@ -104,6 +109,7 @@ def __init__(self, cache_key=None, url=None, get_prefetch_url=None, local=None,
self.templates = templates or {}
self.response_expression = response_expression
self.prefetch_response_expression = prefetch_response_expression or response_expression
self.as_list = as_list

@property
def is_local(self):
Expand Down Expand Up @@ -136,8 +142,9 @@ def _fetch_objects(self, id_list):
log.debug("prefetch_url: %s", url)
response = requests.get(url)
response.raise_for_status()
fetched = get_objects_from_response(response, self.prefetch_response_expression)
fetched = get_objects_from_response(response, self.prefetch_response_expression, self.as_list)
log.debug("fetched from response: %s", fetched)
log.debug("as list?: %r", self.as_list)
mapped = {obj[self.id_key]: obj for obj in fetched if obj[self.id_key] in id_list}
if mapped:
if self.cache:
Expand Down
5 changes: 3 additions & 2 deletions talks/events/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.conf.urls import patterns, url

from talks.events.views import (upcoming_events, show_person, show_event, events_for_day, show_department_organiser,
events_for_month, events_for_year, list_event_groups,show_event_group, show_topic)
events_for_month, events_for_year, list_event_groups,show_event_group, show_topic,
show_department_descendant)
from talks.contributors.views import (create_person, edit_person, edit_event, create_event, create_event_group,
edit_event_group, delete_event, delete_event_group)

Expand All @@ -25,5 +26,5 @@
url(r'^series/id/(?P<event_group_slug>[^/]+)/edit$', edit_event_group, name='edit-event-group'),
url(r'^series/id/(?P<event_group_slug>[^/]+)/delete', delete_event_group, name='delete-event-group'),
url(r'^topics/id/$', show_topic, name="show-topic"),
url(r'^department/id/(?P<org_id>[^/]+)$', show_department_organiser, name="show-department")
url(r'^department/id/(?P<org_id>[^/]+)$', show_department_descendant, name="show-department"),
)
17 changes: 16 additions & 1 deletion talks/events/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from .models import Event, EventGroup, Person
from talks.events.models import ROLES_SPEAKER, ROLES_HOST, ROLES_ORGANISER
from talks.events.datasources import TOPICS_DATA_SOURCE, DEPARTMENT_DATA_SOURCE
from talks.events.datasources import TOPICS_DATA_SOURCE, DEPARTMENT_DATA_SOURCE, DEPARTMENT_DESCENDANT_DATA_SOURCE

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -154,3 +154,18 @@ def show_department_organiser(request, org_id):
'events': events
}
return render(request, 'events/department.html', context)


def show_department_descendant(request, org_id):
org = DEPARTMENT_DATA_SOURCE.get_object_by_id(org_id)
results = DEPARTMENT_DESCENDANT_DATA_SOURCE.get_object_by_id(org_id)
descendants = results['descendants']
sub_orgs = descendants
ids = [o['id'] for o in sub_orgs]
events = Event.published.filter(department_organiser__in=ids)
context = {
'org': org,
'sub_orgs': sub_orgs,
'events': events
}
return render(request, 'events/department.html', context)
5 changes: 5 additions & 0 deletions talks/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,13 @@
},
'oxpoints': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'oxpoints'
},
'topics': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
},
'department_descendant': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'department_descendants'
}
}
8 changes: 8 additions & 0 deletions talks/templates/events/department.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ <h3 class="panel-title">{{ org.name }}</h3>

<p><a href="//maps.ox.ac.uk/#/places/{{ org.id }}" target="_blank">See {{ org.name }} on <strong>maps.ox</strong></a></p>


This page lists all talks organised by this department, or any of its sub-organisations:
<ul>
{% for org in sub_orgs %}
<li>{{ org.title }}</li>
{% endfor %}
</ul>

{% endblock %}

0 comments on commit 274c5ae

Please sign in to comment.