Skip to content

Commit 97a9451

Browse files
committed
Merge branch 'release/2.5.0' into develop
2 parents e69207e + 8a4463b commit 97a9451

30 files changed

+205
-1055
lines changed

CHANGELOG.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
# Change Log
22

3-
## [2.4.0] - 2016-02-10
3+
## [2.5.0] - 2017-03-15
4+
## Added
5+
* Title now has an "exact" multi-field in elasticsearch
6+
* A robot that archives old succeeded celery jobs
7+
* New Harvesters
8+
* Scholarly Commons @ JMU
9+
10+
## Fixed
11+
* Compensate for potential race conditions with the push API
12+
13+
## [2.4.0] - 2017-02-10
414
## Added
515
* New Harvesters
616
* Research Registry Harvester
@@ -15,7 +25,7 @@
1525
## Fixed
1626
* Removed "Notify" from the page title in the browsable API
1727

18-
## [2.3.0] - 2016-02-02
28+
## [2.3.0] - 2017-02-02
1929
## Added
2030
* Support for OSF Registries
2131
* New Harvesters

api/urls.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from graphene_django.views import GraphQLView
88

99
from share import models
10-
from share.graphql.middleware import SameAsMiddleware
1110

1211
from api import views
1312
from api.pagination import FuzzyPageNumberPagination
@@ -97,7 +96,7 @@ def register_url(self, subclass, viewset):
9796
url(r'status/?', views.ServerStatusView.as_view(), name='status'),
9897
url(r'rss/?', views.CreativeWorksRSS(), name='rss'),
9998
url(r'atom/?', views.CreativeWorksAtom(), name='atom'),
100-
url(r'graph/?', GraphQLView.as_view(graphiql=True, middleware=[SameAsMiddleware])),
99+
url(r'graph/?', GraphQLView.as_view(graphiql=True)),
101100
url(r'userinfo/?', ensure_csrf_cookie(views.ShareUserView.as_view()), name='userinfo'),
102101
url(r'search/(?!.*_bulk\/?$)(?P<url_bits>.*)', csrf_exempt(views.ElasticSearchView.as_view()), name='search'),
103102

api/views/elasticsearch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def get(self, request, *args, url_bits='', **kwargs):
3434
def post(self, request, *args, url_bits='', **kwargs):
3535
# Disallow posting to any non-search endpoint
3636
bits = list(filter(None, url_bits.split('/')))
37-
if len(bits) > 2 or bits[-1] not in ('_search', '_suggest'):
37+
if len(bits) > 2 or bits[-1] not in ('_count', '_search', '_suggest'):
3838
return http.HttpResponseBadRequest()
3939
return self._handle_request(request, url_bits)
4040

api/views/share.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ def get_object(self):
9595

9696
filter_kwargs = {self.lookup_field: decoded_pk}
9797
obj = get_object_or_404(queryset, **filter_kwargs)
98-
if obj.same_as_id:
99-
obj = obj._meta.concrete_model.objects.get_canonical(obj.id)
10098

10199
# May raise a permission denied
102100
self.check_object_permissions(self.request, obj)

bots/elasticsearch/tasks.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,21 @@ def bulk_stream(self, model, ids, es_index):
6060

6161
if model is CreativeWork:
6262
for blob in util.fetch_creativework(ids):
63-
if blob.pop('is_deleted') or blob.pop('same_as'):
63+
if blob.pop('is_deleted'):
6464
yield {'_id': blob['id'], '_op_type': 'delete', **opts}
6565
else:
6666
yield {'_id': blob['id'], '_op_type': 'index', **blob, **opts}
6767
return
6868

6969
if model is Agent:
7070
for blob in util.fetch_agent(ids):
71-
if blob.pop('same_as', None):
72-
yield {'_id': blob['id'], '_op_type': 'delete', **opts}
73-
else:
74-
yield {'_id': blob['id'], '_op_type': 'index', **blob, **opts}
71+
yield {'_id': blob['id'], '_op_type': 'index', **blob, **opts}
7572
return
7673

7774
for inst in model.objects.filter(id__in=ids):
78-
if inst.same_as_id: # TODO is_deleted?
79-
yield {'_id': inst.pk, '_op_type': 'delete', **opts}
80-
else:
81-
yield {'_id': inst.pk, '_op_type': 'index', **self.serialize(inst), **opts}
75+
# if inst.is_delete: # TODO
76+
# yield {'_id': inst.pk, '_op_type': 'delete', **opts}
77+
yield {'_id': inst.pk, '_op_type': 'index', **self.serialize(inst), **opts}
8278

8379
def serialize(self, inst):
8480
return {

bots/elasticsearch/util.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def fetch_agent(pks):
5454
, 'additional_name', agent.additional_name
5555
, 'suffix', agent.suffix
5656
, 'location', agent.location
57-
, 'same_as', agent.same_as_id
5857
, 'sources', COALESCE(sources, '{}')
5958
, 'identifiers', COALESCE(identifiers, '{}')
6059
, 'related_types', COALESCE(related_types, '{}')))
@@ -111,7 +110,6 @@ def fetch_creativework(pks):
111110
, 'title', creativework.title
112111
, 'description', creativework.description
113112
, 'is_deleted', creativework.is_deleted
114-
, 'same_as', creativework.same_as_id
115113
, 'language', creativework.language
116114
, 'date_created', creativework.date_created
117115
, 'date_modified', creativework.date_modified

