@@ -173,12 +173,10 @@ def validate_instances(
173173 # The model is not in the fixture, and hasn't been checked already, so use the primary and
174174 # natural keys for already saved instances. Save the keys as a dict mapping to the
175175 # instance, so that we can resolve natural keys later when validating model.clean()
176- remote_keys = {
177- related_instance .pk : related_instance
178- for related_instance in field .related_model .objects .all ()
179- }
180- if hasattr (field .related_model , "natural_key" ):
181- for related_instance in field .related_model .objects .all ():
176+ remote_keys = {}
177+ for related_instance in field .related_model .objects .all ():
178+ remote_keys [related_instance .pk ] = related_instance
179+ if hasattr (field .related_model , "natural_key" ):
182180 remote_keys [related_instance .natural_key ()] = related_instance
183181 valid_keys [field .related_model .__name__ ] = remote_keys
184182
@@ -214,47 +212,63 @@ def validate_instances(
214212 # Replace empty strings with None for optional and numeric fields
215213 if value == "" and (field .null or field .get_internal_type () not in ["CharField" , "TextField" ]):
216214 value = None
217- # Handle foreign key fields, resolving natural keys to model instances
215+ # Handle foreign key fields
218216 if isinstance (field , models .ForeignKey ):
219- if isinstance (value , list ):
220- value = tuple (value )
221- value = valid_keys [field .related_model .__name__ ].get (value )
217+ # Natural keys need to be converted to tuples for looking up in valid_keys
218+ lookup_value = tuple (value ) if isinstance (value , list ) else value
219+ related_instance = valid_keys [field .related_model .__name__ ].get (lookup_value )
220+ if related_instance :
221+ # Assign the resolved model instance to the relationship attribute (e.g. `product`),
222+ setattr (model_instance , field .name , related_instance )
223+ # Also set the attname (e.g. `product_id`) to the primary key
224+ if related_instance .pk :
225+ setattr (model_instance , field .get_attname (), related_instance .pk )
222226 else :
223227 # Use the Django model to validate the fields, so we can apply already defined model
224228 # validations and return informative error messages.
225229 try :
226- field .clean (value , model_instance )
230+ value = field .clean (value , model_instance )
231+ setattr (model_instance , column , value )
232+ except ValidationError as e :
233+ # Record validation error messages in a similar format to field.clean handling
234+ msgs = []
235+ if hasattr (e , "message_dict" ):
236+ for v in e .message_dict .values ():
237+ msgs .extend (v if isinstance (v , list ) else [v ])
238+ elif hasattr (e , "messages" ):
239+ msgs = e .messages
240+ else :
241+ msgs = [str (e )]
242+ for msg in msgs :
243+ error = f"Invalid { field .name } value { value } : { msg } for { record_reference } ."
244+ model_errors .append (error )
227245 except Exception as e :
228- error = (
229- f'Invalid { field .name } value { value } : "{ ", " .join (e .error_list [0 ].messages )} " '
230- f"for { record_reference } ."
231- )
232- model_errors .append (error )
233- setattr (model_instance , column , value )
246+ # Record unexpected errors from clean() so the author can investigate
247+ model_errors .append (f"Invalid { field .name } value { value } : { e } for { record_reference } ." )
234248 # We don't need to validate LivelihoodActivity, because we will validate the subclass instances anyway.
235249 if model != LivelihoodActivity :
236250 try :
237251 model_instance .clean ()
238- except Exception as e :
252+ except ObjectDoesNotExist :
239253 # Ignore missing related object errors since the related object may be
240- # loaded by the same fixture. We check class name to handle a possible
241- # custom RelatedObjectNotFound exception defined elsewhere.
242- if isinstance (e , ObjectDoesNotExist ) or e .__class__ .__name__ == "RelatedObjectNotFound" :
243- pass
244- elif isinstance (e , ValidationError ):
245- # Record validation error messages in a similar format to field.clean handling
246- msgs = []
247- if hasattr (e , "message_dict" ):
248- for k , v in e .message_dict .items ():
249- msgs .extend (v if isinstance (v , list ) else [v ])
250- else :
251- msgs = e .messages if hasattr (e , "messages" ) else [str (e )]
252- for msg in msgs :
253- error = f"{ msg } for { record_reference } ."
254- model_errors .append (error )
254+ # loaded by the same fixture.
255+ pass
256+ except ValidationError as e :
257+ # Record validation error messages in a similar format to field.clean handling
258+ msgs = []
259+ if hasattr (e , "message_dict" ):
260+ for v in e .message_dict .values ():
261+ msgs .extend (v if isinstance (v , list ) else [v ])
262+ elif hasattr (e , "messages" ):
263+ msgs = e .messages
255264 else :
256- # Record unexpected errors from clean() so the author can investigate
257- model_errors .append (f"Error during clean(): { e } for { record_reference } ." )
265+ msgs = [str (e )]
266+ for msg in msgs :
267+ error = f"{ msg } for { record_reference } ."
268+ model_errors .append (error )
269+ except Exception as e :
270+ # Record unexpected errors from clean() so the author can investigate
271+ model_errors .append (f"Error during clean(): { e } for { record_reference } ." )
258272 except Exception as e :
259273 model_errors .append (f"Error creating { model_name } instance: { e } for { record_reference } ." )
260274 # Ignore errors creating the instance for clean()
0 commit comments