Skip to content

Commit c2191f8

Browse files
committed
add list_serializer_class for NodeContributorsCreateSerializer; minor optimization
1 parent 9b15446 commit c2191f8

File tree

4 files changed

+143
-16
lines changed

4 files changed

+143
-16
lines changed

api/base/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,11 @@ class JSONAPISerializer(BaseAPISerializer):
14561456
@classmethod
14571457
def many_init(cls, *args, **kwargs):
14581458
kwargs['child'] = cls(*args, **kwargs)
1459+
# Use DRF list_serializer_class if it exists, otherwise use default JSONAPIListSerializer
1460+
meta = getattr(cls, 'Meta', None)
1461+
list_cls = getattr(meta, 'list_serializer_class', None)
1462+
if list_cls:
1463+
return list_cls(*args, **kwargs)
14591464
return JSONAPIListSerializer(*args, **kwargs)
14601465

14611466
def invalid_embeds(self, fields, embeds):

api/nodes/serializers.py

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.db import connection
1+
from django.db import connection, transaction
22
from packaging.version import Version
33

44
from api.base.exceptions import (
@@ -7,7 +7,7 @@
77
)
88
from api.base.serializers import (
99
VersionedDateTimeField, HideIfRegistration, IDField,
10-
JSONAPISerializer, LinksField,
10+
JSONAPISerializer, JSONAPIListSerializer, LinksField,
1111
NodeFileHyperLinkField, RelationshipField,
1212
ShowIfVersion, TargetTypeField, TypeField,
1313
WaterbutlerLink, BaseAPISerializer,
@@ -38,7 +38,7 @@
3838
Comment, DraftRegistration, ExternalAccount,
3939
RegistrationSchema, AbstractNode, PrivateLink, Preprint,
4040
RegistrationProvider, NodeLicense, DraftNode,
41-
Registration, Node,
41+
Registration, Node, OSFUser,
4242
)
4343
from website.project import new_private_link
4444
from website.project.model import NodeUpdateError
@@ -1219,6 +1219,113 @@ def get_unregistered_contributor(self, obj):
12191219
return unclaimed_records.get('name', None)
12201220

12211221