share/change.py

Lines changed: 30 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
import logging
44
import pendulum
55
import datetime
6-
from functools import reduce
76

87
from django.apps import apps
98
from django.core.exceptions import FieldDoesNotExist
109

1110
from share.disambiguation import GraphDisambiguator
12-
from share.util import TopologicalSorter
11+
from share.util import TopographicalSorter
1312
from share.util import IDObfuscator
1413

1514

@@ -85,7 +84,7 @@ def __init__(self, data, namespace=None):
8584
self.nodes.append(self._lookup[id, type])
8685

8786
for k, v in tuple(blob.items()):
88-
if isinstance(v, dict) and k not in {'extra', 'same_as'} and not k.startswith('@'):
87+
if isinstance(v, dict) and k != 'extra' and not k.startswith('@'):
8988
related = (v.pop('@id'), v.pop('@type').lower())
9089
hints[(id, type), related] = k
9190
relations.add(((id, type), related))
@@ -105,17 +104,17 @@ def __init__(self, data, namespace=None):
105104
self.relations[self._lookup[subject]].add(edge)
106105
self.relations[self._lookup[related]].add(edge)
107106

108-
self.sort_nodes()
107+
self.nodes = TopographicalSorter(self.nodes, dependencies=lambda n: tuple(e.related for e in n.related(backward=False))).sorted()
109108

110109
def prune(self):
111110
gd = GraphDisambiguator()
112111
gd.prune(self)
113-
self.sort_nodes()
112+
self.nodes = TopographicalSorter(self.nodes, dependencies=lambda n: tuple(e.related for e in n.related(backward=False))).sorted()
114113

115114
def disambiguate(self):
116115
gd = GraphDisambiguator()
117116
gd.find_instances(self)
118-
self.sort_nodes()
117+
self.nodes = TopographicalSorter(self.nodes, dependencies=lambda n: tuple(e.related for e in n.related(backward=False))).sorted()
119118

120119
def normalize(self):
121120
# Freeze nodes to avoid oddities with inserting and removing nodes
@@ -141,7 +140,7 @@ def process(self, normalize=True, prune=True, disambiguate=True):
141140
if disambiguate:
142141
gd.find_instances(self)
143142

144-
self.sort_nodes()
143+
self.nodes = TopographicalSorter(self.nodes, dependencies=lambda n: tuple(e.related for e in n.related(backward=False))).sorted()
145144

146145
def get(self, id, type):
147146
return self._lookup[(id, type)]
@@ -203,26 +202,19 @@ def serialize(self):
203202
for n in sorted(self.nodes, key=lambda x: x.type + str(x.id))
204203
]
205204

206-
def sort_nodes(self):
207-
# Put merge nodes first, then everything else sorted topologically
208-
nonmerges, merges = reduce(lambda lists, n: lists[n.is_merge].append(n) or lists, self.nodes, ([], []))
209-
self.nodes = merges + TopologicalSorter(nonmerges, dependencies=lambda n: tuple(e.related for e in n.related(backward=False))).sorted()
210-
211205

212206
class ChangeNode:
213207

214208
@property
215209
def id(self):
216-
instance = self.get_instance()
217-
return IDObfuscator.encode(instance) if instance else self._id
210+
return (self.instance and IDObfuscator.encode(self.instance)) or self._id
218211

219212
@property
220213
def type(self):
221214
model = apps.get_app_config('share').get_model(self._type)
222-
instance = self.get_instance()
223-
if not instance or len(model.mro()) >= len(type(instance).mro()):
215+
if not self.instance or len(model.mro()) >= len(type(self.instance).mro()):
224216
return self._type
225-
return instance._meta.model_name.lower()
217+
return self.instance._meta.model_name.lower()
226218

227219
@property
228220
def ref(self):
@@ -231,43 +223,41 @@ def ref(self):
231223
@property
232224
def model(self):
233225
model = apps.get_model('share', self._type)
234-
instance = self.get_instance()
235-
if not instance or len(model.mro()) >= len(type(instance).mro()):
226+
if not self.instance or len(model.mro()) >= len(type(self.instance).mro()):
236227
return model
237228
# Special case to allow creators to be downgraded to contributors
238229
# This allows OSF users to mark project contributors as bibiliographic or non-bibiliographic
239230
# and have that be reflected in SHARE
240231
if issubclass(model, apps.get_model('share', 'contributor')):
241232
return model
242-
return type(instance)
233+
return type(self.instance)
243234

244235
@property
245236
def is_merge(self):
246-
return 'same_as' in self.attrs
237+
return False # TODO
247238

248239
@property
249240
def is_blank(self):
250241
return self.id.startswith('_:')
251242

