Skip to content

Commit

Permalink
Merge pull request #186 from ox-it/django-rest-3-0
Browse files Browse the repository at this point in the history
Django REST framework 3.0
  • Loading branch information
martinfilliau committed Jan 12, 2015
2 parents bc9ba55 + a2a17b4 commit 21caeaa
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 39 deletions.
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
Django==1.7.2
pytz
djangorestframework==2.3.13
djangorestframework==3.0.3
icalendar==3.6.2
requests==2.3.0
pysolr==3.2.0
git+https://github.com/ox-it/django-haystack.git#egg=django_haystack
django-bootstrap-form==3.1
psycopg2==2.5.3
raven==5.0.0
git+https://github.com/ox-it/django-webauth.git@0.4#egg=django-webauth
python-ldap==2.4.15
Expand Down
81 changes: 54 additions & 27 deletions talks/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,83 @@
from talks.events.models import Event, Person, EventGroup
from talks.users.models import CollectionItem

class PersonSerializer(serializers.ModelSerializer):

title = serializers.SerializerMethodField(method_name='formatted_title')

def formatted_title(self, obj):
if obj.bio:
return obj.name + ', ' + obj.bio
else:
return obj.name

class Meta:
model = Person
fields = ('id', 'name', 'bio', 'title')


class ClassNameField(serializers.Field):
def field_to_native(self, obj, field_name):
"""
Serialize the object's class name.
"""
return obj.__class__.__name__
"""
Pass the entire object in the get_attribute method, then render its representation by returning the class name
"""
def get_attribute(self, obj):
return obj

def to_representation(self, value):
return value.__class__.__name__


class EventSerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url',
read_only=True)
formatted_date = serializers.CharField(source='formatted_date',
read_only=True)
formatted_time = serializers.CharField(source='formatted_time',
read_only=True)
happening_today = serializers.BooleanField(source='happening_today',
read_only=True)
formatted_date = serializers.CharField(read_only=True)
formatted_time = serializers.CharField(read_only=True)
happening_today = serializers.BooleanField(read_only=True)
speakers = PersonSerializer(many=True, read_only=True)
organisers = PersonSerializer(many=True, read_only=True)
hosts = PersonSerializer(many=True, read_only=True)
class_name = ClassNameField()

class Meta:
model = Event
fields = ('slug', 'url', 'title', 'start', 'end', 'description',
'formatted_date', 'formatted_time', 'happening_today',
'class_name')
'formatted_date', 'formatted_time', 'speakers', 'organisers', 'hosts', 'happening_today', 'audience', 'api_location',
'api_organisation', 'api_topics', 'class_name')


class SpeakerSerializer(serializers.ModelSerializer):
"""
Serialize a speaker and all the events that they are speaking at
"""
speaker_events=EventSerializer(many=True, read_only=True)

class Meta:
model = Person
fields = ('name', 'bio', 'speaker_events')


class EventGroupSerializer(serializers.ModelSerializer):
class_name = ClassNameField()
url = serializers.CharField(source='get_absolute_url',
read_only=True)
organisers = PersonSerializer(many=True, read_only=True)

class Meta:
model = EventGroup
fields = ('id', 'slug', 'url', 'title', 'description', 'class_name', 'department_organiser')

model = EventGroup
fields = ('id', 'slug', 'url', 'title', 'description', 'class_name', 'organisers', 'department_organiser')

class PersonSerializer(serializers.ModelSerializer):

title = serializers.SerializerMethodField(method_name='formatted_title')

def formatted_title(self, obj):
if obj.bio:
return obj.name + ', ' + obj.bio
else:
return obj.name
class EventGroupWithEventsSerializer(serializers.ModelSerializer):
"""
Serialize an event group and include info on all constitutent events
"""
events = EventSerializer(many=True, read_only=True)

class Meta:
model = Person
fields = ('id', 'name', 'bio', 'title')
fields = ('id', 'title', 'description', 'department_organiser', 'events')
model = EventGroup



