# along with Patchwork; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+from distutils.version import StrictVersion
+
from django.conf import settings
from django.shortcuts import get_object_or_404
from rest_framework import permissions
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from rest_framework.serializers import HyperlinkedIdentityField
+from rest_framework.serializers import HyperlinkedModelSerializer
class LinkHeaderPagination(PageNumberPagination):
request=request,
format=format,
)
+
+
+class BaseHyperlinkedModelSerializer(HyperlinkedModelSerializer):
+
+ def to_representation(self, instance):
+ data = super(BaseHyperlinkedModelSerializer, self).to_representation(
+ instance)
+
+ request = self.context.get('request')
+ if not request or not request.version:
+ # without version information, we have to assume the latest
+ return data
+
+ requested_version = StrictVersion(request.version)
+
+ for version in getattr(self.Meta, 'versioned_fields', {}):
+ # if the user has requested a version lower that than in which the
+ # field was added, we drop it
+ required_version = StrictVersion(version)
+ if required_version > requested_version:
+ for field in self.Meta.versioned_fields[version]:
+ data.pop(field)
+
+ return data
from rest_framework.generics import ListAPIView
from rest_framework.generics import RetrieveAPIView
-from rest_framework.serializers import HyperlinkedModelSerializer
from rest_framework.serializers import SerializerMethodField
+from patchwork.api.base import BaseHyperlinkedModelSerializer
from patchwork.api.filters import CoverLetterFilter
from patchwork.api.embedded import PersonSerializer
from patchwork.api.embedded import ProjectSerializer
from patchwork.models import CoverLetter
-class CoverLetterListSerializer(HyperlinkedModelSerializer):
+class CoverLetterListSerializer(BaseHyperlinkedModelSerializer):
project = ProjectSerializer(read_only=True)
submitter = PersonSerializer(read_only=True)
fields = ('id', 'url', 'project', 'msgid', 'date', 'name', 'submitter',
'mbox', 'series')
read_only_fields = fields
+ versioned_fields = {
+ '1.1': ('mbox', ),
+ }
extra_kwargs = {
'url': {'view_name': 'api-cover-detail'},
}
"""
from rest_framework.serializers import CharField
-from rest_framework.serializers import HyperlinkedModelSerializer
from rest_framework.serializers import SerializerMethodField
+from patchwork.api.base import BaseHyperlinkedModelSerializer
from patchwork.api.base import CheckHyperlinkedIdentityField
from patchwork import models
-class MboxMixin(HyperlinkedModelSerializer):
+class MboxMixin(BaseHyperlinkedModelSerializer):
"""Embed an link to the mbox URL.
This field is just way too useful to leave out of even the embedded
return request.build_absolute_uri(instance.get_mbox_url())
-class BundleSerializer(MboxMixin, HyperlinkedModelSerializer):
+class BundleSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
class Meta:
model = models.Bundle
}
-class CheckSerializer(HyperlinkedModelSerializer):
+class CheckSerializer(BaseHyperlinkedModelSerializer):
url = CheckHyperlinkedIdentityField('api-check-detail')
}
-class CoverLetterSerializer(MboxMixin, HyperlinkedModelSerializer):
+class CoverLetterSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
class Meta:
model = models.CoverLetter
fields = ('id', 'url', 'msgid', 'date', 'name', 'mbox')
read_only_fields = fields
+ versioned_field = {
+ '1.1': ('mbox', ),
+ }
extra_kwargs = {
'url': {'view_name': 'api-cover-detail'},
}
-class PatchSerializer(MboxMixin, HyperlinkedModelSerializer):
+class PatchSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
class Meta:
model = models.Patch
}
-class PersonSerializer(HyperlinkedModelSerializer):
+class PersonSerializer(BaseHyperlinkedModelSerializer):
class Meta:
model = models.Person
}
-class ProjectSerializer(HyperlinkedModelSerializer):
+class ProjectSerializer(BaseHyperlinkedModelSerializer):
link_name = CharField(max_length=255, source='linkname')
list_id = CharField(max_length=255, source='listid')
}
-class SeriesSerializer(MboxMixin, HyperlinkedModelSerializer):
+class SeriesSerializer(MboxMixin, BaseHyperlinkedModelSerializer):
class Meta:
model = models.Series
}
-class UserSerializer(HyperlinkedModelSerializer):
+class UserSerializer(BaseHyperlinkedModelSerializer):
class Meta:
model = models.User
}
-class UserProfileSerializer(HyperlinkedModelSerializer):
+class UserProfileSerializer(BaseHyperlinkedModelSerializer):
username = CharField(source='user.username')
first_name = CharField(source='user.first_name')
from rest_framework.generics import ListAPIView
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.serializers import CharField
-from rest_framework.serializers import HyperlinkedModelSerializer
+from patchwork.api.base import BaseHyperlinkedModelSerializer
from patchwork.api.base import PatchworkPermission
from patchwork.api.embedded import UserProfileSerializer
from patchwork.models import Project
-class ProjectSerializer(HyperlinkedModelSerializer):
+class ProjectSerializer(BaseHyperlinkedModelSerializer):
link_name = CharField(max_length=255, source='linkname')
list_id = CharField(max_length=255, source='listid')
'web_url', 'scm_url', 'webscm_url', 'maintainers',
'subject_match')
read_only_fields = ('name', 'maintainers', 'subject_match')
+ versioned_fields = {
+ '1.1': ('subject_match', ),
+ }
extra_kwargs = {
'url': {'view_name': 'api-project-detail'},
}
fixtures = ['default_tags']
@staticmethod
- def api_url(item=None):
+ def api_url(item=None, version=None):
+ kwargs = {}
+ if version:
+ kwargs['version'] = version
+
if item is None:
- return reverse('api-cover-list')
- return reverse('api-cover-detail', args=[item])
+ return reverse('api-cover-list', kwargs=kwargs)
+ return reverse('api-cover-detail', args=[item], kwargs=kwargs)
def assertSerialized(self, cover_obj, cover_json):
self.assertEqual(cover_obj.id, cover_json['id'])
'submitter': 'test@example.org'})
self.assertEqual(0, len(resp.data))
+ # test old version of API
+ resp = self.client.get(self.api_url(version='1.0'))
+ self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ self.assertEqual(1, len(resp.data))
+ self.assertNotIn('mbox', resp.data[0])
+
def test_detail(self):
"""Validate we can get a specific cover letter."""
cover_obj = create_cover()
class TestProjectAPI(APITestCase):
@staticmethod
- def api_url(item=None):
+ def api_url(item=None, version=None):
+ kwargs = {}
+ if version:
+ kwargs['version'] = version
+
if item is None:
- return reverse('api-project-list')
- return reverse('api-project-detail', args=[item])
+ return reverse('api-project-list', kwargs=kwargs)
+ return reverse('api-project-detail', args=[item], kwargs=kwargs)
def assertSerialized(self, project_obj, project_json):
self.assertEqual(project_obj.id, project_json['id'])
self.assertEqual(project_obj.name, project_json['name'])
self.assertEqual(project_obj.linkname, project_json['link_name'])
self.assertEqual(project_obj.listid, project_json['list_id'])
+ self.assertEqual(project_obj.subject_match,
+ project_json['subject_match'])
# nested fields
self.assertEqual(1, len(resp.data))
self.assertSerialized(project, resp.data[0])
+ # test old version of API
+ resp = self.client.get(self.api_url(version='1.0'))
+ self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ self.assertEqual(1, len(resp.data))
+ self.assertNotIn('subject_match', resp.data[0])
+
def test_detail(self):
"""Validate we can get a specific project."""
project = create_project()
]
urlpatterns += [
- url(r'^api/(?:(?P<version>(1.0))/)?', include(api_patterns)),
+ url(r'^api/(?:(?P<version>(1.0|1.1))/)?', include(api_patterns)),
# token change
url(r'^user/generate-token/$', user_views.generate_token,