1222+
class NodeContributorsBulkCreateListSerializer(JSONAPIListSerializer):
1223+
1224+
def create(self, validated_data):
1225+
request = self.context['request']
1226+
node = self.context['resource']
1227+
auth = Auth(request.user)
1228+
1229+
with transaction.atomic():
1230+
results = []
1231+
registered_items = []
1232+
unregistered_items = []
1233+
1234+
for item in validated_data:
1235+
if item.get('_id') and not item.get('user', {}).get('email'):
1236+
registered_items.append(item)
1237+
else:
1238+
unregistered_items.append(item)
1239+
1240+
def _perm(item):
1241+
return osf_permissions.get_contributor_proposed_permissions(item)
1242+
1243+
if registered_items:
1244+
# Load users once and build a mapping to reuse
1245+
user_map = {}
1246+
for item in registered_items:
1247+
user_id = item.get('_id')
1248+
if user_id and user_id not in user_map:
1249+
user = OSFUser.load(user_id)
1250+
if not user:
1251+
raise exceptions.NotFound(detail=f"User {user_id} not found")
1252+
user_map[user_id] = user
1253+
1254+
contrib_dicts = []
1255+
for item in registered_items:
1256+
user = user_map[item.get('_id')]
1257+
contrib_dicts.append({
1258+
'user': user,
1259+
'permissions': _perm(item),
1260+
'visible': item.get('bibliographic'),
1261+
})
1262+
contribs = node.add_contributors(contrib_dicts, auth=auth, log=True, save=True)
1263+
results.extend(contribs)
1264+
1265+
child_to_items = {}
1266+
for item in registered_items:
1267+
child_nodes = item.get('child_nodes')
1268+
if child_nodes:
1269+
for child_id in child_nodes:
1270+
child_to_items.setdefault(child_id, []).append(item)
1271+
1272+
for child_id, items in child_to_items.items():
1273+
child = AbstractNode.load(child_id)
1274+
if not child:
1275+
continue
1276+
child_contribs = []
1277+
for item in items:
1278+
child_contribs.append({
1279+
'user': user_map[item.get('_id')],
1280+
'permissions': _perm(item),
1281+
'visible': item.get('bibliographic'),
1282+
})
1283+
child.add_contributors(child_contribs, auth=auth, log=True, save=True)
1284+
1285+
for item in unregistered_items:
1286+
id = item.get('_id')
1287+
email = item.get('user', {}).get('email', None)
1288+
full_name = item.get('full_name')
1289+
bibliographic = item.get('bibliographic')
1290+
index = item.get('_order') if '_order' in item else None
1291+
send_email = request.GET.get('send_email') or self.context['default_email']
1292+
permissions = _perm(item)
1293+
1294+
try:
1295+
contributor_obj = node.add_contributor_registered_or_not(
1296+
auth=auth,
1297+
user_id=id,
1298+
email=email,
1299+
full_name=full_name,
1300+
send_email=send_email,
1301+
permissions=permissions,
1302+
bibliographic=bibliographic,
1303+
index=index,
1304+
save=True,
1305+
)
1306+
results.append(contributor_obj)
1307+
child_nodes = item.get('child_nodes')
1308+
if child_nodes:
1309+
for child in AbstractNode.objects.filter(guids___id__in=child_nodes):
1310+
child.add_contributor_registered_or_not(
1311+
auth=auth,
1312+
user_id=id,
1313+
email=email,
1314+
full_name=full_name,
1315+
send_email=send_email,
1316+
permissions=permissions,
1317+
bibliographic=bibliographic,
1318+
index=index,
1319+
save=True,
1320+
)
1321+
except ValidationError as e:
1322+
raise exceptions.ValidationError(detail=e.messages[0])
1323+
except ValueError as e:
1324+
raise exceptions.NotFound(detail=e.args[0])
1325+
1326+
return results
1327+
1328+
12221329
class NodeContributorsCreateSerializer(NodeContributorsSerializer):
12231330
"""
12241331
Overrides NodeContributorsSerializer to add email, full_name, send_email, and non-required index and users field.
@@ -1239,8 +1346,8 @@ class NodeContributorsCreateSerializer(NodeContributorsSerializer):
12391346

12401347
email_preferences = ['default', 'false']
12411348

1242-
def get_proposed_permissions(self, validated_data):
1243-
return validated_data.get('permission') or osf_permissions.DEFAULT_CONTRIBUTOR_PERMISSIONS
1349+
class Meta(NodeContributorsSerializer.Meta):
1350+
list_serializer_class = NodeContributorsBulkCreateListSerializer
12441351

12451352
def validate_data(self, node, user_id=None, full_name=None, email=None, index=None, child_nodes=None):
12461353
if not user_id and not full_name:
@@ -1264,7 +1371,7 @@ def create(self, validated_data):
12641371
bibliographic = validated_data.get('bibliographic')
12651372
send_email = self.context['request'].GET.get('send_email') or self.context['default_email']
12661373
child_nodes = validated_data.get('child_nodes')
1267-
permissions = self.get_proposed_permissions(validated_data)
1374+
permissions = osf_permissions.get_contributor_proposed_permissions(validated_data)
12681375

12691376
self.validate_data(node, user_id=id, full_name=full_name, email=email, index=index, child_nodes=child_nodes)
12701377

osf/models/mixins.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,7 +1432,7 @@ def add_contributor(self, contributor, permissions=None, visible=True,
14321432
# Add default contributor permissions
14331433
permissions = permissions or self.DEFAULT_CONTRIBUTOR_PERMISSIONS
14341434

1435-
self.add_permission(contrib_to_add, permissions, save=True)
1435+
self.add_permission(contrib_to_add, permissions, save=False)
14361436
if make_curator:
14371437
contributor_obj.is_curator = True
14381438
contributor_obj.save()
@@ -1482,11 +1482,13 @@ def add_contributors(self, contributors, auth=None, log=True, save=False):
14821482
:param log: Add log to self
14831483
:param save: Save after adding contributor
14841484
"""
1485+
users = []
14851486
for contrib in contributors:
14861487
self.add_contributor(
14871488
contributor=contrib['user'], permissions=contrib['permissions'],
14881489
visible=contrib['visible'], auth=auth, log=False, save=False,
14891490
)
1491+
users.append(contrib['user'])
14901492
if log and contributors:
14911493
params = self.log_params
14921494
params['contributors'] = [
@@ -1502,8 +1504,10 @@ def add_contributors(self, contributors, auth=None, log=True, save=False):
15021504
if save:
15031505
self.save()
15041506

1507+
return self.contributor_set.filter(user__in=users)
1508+
15051509
def add_unregistered_contributor(self, fullname, email, auth, send_email=None,
1506-
visible=True, permissions=None, save=False, existing_user=None):
1510+
visible=True, permissions=None, save=True, existing_user=None):
15071511
"""Add a non-registered contributor to the project.
15081512
15091513
:param str fullname: The full name of the person.
@@ -1554,12 +1558,13 @@ def add_unregistered_contributor(self, fullname, email, auth, send_email=None,
15541558
visible=visible, send_email=send_email, log=True, save=False
15551559
)
15561560
self._add_related_source_tags(contributor)
1557-
self.save()
1561+
if save:
1562+
self.save()
15581563
return contributor
15591564

15601565
def add_contributor_registered_or_not(self, auth, user_id=None,
15611566
full_name=None, email=None, send_email=None,
1562-
permissions=None, bibliographic=True, index=None, save=False):
1567+
permissions=None, bibliographic=True, index=None, save=True):
15631568
OSFUser = apps.get_model('osf.OSFUser')
15641569
send_email = send_email or self.contributor_email_template
15651570

@@ -1573,7 +1578,7 @@ def add_contributor_registered_or_not(self, auth, user_id=None,
15731578

15741579
if contributor.is_registered:
15751580
contributor = self.add_contributor(contributor=contributor, auth=auth, visible=bibliographic,
1576-
permissions=permissions, send_email=send_email, save=True)
1581+
permissions=permissions, send_email=send_email, save=save)
15771582
else:
15781583
if not full_name:
15791584
raise ValueError(
@@ -1583,7 +1588,7 @@ def add_contributor_registered_or_not(self, auth, user_id=None,
15831588
contributor = self.add_unregistered_contributor(
15841589
fullname=full_name, email=contributor.username, auth=auth,
15851590
send_email=send_email, permissions=permissions,
1586-
visible=bibliographic, existing_user=contributor, save=True
1591+
visible=bibliographic, existing_user=contributor, save=save
15871592
)
15881593

15891594
else:
@@ -1592,20 +1597,26 @@ def add_contributor_registered_or_not(self, auth, user_id=None,
15921597
raise ValidationValueError(f'{contributor.fullname} is already a contributor.')
15931598

15941599
if contributor and contributor.is_registered:
1595-
self.add_contributor(contributor=contributor, auth=auth, visible=bibliographic,
1596-
send_email=send_email, permissions=permissions, save=True)
1600+
self.add_contributor(
1601+
contributor=contributor,
1602+
auth=auth,
1603+
visible=bibliographic,
1604+
send_email=send_email,
1605+
permissions=permissions,
1606+
save=save,
1607+
)
15971608
else:
15981609
contributor = self.add_unregistered_contributor(
15991610
fullname=full_name, email=email, auth=auth,
16001611
send_email=send_email, permissions=permissions,
1601-
visible=bibliographic, save=True
1612+
visible=bibliographic, save=save
16021613
)
16031614

16041615
auth.user.email_last_sent = timezone.now()
16051616
auth.user.save()
16061617

16071618
if index is not None:
1608-
self.move_contributor(contributor=contributor, index=index, auth=auth, save=True)
1619+
self.move_contributor(contributor=contributor, index=index, auth=auth, save=save)
16091620

16101621
contributor_obj = self.contributor_set.get(user=contributor)
16111622
return contributor_obj

osf/utils/permissions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,7 @@ def check_private_key_for_anonymized_link(private_key):
7272
except PrivateLink.DoesNotExist:
7373
return False
7474
return link.anonymous
75+
76+
77+
def get_contributor_proposed_permissions(validated_data):
78+
return validated_data.get('permission') or DEFAULT_CONTRIBUTOR_PERMISSIONS

0 commit comments

Comments
 (0)