class UserSerializer(serializers.ModelSerializer):
Expand All @@ -76,7 +103,7 @@ class CollectionItemRelatedField(serializers.RelatedField):
A custom field to use for the `item` generic relationship.
"""

def to_native(self, value):
def to_representation(self, value):
"""
Serialize event instances using a event serializer,
"""
Expand All @@ -85,7 +112,7 @@ def to_native(self, value):


class CollectionItemSerializer(serializers.ModelSerializer):
item = CollectionItemRelatedField()
item = CollectionItemRelatedField(queryset=CollectionItem.objects.all())

class Meta:
fields = ('id', 'item', 'collection')
Expand Down
109 changes: 102 additions & 7 deletions talks/api/views.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
import logging
from datetime import date, datetime, timedelta
from django.contrib.auth.models import User

from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
import operator
from django.http.response import HttpResponse

from rest_framework import viewsets, status, permissions
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.exceptions import ParseError
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer, JSONPRenderer, XMLRenderer
from rest_framework.response import Response

from talks.events.models import Event, EventGroup, Person
from talks.events.models import Event, EventGroup, Person, ROLES_SPEAKER, TopicItem
from talks.users.authentication import GROUP_EDIT_EVENTS, user_in_group_or_super
from talks.users.models import Collection
from talks.api.serializers import (EventSerializer, PersonSerializer, EventGroupSerializer, UserSerializer,
from talks.api.serializers import (EventSerializer, PersonSerializer, SpeakerSerializer, EventGroupSerializer, EventGroupWithEventsSerializer, UserSerializer,
CollectionItemSerializer,
get_item_serializer)
from talks.core.renderers import ICalRenderer

logger = logging.getLogger(__name__)


class EventViewSet(viewsets.ModelViewSet):
class EventViewSet(viewsets.ReadOnlyModelViewSet):
"""API endpoint for events
"""
renderer_classes = (ICalRenderer, JSONRenderer, JSONPRenderer, XMLRenderer)
queryset = Event.objects.all()
serializer_class = EventSerializer
lookup_field = 'slug'


class EventGroupViewSet(viewsets.ReadOnlyModelViewSet):
renderer_classes = (ICalRenderer, JSONPRenderer, JSONPRenderer, XMLRenderer)
queryset = EventGroup.objects.all()
serializer_class = EventGroupWithEventsSerializer
lookup_field = 'slug'


class IsSuperuserOrContributor(permissions.BasePermission):
Expand All @@ -37,25 +49,27 @@ class IsSuperuserOrContributor(permissions.BasePermission):
def has_permission(self, request, view):
return user_in_group_or_super(request.user)

# These views are typically used by ajax

# These views are typically used by ajax
@authentication_classes((SessionAuthentication,))
@permission_classes((IsAuthenticated, IsSuperuserOrContributor,))
@api_view(["POST"])
def api_create_person(request):
serializer = PersonSerializer(data=request.DATA)
serializer = PersonSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(["GET"])
def suggest_person(request):
query = request.GET.get('q', '')
persons = Person.objects.suggestions(query)
serializer = PersonSerializer(persons, many=True)
return Response(serializer.data)


@api_view(["GET"])
@authentication_classes((SessionAuthentication,))
@permission_classes((IsAuthenticated, IsSuperuserOrContributor,))
Expand All @@ -66,8 +80,12 @@ def suggest_user(request):
serializer = UserSerializer(users, many=True)
return Response(serializer.data)


@api_view(["GET"])
def get_event_group(request, event_group_id):
"""
Used via ajax to retrieve group details when changing selection
"""
try:
eg = EventGroup.objects.get(id=event_group_id)
except ObjectDoesNotExist:
Expand All @@ -78,9 +96,86 @@ def get_event_group(request, event_group_id):
return Response(serializer.data, status=status.HTTP_200_OK)


@api_view(["GET"])
def api_event_search(request):
"""
Return a list of events based on the query term
:param query:
Query which can include the terms
from=<dd/mm/yy> - events starting after this date
to=<dd/mm/yy> - events starting before this date
speaker=<speaker_slug> - event where the given speaker is speaking
venue=<oxpoints_id> - events taking place in the specified building (or their children)
subvenues=<bool=False> - include children of the specified building when searching on venue
dept=<oxpoints_id> - events with any of organising_department set as the specified oxpoints entity
subdepts=<bool=False> - include children of the specified depts when searching on dept
topic=<topic_uri> - events featuring the specified listed topic as FAST URIs
:return:
"""
queries = []

from_date = parse_date(request.GET.get("from"))
if not from_date:
raise ParseError(detail="'from' parameter is mandatory. Supply either 'today' or a date in form 'dd/mm/yy'.")
else:
queries.append(Q(start__gt=from_date))

to_date = parse_date(request.GET.get("to"))
if to_date:
queries.append(Q(start__lt=to_date))

speakers = request.GET.getlist("speaker")
if speakers:
queries.append(Q(personevent__role=ROLES_SPEAKER, personevent__person__slug__in=speakers))

venues = request.GET.getlist("venue")
if venues:
queries.append(Q(location__in=venues))

depts = request.GET.getlist("organising_department")
if depts:
queries.append(Q(department_organiser__in=depts))

topics = request.GET.getlist("topic")
if topics:
queries.append(Q(topics__uri__in=topics))

final_query = reduce(operator.and_, queries)
events = Event.published.filter(final_query)
serializer = EventSerializer(events, many=True, read_only=True)
return Response(serializer.data, status=status.HTTP_200_OK)


def parse_date(date_param):
"""
Parse the date string parameter
:param date_param:
Either a keyword:
'today', 'tomorrow'
or a string in the format 'dd/mm/yy'
:return:
datetime object
"""
if not date_param:
return None
elif date_param == "today":
from_date = datetime.today().date()
print from_date
elif date_param == "tomorrow":
from_date = datetime.today().date() + timedelta(1)
print from_date
else:
try:
from_date = datetime.strptime(date_param, "%d/%m/%y")
except Exception as e:
# catch the exception and raise an API exception instead, which the user will see
raise ParseError(e.message);
return from_date


def item_from_request(request):
event_slug = request.DATA.get('event', None)
group_id = request.DATA.get('group', None)
event_slug = request.data.get('event', None)
group_id = request.data.get('group', None)
# Our JS doesn't support sending both
assert not(event_slug and group_id)
try:
Expand Down
5 changes: 5 additions & 0 deletions talks/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ def __unicode__(self):
def get_absolute_url(self):
return reverse('show-person', args=[self.slug])

@property
def speaker_events(self):
events = Event.published.filter(personevent__role=ROLES_SPEAKER,
personevent__person__slug=self.slug)
return events

class TopicItem(models.Model):

Expand Down
10 changes: 7 additions & 3 deletions talks/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
events_for_month, events_for_year, create_event, list_event_groups,
create_event_group, show_event_group, edit_event_group, contributors_home, contributors_events,
contributors_eventgroups, contributors_persons, delete_event, delete_event_group, show_topic)
from talks.api.views import api_event_search
from talks.events_search.forms import DateFacetedSearchForm
from talks.events_search.views import SearchView
from talks.events_search.conf import sqs
from api.views import (EventViewSet, suggest_person, suggest_user, api_create_person,
from api.views import (EventViewSet, EventGroupViewSet, suggest_person, suggest_user, api_create_person,
save_item, remove_item, get_event_group)

from audit_trail.urls import urlpatterns as audit_urls
Expand All @@ -20,19 +21,22 @@

router = routers.DefaultRouter()
router.register(r'events', EventViewSet)

router.register(r'series', EventGroupViewSet)

urlpatterns = patterns('',
# WebAuth login/logout
url(r'^login/$', LoginView.as_view(), name='login'),
url(r'^logout/$', webauth_logout, name='logout'),

url(r'^api/', include(router.urls)),
url(r'^api/series/id/(?P<event_group_id>\d+)', get_event_group, name='get-event-group'),
url(r'^api/events/search$', api_event_search, name='api-search-events'),
url(r'^api/user/suggest$', suggest_user, name='suggest-user'),
url(r'^api/persons/new$', api_create_person, name='api-create-person'),

url(r'^api/collections/me/add$', save_item, name="save-item"),
url(r'^api/collections/me/remove$', remove_item, name="remove-item"),
url(r'^api/series/id/(?P<event_group_id>\d+)', get_event_group, name='get-event-group'),

url(r'^search/', SearchView(form_class=DateFacetedSearchForm, searchqueryset=sqs, load_all=False), name='haystack_search'),
url(r'^$', homepage, name='homepage'),
url(r'^talks$', upcoming_events, name='upcoming_events'),
Expand Down

0 comments on commit 21caeaa

Please sign in to comment.