Skip to content

Commit b23a57b

Browse files
committed
Merge branch 'release/2.3.0'
2 parents 1293de3 + 1685857 commit b23a57b

31 files changed

Lines changed: 659 additions & 436 deletions

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Change Log
22

3+
## [2.3.0] - 2016-02-02
4+
## Added
5+
* Support for OSF Registries
6+
* New Harvesters
7+
* University of Utah
8+
9+
## Changed
10+
* Updated the API
11+
* Improved Elasticsearch mappings
12+
* Updated NIH and NSFAwards
13+
* Affiliations are now gathered
14+
* Non-Unique URLs are no longer collected
15+
* Lots of under the hood changes to make dev's lives easier
16+
317
## [2.1.0] - 2016-12-16
418
## Added
519
* New Harvesters

api/filters.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import django_filters
22
import shortuuid
33

4-
from share.models import ChangeSet, ShareObject, Change
4+
from share.models import ShareObject
55

66

77
class ObjectIDFilter(django_filters.filters.CharFilter):
@@ -18,27 +18,3 @@ class ShareObjectFilterSet(django_filters.FilterSet):
1818
class Meta:
1919
model = ShareObject
2020
fields = ['object_id', ]
21-
22-
23-
class ChangeSetFilterSet(django_filters.FilterSet):
24-
status = django_filters.MethodFilter()
25-
target_uuid = django_filters.filters.UUIDFilter(name='changes__share_objects__uuid')
26-
submitted_by = django_filters.filters.NumberFilter(name='normalized_data__source')
27-
28-
def filter_status(self, queryset, value):
29-
# django-filters ChoicesFilter doesn't actually work, at least not with django-model-utils choices
30-
if value and hasattr(ChangeSet.STATUS, value):
31-
return queryset.filter(status=getattr(ChangeSet.STATUS, value))
32-
return queryset
33-
34-
class Meta:
35-
model = ChangeSet
36-
fields = ['submitted_by', 'status', 'target_uuid']
37-
38-
39-
class ChangeFilterSet(django_filters.FilterSet):
40-
changeset = django_filters.filters.NumberFilter(name='change_set_id')
41-
42-
class Meta:
43-
model = Change
44-
fields = ['changeset']

api/renderers.py

Lines changed: 196 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@
33
from collections import OrderedDict
44

55
from django.utils import encoding
6+
from django.apps import apps
67

78
# from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS, INDENT_SEPARATORS
89
from rest_framework.renderers import JSONRenderer
910
from rest_framework.settings import api_settings
1011
from rest_framework.utils import encoders
1112
from rest_framework import relations
12-
from rest_framework.serializers import BaseSerializer
13+
from rest_framework.serializers import BaseSerializer, Serializer, ListSerializer
1314

1415
from rest_framework_json_api.renderers import JSONRenderer as JSONAPIRenderer
1516
from rest_framework_json_api import utils
1617

18+
from share.util import IDObfuscator
19+
1720

1821
class HideNullJSONAPIRenderer(JSONAPIRenderer):
1922

23+
# override null behavior from JSONAPIRenderer
2024
@staticmethod
2125
def extract_attributes(fields, resource):
2226
data = OrderedDict()
@@ -46,11 +50,201 @@ def extract_attributes(fields, resource):
4650

4751
return utils.format_keys(data)
4852

