Skip to content

Commit

Permalink
Merge branch 'master' of github.com:ox-it/talks.ox
Browse files Browse the repository at this point in the history
  • Loading branch information
drewhemm committed Mar 26, 2015
2 parents 2bad28c + 1cd010c commit 0bbdc98
Show file tree
Hide file tree
Showing 26 changed files with 246 additions and 38 deletions.
5 changes: 4 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
# ones.
extensions = [
'sphinx.ext.todo',
'sphinxcontrib.httpdomain'
'sphinxcontrib.httpdomain',
'sphinx.ext.intersphinx',
]

intersphinx_mapping = {'widget': ('http://talksox.readthedocs.org/projects/talksox-js-widget/en/latest/', None)}

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

Expand Down
12 changes: 5 additions & 7 deletions docs/source/http_api/endpoints/search.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ Retrieve Talks via Search

.. http:get:: /talks/search
Search for events
Search for Talks

**Example request**:

.. sourcecode:: http

GET /api/events/search?from=today&topic=X HTTP/1.1
GET /api/talks/search?from=today&topic=X HTTP/1.1
Host: talks.ox.ac.uk
Accept: application/json

Expand All @@ -24,7 +24,7 @@ Retrieve Talks via Search
{
"_links": {
"self": {
"href": "http://127.0.0.1:8000/api/talks/search?from=01/01/01"
"href": "http://talks.ox.ac.uk/api/talks/search?from=01/01/01"
},
"next": null,
"prev": null
Expand Down Expand Up @@ -81,9 +81,7 @@ Retrieve Talks via Search
:type from: string
:query to: Optional date to end filtering. Format should be dd/mm/yy OR 'today' or 'tomorrow'
:type to: string
:query subvenues: If true, include all sub-locations of the specified venue within the search
:type subvenues: boolean
:query subdepartments: If true, include all sub-organisations of the specified department within the search
:query subdepartments: Optional. Defaults to true. If true, include all sub-organisations of the specified department within the search
:type subdepartments: boolean

The below parameters can each be repeated multiple times
Expand All @@ -94,7 +92,7 @@ Retrieve Talks via Search
:type venue: string
:query organising_department: Search for talks whose organising department is the organisation specified by this oxpoints ID
:type organising_department: string
:query speaker: Search for talks at which the specified person is a speaker. Supply the unique slug for the person e.g. 'james-bond'
:query speaker: Search for talks at which the specified person is a speaker. Supply the unique slug for the person e.g. 'd47e2458-af73-4bc1-bf04-3c275e1c1254'
:type speaker: string

The response can be either in XML or JSON dependent on the 'accept' header in the request.
Expand Down
6 changes: 4 additions & 2 deletions docs/source/http_api/summary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ Formats: JSON, XML

The API is able to return either XML or JSON, depending on the ‘Accept’ header in the request. By default a web browser will specify XML in the request. Note curl doesn’t specify any preference, so the API will respond with json.

To ensure you get xml back then add an Accept header to your request::
To ensure you get xml back, add an Accept header to your request::

curl https://new.talks.ox.ac.uk/api/series/041a5cc6-d65a-4dec-967d-3adc5162cea3 -H "Accept: application/xml"

TODO authentication on some endpoints
For a more detailed example of the API in use, see the :ref:`JavaScript widget documentation <widget:widget-index>`.


16 changes: 15 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,21 @@ User Guide
:maxdepth: 2
:glob:

user/*
user/*

****************************
Web Managers and Integrators
****************************

An example widget to get you started with embedding talks in your own webpages can be found here:

`https://github.com/ox-it/talks.ox-js-widget <https://github.com/ox-it/talks.ox-js-widget>`_

The widget uses JavaScript to write a table, list or calendar view of selected talks to an HTML page. You can specify the criteria to select the talks you want.

* :ref:`Widget Documentation Overview <widget:widget-index>`
* :ref:`Parameters Reference <widget:parameters>`


***********************
Developer Documentation
Expand Down
1 change: 1 addition & 0 deletions docs/source/user/talk-editors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ Talk Editors
talk-editors/people-details
talk-editors/contact
talk-editors/differences
talk-editors/organisers


37 changes: 37 additions & 0 deletions docs/source/user/talk-editors/organisers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Talks Editors and Talk Organisers
=================================

Talk and Series Organisers
--------------------------

Organisers

* are involved in the practical organisation of a talk or a series of talks
* are the main point of contact for enquiries about the event.

Organisers are added to Oxford Talks in the same way as speakers and hosts. If they don't appear in the drop down list as you type then you can use the Add Person button to add them quickly to the system.

Very often organisers will also be the people who add information about the talk to Oxford Talks - if that's the case then they will also need to be signed up as a Talks Editor.

Talks Editors
-------------

Talks Editors

* have the rights to edit the information in Oxford Talks
* must have an Oxford University Single Sign On account and have applied to be a Talks Editor
* can create new series or talks
* can be added as an editor to other talks and series.

Once a Talks Editor has been signed up, you should be able to add them to the list of editors for a series or talk by typing their email address.

Why the two roles?
------------------

The old Oxford Talks had just one role - the List Manager was the person who added and edited lists and talks and was also the point of contact for enquiries. Anyone could add or edit talks and lists.

When we designed the new system, feedback from administrators was that they would prefer editing and adding of talks to be restricted, so we created the Talks Editor role and put in the option to specify an organiser without having to go through the process of signing them up to be an Editor as well.




3 changes: 2 additions & 1 deletion talks/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ def get_location_summary(self, obj):
summary = api_loc['name']
if(obj.location_details):
summary = summary + ", " + obj.location_details
summary = summary + ", " + api_loc['address']
if 'address' in api_loc:
summary = summary + ", " + api_loc['address']
return summary
return None

Expand Down
31 changes: 30 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 @@ -23,11 +24,19 @@ def events_search(request):
if to_date:
queries.append(Q(start__lt=to_date))

include_sub_departments = True
subdepartments = request.GET.get("subdepartments")
print subdepartments
if subdepartments and subdepartments == 'false':
print "no subdepartments"
include_sub_departments = False

print include_sub_departments
# map between URL query parameters and their corresponding django ORM query
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, include_sub_departments)),
'topic': lambda topics: Q(topics__uri__in=topics)
}

Expand All @@ -42,6 +51,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)
7 changes: 7 additions & 0 deletions talks/contributors/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,18 @@ def contributors_persons(request):
count = request.GET.get('count', 20)
page = request.GET.get('page', 1)
letter = request.GET.get('letter', None)
persons_missing = request.GET.get('missing', None)

if letter == 'None':
letter = None

args = {'count': count, 'letter': letter}

if persons_missing :
if persons_missing == 'affiliation':
args['missing'] = 'affiliation'
persons = persons.filter(bio='')

if letter:
# filter by letter
persons = persons.filter(lastname__istartswith=letter)
Expand Down
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"
20 changes: 20 additions & 0 deletions talks/events/migrations/0008_auto_20150318_1624.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('events', '0007_auto_20150225_0950'),
]

operations = [
migrations.AlterField(
model_name='person',
name='bio',
field=models.TextField(null=True, verbose_name=b'Affiliation', blank=True),
preserve_default=True,
),
]
2 changes: 1 addition & 1 deletion talks/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class Person(models.Model):
name = models.CharField(max_length=250)
lastname = models.CharField(max_length=250, blank=True)
slug = models.SlugField()
bio = models.TextField(verbose_name="Affiliation")
bio = models.TextField(verbose_name="Affiliation", null=True, blank=True)
email_address = models.EmailField(max_length=254,
null=True,
blank=True)
Expand Down
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

0 comments on commit 0bbdc98

Please sign in to comment.