252243
@property
253244
def is_skippable(self):
254-
return self.instances and not self.change
245+
return self.is_merge or (self.instance and not self.change)
255246

256247
@property
257248
def change(self):
258249
changes, relations = {}, {}
259-
instance = self.get_instance()
260250

261251
extra = copy.deepcopy(self.extra)
262252
if self.namespace:
263-
if self.namespace and getattr(instance, 'extra', None):
253+
if self.namespace and getattr(self.instance, 'extra', None):
264254
# NOTE extra changes are only diffed at the top level
265-
instance.extra.data.setdefault(self.namespace, {})
255+
self.instance.extra.data.setdefault(self.namespace, {})
266256
changes['extra'] = {
267257
k: v
268258
for k, v in extra.items()
269-
if k not in instance.extra.data[self.namespace]
270-
or instance.extra.data[self.namespace][k] != v
259+
if k not in self.instance.extra.data[self.namespace]
260+
or self.instance.extra.data[self.namespace][k] != v
271261
}
272262
else:
273263
changes['extra'] = extra
@@ -283,21 +273,21 @@ def change(self):
283273
if self.is_blank:
284274
return {**changes, **self.attrs, **relations}
285275

286-
if instance and type(instance) is not self.model:
276+
if self.instance and type(self.instance) is not self.model:
287277
changes['type'] = self.model._meta.label_lower
288278

289279
# Hacky fix for SHARE-604
290280
# If the given date_updated is older than the current one, don't accept any changes that would overwrite newer changes
291281
ignore_attrs = set()
292-
if issubclass(self.model, apps.get_model('share', 'creativework')) and 'date_updated' in self.attrs and instance.date_updated:
282+
if issubclass(self.model, apps.get_model('share', 'creativework')) and 'date_updated' in self.attrs and self.instance.date_updated:
293283
date_updated = pendulum.parse(self.attrs['date_updated'])
294-
if date_updated < instance.date_updated:
295-
logger.warning('%s appears to be from the past, change date_updated (%s) is older than the current (%s). Ignoring conflicting changes.', self, self.attrs['date_updated'], instance.date_updated)
284+
if date_updated < self.instance.date_updated:
285+
logger.warning('%s appears to be from the past, change date_updated (%s) is older than the current (%s). Ignoring conflicting changes.', self, self.attrs['date_updated'], self.instance.date_updated)
296286
# Just in case
297-
ignore_attrs.update(instance.change.change.keys())
287+
ignore_attrs.update(self.instance.change.change.keys())
298288

299289
# Go back until we find a change that is older than us
300-
for version in instance.versions.select_related('change').all():
290+
for version in self.instance.versions.select_related('change').all():
301291
if not version.date_updated or date_updated > version.date_updated:
302292
break
303293
ignore_attrs.update(version.change.change.keys())
@@ -307,7 +297,7 @@ def change(self):
307297
if k in ignore_attrs:
308298
logger.debug('Ignoring potentially conflicting change to "%s"', k)
309299
continue
310-
old_value = getattr(instance, k)
300+
old_value = getattr(self.instance, k)
311301
if isinstance(old_value, datetime.datetime):
312302
v = pendulum.parse(v)
313303
if v != old_value:
@@ -320,25 +310,16 @@ def __init__(self, graph, id, type, attrs, namespace=None):
320310
self.graph = graph
321311
self._id = id
322312
self._type = type.lower()
323-
self.instances = set()
313+
self.instance = None
324314
self.attrs = attrs
325315
self.extra = attrs.pop('extra', {})
326316
self.context = attrs.pop('@context', {})
327317
self.namespace = namespace
328318

329319
if not self.is_blank:
330-
instance = IDObfuscator.load(self.id, None)
331-
if not instance or instance._meta.concrete_model is not self.model._meta.concrete_model:
320+
self.instance = IDObfuscator.load(self.id, None)
321+
if not self.instance or self.instance._meta.concrete_model is not self.model._meta.concrete_model:
332322
raise UnresolvableReference((self.id, self.type))
333-
self.instances.add(instance)
334-
335-
def get_instance(self):
336-
try:
337-
return max(self.instances, key=lambda i: i.date_modified, default=None)
338-
except AttributeError:
339-
if len(self.instances) == 1:
340-
return next(i for i in self.instances)
341-
raise
342323

343324
def related(self, name=None, forward=True, backward=True):
344325
edges = tuple(
@@ -365,13 +346,12 @@ def resolve_attrs(self):
365346
name = edge.name if edge.subject == self else edge.remote_name
366347
node = edge.related if edge.subject == self else edge.subject
367348
many = edge.field.one_to_many if edge.subject == self else edge.field.remote_field.one_to_many
368-
instance = node.get_instance()
369-
if not instance:
349+
if not node.instance:
370350
continue
371351
if many:
372-
relations.setdefault(name, []).append(instance.pk)
352+
relations.setdefault(name, []).append(node.instance.pk)
373353
else:
374-
relations[name] = instance.pk
354+
relations[name] = node.instance.pk
375355

376356
return {**self.attrs, **relations}
377357

0 commit comments

Comments
 (0)