53+
def encode_id(resource_id, resource_type):
54+
return encoding.force_text(IDObfuscator.encode_id(resource_id, apps.get_model('share', resource_type)))
55+
56+
@classmethod
57+
def encode_ids(cls, relation_data):
58+
if relation_data:
59+
if isinstance(relation_data, list):
60+
for obj in relation_data:
61+
obj['id'] = cls.encode_id(int(obj['id']), obj['type'])
62+
else:
63+
relation_data['id'] = cls.encode_id(int(relation_data['id']), relation_data['type'])
64+
return relation_data
65+
66+
# override ids in relationships from JSONAPIRenderer
67+
@classmethod
68+
def extract_relationships(cls, fields, resource, resource_instance):
69+
# Avoid circular deps
70+
from rest_framework_json_api.relations import ResourceRelatedField
71+
72+
data = OrderedDict()
73+
74+
# Don't try to extract relationships from a non-existent resource
75+
if resource_instance is None:
76+
return
77+
78+
for field_name, field in six.iteritems(fields):
79+
# Skip URL field
80+
if field_name == api_settings.URL_FIELD_NAME:
81+
continue
82+
83+
# Skip fields without relations
84+
if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)):
85+
continue
86+
87+
source = field.source
88+
relation_type = utils.get_related_resource_type(field)
89+
90+
if isinstance(field, relations.HyperlinkedIdentityField):
91+
resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent)
92+
if not resolved:
93+
continue
94+
# special case for HyperlinkedIdentityField
95+
relation_data = list()
96+
97+
# Don't try to query an empty relation
98+
relation_queryset = relation_instance \
99+
if relation_instance is not None else list()
100+
101+
for related_object in relation_queryset:
102+
relation_data.append(
103+
OrderedDict([('type', relation_type), ('id', encoding.force_text(related_object.pk))])
104+
)
105+
106+
data.update({field_name: {
107+
'links': {
108+
'related': resource.get(field_name)},
109+
'data': cls.encode_ids(relation_data),
110+
'meta': {
111+
'count': len(relation_data)
112+
}
113+
}})
114+
continue
115+
116+
if isinstance(field, ResourceRelatedField):
117+
resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent)
118+
if not resolved:
119+
continue
120+
121+
# special case for ResourceRelatedField
122+
relation_data = {
123+
'data': cls.encode_ids(resource.get(field_name))
124+
}
125+
126+
field_links = field.get_links(resource_instance)
127+
relation_data.update(
128+
{'links': field_links}
129+
if field_links else dict()
130+
)
131+
data.update({field_name: relation_data})
132+
continue
133+
134+
if isinstance(field, (relations.PrimaryKeyRelatedField, relations.HyperlinkedRelatedField)):
135+
resolved, relation = utils.get_relation_instance(resource_instance, '%s_id' % source, field.parent)
136+
if not resolved:
137+
continue
138+
relation_id = relation if resource.get(field_name) else None
139+
relation_data = {
140+
'data': (
141+
OrderedDict([('type', relation_type), ('id', cls.encode_id(relation_id, relation_type))])
142+
if relation_id is not None else None)
143+
}
144+
145+
relation_data.update(
146+
{'links': {'related': resource.get(field_name)}}
147+
if isinstance(field, relations.HyperlinkedRelatedField) and resource.get(field_name) else dict()
148+
)
149+
data.update({field_name: relation_data})
150+
continue
151+
152+
if isinstance(field, relations.ManyRelatedField):
153+
resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent)
154+
if not resolved:
155+
continue
156+
157+
if isinstance(field.child_relation, ResourceRelatedField):
158+
# special case for ResourceRelatedField
159+
relation_data = {
160+
'data': cls.encode_ids(resource.get(field_name))
161+
}
162+
163+
field_links = field.child_relation.get_links(resource_instance)
164+
relation_data.update(
165+
{'links': field_links}
166+
if field_links else dict()
167+
)
168+
relation_data.update(
169+
{
170+
'meta': {
171+
'count': len(resource.get(field_name))
172+
}
173+
}
174+
)
175+
data.update({field_name: relation_data})
176+
continue
177+
178+
relation_data = list()
179+
for nested_resource_instance in relation_instance:
180+
nested_resource_instance_type = (
181+
relation_type or
182+
utils.get_resource_type_from_instance(nested_resource_instance)
183+
)
184+
185+
relation_data.append(OrderedDict([
186+
('type', nested_resource_instance_type),
187+
('id', encoding.force_text(nested_resource_instance.pk))
188+
]))
189+
data.update({
190+
field_name: {
191+
'data': cls.encode_ids(relation_data),
192+
'meta': {
193+
'count': len(relation_data)
194+
}
195+
}
196+
})
197+
continue
198+
199+
if isinstance(field, ListSerializer):
200+
resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent)
201+
if not resolved:
202+
continue
203+
204+
relation_data = list()
205+
206+
serializer_data = resource.get(field_name)
207+
resource_instance_queryset = list(relation_instance)
208+
if isinstance(serializer_data, list):
209+
for position in range(len(serializer_data)):
210+
nested_resource_instance = resource_instance_queryset[position]
211+
nested_resource_instance_type = (
212+
relation_type or
213+
utils.get_resource_type_from_instance(nested_resource_instance)
214+
)
215+
216+
relation_data.append(OrderedDict([
217+
('type', nested_resource_instance_type),
218+
('id', encoding.force_text(nested_resource_instance.pk))
219+
]))
220+
221+
data.update({field_name: {'data': cls.encode_ids(relation_data)}})
222+
continue
223+
224+
if isinstance(field, Serializer):
225+
resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent)
226+
if not resolved:
227+
continue
228+
229+
data.update({
230+
field_name: {
231+
'data': (
232+
OrderedDict([
233+
('type', relation_type),
234+
('id', cls.encode_id(resource_instance.pk, relation_type))
235+
]) if resource.get(field_name) else None)
236+
}
237+
})
238+
continue
239+
240+
return utils.format_keys(data)
241+
242+
# override top level id from JSONAPIRenderer
49243
@classmethod
50244
def build_json_resource_obj(cls, fields, resource, resource_instance, resource_name):
51245
resource_data = [
52246
('type', resource_name),
53-
('id', encoding.force_text(resource_instance.pk) if resource_instance else None),
247+
('id', cls.encode_id(resource_instance.pk, resource_instance._meta.model.__name__) if resource_instance and resource_instance.pk else None),
54248
('attributes', cls.extract_attributes(fields, resource)),
55249
]
56250
relationships = cls.extract_relationships(fields, resource, resource_instance)

