1- from django .db import connection
1+ from django .db import connection , transaction
22from packaging .version import Version
33
44from api .base .exceptions import (
77)
88from 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 ,
3838 Comment , DraftRegistration , ExternalAccount ,
3939 RegistrationSchema , AbstractNode , PrivateLink , Preprint ,
4040 RegistrationProvider , NodeLicense , DraftNode ,
41- Registration , Node ,
41+ Registration , Node , OSFUser ,
4242)
4343from website .project import new_private_link
4444from 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+
12221329class 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
0 commit comments