Skip to content

Commit

Permalink
update for DRF3 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
sheppard committed Mar 11, 2015
1 parent e5aa51c commit 7f5359c
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 35 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ python:
env:
matrix:
- DJANGO="django==1.6.10" REST="djangorestframework==2.4.4"
- DJANGO="django==1.6.10" REST="djangorestframework==3.0.3"
- DJANGO="django==1.7.3" REST="djangorestframework==2.4.4"
- DJANGO="django==1.7.3" REST="djangorestframework==3.0.3"
- DJANGO="django==1.6.10" REST="djangorestframework==3.1.0"
- DJANGO="django==1.7.6" REST="djangorestframework==2.4.4"
- DJANGO="django==1.7.6" REST="djangorestframework==3.1.0"
global:
- PANDAS="pandas==0.15.2"
install:
Expand Down
5 changes: 5 additions & 0 deletions rest_pandas/renderers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rest_framework.renderers import BaseRenderer
from rest_framework import status
from tempfile import mkstemp
from pandas import DataFrame

try:
# Python 2 (uses str)
Expand All @@ -24,6 +25,10 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
if not status.is_success(status_code):
return "Error: %s" % data.get('detail', status_code)

if not isinstance(data, DataFrame):
raise Exception(
"Response data is a %s, not a DataFrame!" % type(data)
)
name = getattr(self, 'function', "to_%s" % self.format)
function = getattr(data, name, None)
if not function:
Expand Down
40 changes: 23 additions & 17 deletions rest_pandas/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@
from pandas import DataFrame


class PandasBaseSerializer(serializers.Serializer):
if hasattr(serializers, 'ListSerializer'):
# Django REST Framework 3
BaseSerializer = serializers.ListSerializer
USE_LIST_SERIALIZERS = True
else:
# Django REST Framework 2
BaseSerializer = serializers.Serializer
USE_LIST_SERIALIZERS = False


class PandasSerializer(BaseSerializer):
"""
Transforms dataset into a dataframe and appies an index
Transforms dataset into a dataframe and applies an index
"""
read_only = True
index_none_value = None

def get_index(self, dataframe):
model_serializer = getattr(self, 'child', self)
if getattr(model_serializer.Meta, 'model', None):
return ['id']
return None

def get_dataframe(self, data):
Expand All @@ -33,30 +46,23 @@ def transform_dataframe(self, dataframe):

@property
def data(self):
data = super(PandasBaseSerializer, self).data
data = super(PandasSerializer, self).data
if data:
dataframe = self.get_dataframe(data)
return self.transform_dataframe(dataframe)
else:
return DataFrame([])


class PandasSimpleSerializer(PandasBaseSerializer):
class SimpleSerializer(serializers.Serializer):
"""
Simple serializer for non-model (simple) views
"""
def get_default_fields(self):
if not self.object:
return {}
return {
name: serializers.Field()
for name in self.object[0].keys()
}

# DRF 3
def to_representation(self, obj):
return obj

class PandasSerializer(PandasBaseSerializer, serializers.ModelSerializer):
"""
Serializer for model views.
"""
def get_index(self, dataframe):
return ['id']
# DRF 2
def to_native(self, obj):
return obj
53 changes: 40 additions & 13 deletions rest_pandas/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from django.conf import settings
from rest_framework.settings import perform_import

from .serializers import PandasSimpleSerializer, PandasSerializer
from .serializers import (
SimpleSerializer, PandasSerializer, USE_LIST_SERIALIZERS
)

PANDAS_RENDERERS = getattr(settings, "PANDAS_RENDERERS", None)
if PANDAS_RENDERERS is None:
Expand All @@ -24,36 +26,61 @@
PANDAS_RENDERERS = perform_import(PANDAS_RENDERERS, "PANDAS_RENDERERS")


class PandasSimpleView(APIView):
class PandasMixin(object):
renderer_classes = PANDAS_RENDERERS
paginate_by = None
pandas_serializer_class = PandasSerializer

def with_list_serializer(self, cls):
if not USE_LIST_SERIALIZERS:
# Django REST Framework 2 used the instance serializer for lists
class SerializerWithListSerializer(
self.pandas_serializer_class, cls):
pass
else:

# DRF3 uses a separate list_serializer_class; set if not present
meta = getattr(cls, 'Meta', object)
if getattr(meta, 'list_serializer_class', None):
return cls

class SerializerWithListSerializer(cls):
class Meta(meta):
list_serializer_class = self.pandas_serializer_class

return SerializerWithListSerializer


class PandasSimpleView(PandasMixin, APIView):
"""
Simple (non-model) Pandas API view; override get_data
with a function that returns a list of dicts.
"""
serializer_class = PandasSimpleSerializer
renderer_classes = PANDAS_RENDERERS
serializer_class = SimpleSerializer

def get_data(self, request, *args, **kwargs):
return []

def get(self, request, *args, **kwargs):
data = self.get_data(request, *args, **kwargs)
serializer = self.serializer_class(data, many=True)
serializer_class = self.with_list_serializer(self.serializer_class)
serializer = serializer_class(data, many=True)
return Response(serializer.data)


class PandasView(ListAPIView):
class PandasView(PandasMixin, ListAPIView):
"""
Pandas-capable model list view
"""
model_serializer_class = PandasSerializer
renderer_classes = PANDAS_RENDERERS
paginate_by = None
def get_serializer_class(self, *args, **kwargs):
cls = super(PandasView, self).get_serializer_class(*args, **kwargs)
return self.with_list_serializer(cls)


class PandasViewSet(ListModelMixin, GenericViewSet):
class PandasViewSet(PandasMixin, ListModelMixin, GenericViewSet):
"""
Pandas-capable model ViewSet (list only)
"""
model_serializer_class = PandasSerializer
renderer_classes = PANDAS_RENDERERS
paginate_by = None
def get_serializer_class(self, *args, **kwargs):
cls = super(PandasViewSet, self).get_serializer_class(*args, **kwargs)
return self.with_list_serializer(cls)
7 changes: 7 additions & 0 deletions tests/testapp/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rest_framework.serializers import ModelSerializer
from .models import TimeSeries


class TimeSeriesSerializer(ModelSerializer):
class Meta:
model = TimeSeries
11 changes: 9 additions & 2 deletions tests/testapp/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from rest_pandas import PandasSimpleView, PandasView, PandasViewSet
from .models import TimeSeries
from .serializers import TimeSeriesSerializer


class NoModelView(PandasSimpleView):
Expand All @@ -13,8 +14,14 @@ def get_data(self, request, *args, **kwargs):


class TimeSeriesView(PandasView):
model = TimeSeries
queryset = TimeSeries.objects.all()
serializer_class = TimeSeriesSerializer

def transform_dataframe(self, df):
df['date'] = df['date'].astype('datetime64[D]')
return df


class TimeSeriesViewSet(PandasViewSet):
model = TimeSeries
queryset = TimeSeries.objects.all()
serializer_class = TimeSeriesSerializer

0 comments on commit 7f5359c

Please sign in to comment.