api/serializers.py

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,55 @@
55
from rest_framework_json_api import serializers
66

77
from share import models
8-
from share.models import ChangeSet, ProviderRegistration, CeleryProviderTask, SiteBanner
8+
from share.models import ProviderRegistration, CeleryProviderTask, SiteBanner
9+
10+
from api import fields
11+
12+
13+
class BaseShareSerializer(serializers.ModelSerializer):
14+
15+
def __init__(self, *args, **kwargs):
16+
# super hates my additional kwargs
17+
sparse = kwargs.pop('sparse', False)
18+
version_serializer = kwargs.pop('version_serializer', False)
19+
super(BaseShareSerializer, self).__init__(*args, **kwargs)
20+
21+
if sparse:
22+
# clear the fields if they asked for sparse
23+
self.fields.clear()
24+
else:
25+
# remove hidden fields
26+
excluded_fields = ['change', 'sources']
27+
for field_name in tuple(self.fields.keys()):
28+
if 'version' in field_name or field_name in excluded_fields:
29+
self.fields.pop(field_name)
30+
31+
if not version_serializer:
32+
# add links to related objects
33+
self.fields.update({
34+
'links': fields.LinksField(links=self.Meta.links, source='*')
35+
})
36+
37+
# version specific fields
38+
if version_serializer:
39+
self.fields.update({
40+
'action': serializers.CharField(max_length=10),
41+
'persistent_id': serializers.IntegerField()
42+
})
43+
44+
# add fields with improper names
45+
self.fields.update({
46+
'type': fields.TypeField(),
47+
})
48+
49+
class Meta:
50+
links = ('versions', 'changes', 'rawdata')
51+
52+
# http://stackoverflow.com/questions/27015931/remove-null-fields-from-django-rest-framework-response
53+
def to_representation(self, instance):
54+
ret = super(BaseShareSerializer, self).to_representation(instance)
55+
ret = OrderedDict(list(filter(lambda x: x[1] is not None, ret.items())))
56+
return ret
957

1058

1159
class ShareModelSerializer(serializers.ModelSerializer):
@@ -57,15 +105,6 @@ class Meta:
57105
fields = ('data', 'source')
58106

59107

60-
class ChangeSerializer(ShareModelSerializer):
61-
self = serializers.HyperlinkedIdentityField(view_name='api:change-detail')
62-
target_type = serializers.StringRelatedField()
63-
64-
class Meta:
65-
model = models.Change
66-
fields = ('self', 'id', 'change', 'node_id', 'type', 'target_type', 'target_id')
67-
68-
69108
class ShareUserSerializer(ShareModelSerializer):
70109
def __init__(self, *args, token=None, **kwargs):
71110
super(ShareUserSerializer, self).__init__(*args, **kwargs)
@@ -100,24 +139,6 @@ class Meta:
100139
)
101140

102141

103-
class ChangeSetSerializer(ShareModelSerializer):
104-
# changes = ChangeSerializer(many=True)
105-
change_count = serializers.SerializerMethodField()
106-
self = serializers.HyperlinkedIdentityField(view_name='api:changeset-detail')
107-
source = ShareUserSerializer(source='normalized_data.source')
108-
status = serializers.SerializerMethodField()
109-
110-
def get_status(self, obj):
111-
return ChangeSet.STATUS[obj.status]
112-
113-
def get_change_count(self, obj):
114-
return obj.changes.count()
115-
116-
class Meta:
117-
model = models.ChangeSet
118-
fields = ('self', 'id', 'submitted_at', 'change_count', 'source', 'status')
119-
120-
121142
class ProviderSerializer(ShareUserSerializer):
122143
def __init__(self, *args, **kwargs):
123144
super(ShareUserSerializer, self).__init__(*args, **kwargs)

0 commit comments

Comments
 (0)