@@ -202,6 +202,152 @@ class Meta(InclusionPatternBase.Meta):
202202 verbose_name_plural = "Delta Include Patterns"
203203
204204
205+ class FieldModifyingPattern (BaseMatchPattern ):
206+ """
207+ Abstract base class for patterns that modify a single field on matching URLs.
208+ Examples: DeltaDivisionPattern, DeltaDocumentTypePattern
209+ """
210+
211+ class Meta (BaseMatchPattern .Meta ):
212+ abstract = True
213+
214+ def get_field_to_modify (self ) -> str :
215+ """Return the name of the field this pattern modifies. Must be implemented by subclasses."""
216+ raise NotImplementedError
217+
218+ def get_new_value (self ) -> Any :
219+ """Return the new value for the field. Must be implemented by subclasses."""
220+ raise NotImplementedError
221+
222+ def apply (self ) -> None :
223+ """
224+ Apply field modification to matching URLs:
225+ 1. Find new Curated URLs that match but weren't previously affected
226+ 2. Create Delta URLs only for Curated URLs where the field value would change
227+ 3. Update the pattern's list of affected URLs
228+ 4. Set the field value on all matching Delta URLs
229+ """
230+ DeltaUrl = apps .get_model ("sde_collections" , "DeltaUrl" )
231+
232+ field = self .get_field_to_modify ()
233+ new_value = self .get_new_value ()
234+
235+ # Get newly matching Curated URLs
236+ matching_curated_urls = self .get_matching_curated_urls ()
237+ previously_unaffected_curated = matching_curated_urls .exclude (
238+ id__in = self .curated_urls .values_list ("id" , flat = True )
239+ )
240+
241+ # Create DeltaUrls only where field value would change
242+ for curated_url in previously_unaffected_curated :
243+ if (
244+ getattr (curated_url , field ) == new_value
245+ or DeltaUrl .objects .filter (url = curated_url .url , collection = self .collection ).exists ()
246+ ):
247+ continue
248+
249+ fields = {
250+ f .name : getattr (curated_url , f .name )
251+ for f in curated_url ._meta .fields
252+ if f .name not in ["id" , "collection" ]
253+ }
254+ fields [field ] = new_value
255+ fields ["to_delete" ] = False
256+ fields ["collection" ] = self .collection
257+
258+ DeltaUrl .objects .create (** fields )
259+
260+ # Update all matching DeltaUrls with the new field value
261+ self .get_matching_delta_urls ().update (** {field : new_value })
262+ self .update_affected_delta_urls_list ()
263+
264+ def unapply (self ) -> None :
265+ """
266+ Remove field modifications:
267+ 1. Create Delta URLs for affected Curated URLs to explicitly set NULL
268+ 2. Remove field value from affected Delta URLs only if no other patterns affect them
269+ 3. Clean up Delta URLs that become identical to their Curated URL
270+ """
271+
272+ DeltaUrl = apps .get_model ("sde_collections" , "DeltaUrl" )
273+ CuratedUrl = apps .get_model ("sde_collections" , "CuratedUrl" )
274+
275+ field = self .get_field_to_modify ()
276+
277+ # Get all affected URLs
278+ affected_deltas = self .delta_urls .all ()
279+ affected_curated = self .curated_urls .all ()
280+
281+ # Process each affected delta URL
282+ for delta in affected_deltas :
283+ curated = CuratedUrl .objects .filter (collection = self .collection , url = delta .url ).first ()
284+
285+ if not curated :
286+ # Scenario 1: Delta only - new URL
287+ setattr (delta , field , None )
288+ delta .save ()
289+ else :
290+ # Scenario 2: Both exist
291+ setattr (delta , field , getattr (curated , field ))
292+ delta .save ()
293+
294+ # Check if delta is now redundant
295+ fields_match = all (
296+ getattr (delta , f .name ) == getattr (curated , f .name )
297+ for f in delta ._meta .fields
298+ if f .name not in ["id" , "to_delete" ]
299+ )
300+ if fields_match :
301+ delta .delete ()
302+
303+ # Handle curated URLs that don't have deltas
304+ for curated in affected_curated :
305+ if not DeltaUrl .objects .filter (url = curated .url ).exists ():
306+ # Scenario 3: Curated only
307+ # Copy all fields from curated except the one we're nulling
308+ fields = {
309+ f .name : getattr (curated , f .name ) for f in curated ._meta .fields if f .name not in ["id" , "collection" ]
310+ }
311+ fields [field ] = None # Set the pattern's field to None
312+ delta = DeltaUrl .objects .create (collection = self .collection , ** fields )
313+
314+ # Clear pattern relationships
315+ self .delta_urls .clear ()
316+ self .curated_urls .clear ()
317+
318+
319+ class DeltaDocumentTypePattern (FieldModifyingPattern ):
320+ """Pattern for setting document types."""
321+
322+ document_type = models .IntegerField (choices = DocumentTypes .choices )
323+
324+ def get_field_to_modify (self ) -> str :
325+ return "document_type"
326+
327+ def get_new_value (self ) -> Any :
328+ return self .document_type
329+
330+ class Meta (FieldModifyingPattern .Meta ):
331+ verbose_name = "Delta Document Type Pattern"
332+ verbose_name_plural = "Delta Document Type Patterns"
333+
334+
335+ class DeltaDivisionPattern (FieldModifyingPattern ):
336+ """Pattern for setting divisions."""
337+
338+ division = models .IntegerField (choices = Divisions .choices )
339+
340+ def get_field_to_modify (self ) -> str :
341+ return "division"
342+
343+ def get_new_value (self ) -> Any :
344+ return self .division
345+
346+ class Meta (FieldModifyingPattern .Meta ):
347+ verbose_name = "Delta Division Pattern"
348+ verbose_name_plural = "Delta Division Patterns"
349+
350+
205351def validate_title_pattern (title_pattern_string : str ) -> None :
206352 """Validate title pattern format."""
207353 parsed_title = parse_title (title_pattern_string )
@@ -373,147 +519,30 @@ class Meta(BaseMatchPattern.Meta):
373519 verbose_name_plural = "Delta Title Patterns"
374520
375521
376- class FieldModifyingPattern (BaseMatchPattern ):
377- """
378- Abstract base class for patterns that modify a single field on matching URLs.
379- Examples: DeltaDivisionPattern, DeltaDocumentTypePattern
380- """
381-
382- class Meta (BaseMatchPattern .Meta ):
383- abstract = True
384-
385- def get_field_to_modify (self ) -> str :
386- """Return the name of the field this pattern modifies. Must be implemented by subclasses."""
387- raise NotImplementedError
388-
389- def get_new_value (self ) -> Any :
390- """Return the new value for the field. Must be implemented by subclasses."""
391- raise NotImplementedError
392-
393- def apply (self ) -> None :
394- """
395- Apply field modification to matching URLs:
396- 1. Find new Curated URLs that match but weren't previously affected
397- 2. Create Delta URLs only for Curated URLs where the field value would change
398- 3. Update the pattern's list of affected URLs
399- 4. Set the field value on all matching Delta URLs
400- """
401- DeltaUrl = apps .get_model ("sde_collections" , "DeltaUrl" )
402-
403- field = self .get_field_to_modify ()
404- new_value = self .get_new_value ()
405-
406- # Get newly matching Curated URLs
407- matching_curated_urls = self .get_matching_curated_urls ()
408- previously_unaffected_curated = matching_curated_urls .exclude (
409- id__in = self .curated_urls .values_list ("id" , flat = True )
410- )
411-
412- # Create DeltaUrls only where field value would change
413- for curated_url in previously_unaffected_curated :
414- if (
415- getattr (curated_url , field ) == new_value
416- or DeltaUrl .objects .filter (url = curated_url .url , collection = self .collection ).exists ()
417- ):
418- continue
419-
420- fields = {
421- f .name : getattr (curated_url , f .name )
422- for f in curated_url ._meta .fields
423- if f .name not in ["id" , "collection" ]
424- }
425- fields [field ] = new_value
426- fields ["to_delete" ] = False
427- fields ["collection" ] = self .collection
428-
429- DeltaUrl .objects .create (** fields )
430-
431- # Update all matching DeltaUrls with the new field value
432- self .get_matching_delta_urls ().update (** {field : new_value })
433- self .update_affected_delta_urls_list ()
434-
435- def unapply (self ) -> None :
436- """
437- Remove field modifications:
438- 1. Create Delta URLs for affected Curated URLs to explicitly set NULL
439- 2. Remove field value from affected Delta URLs only if no other patterns affect them
440- 3. Clean up Delta URLs that become identical to their Curated URL
441- """
522+ class DeltaResolvedTitleBase (models .Model ):
523+ # TODO: need to understand this logic and whether we need to have these match to CuratedUrls as well
442524
443- DeltaUrl = apps .get_model ("sde_collections" , "DeltaUrl" )
444- CuratedUrl = apps .get_model ("sde_collections" , "CuratedUrl" )
525+ title_pattern = models .ForeignKey (DeltaTitlePattern , on_delete = models .CASCADE )
526+ delta_url = models .OneToOneField ("sde_collections.DeltaUrl" , on_delete = models .CASCADE )
527+ created_at = models .DateTimeField (auto_now_add = True )
445528
446- field = self .get_field_to_modify ()
447-
448- # Get all affected URLs
449- affected_deltas = self .delta_urls .all ()
450- affected_curated = self .curated_urls .all ()
451-
452- # Process each affected delta URL
453- for delta in affected_deltas :
454- curated = CuratedUrl .objects .filter (collection = self .collection , url = delta .url ).first ()
455-
456- if not curated :
457- # Scenario 1: Delta only - new URL
458- setattr (delta , field , None )
459- delta .save ()
460- else :
461- # Scenario 2: Both exist
462- setattr (delta , field , getattr (curated , field ))
463- delta .save ()
464-
465- # Check if delta is now redundant
466- fields_match = all (
467- getattr (delta , f .name ) == getattr (curated , f .name )
468- for f in delta ._meta .fields
469- if f .name not in ["id" , "to_delete" ]
470- )
471- if fields_match :
472- delta .delete ()
473-
474- # Handle curated URLs that don't have deltas
475- for curated in affected_curated :
476- if not DeltaUrl .objects .filter (url = curated .url ).exists ():
477- # Scenario 3: Curated only
478- # Copy all fields from curated except the one we're nulling
479- fields = {
480- f .name : getattr (curated , f .name ) for f in curated ._meta .fields if f .name not in ["id" , "collection" ]
481- }
482- fields [field ] = None # Set the pattern's field to None
483- delta = DeltaUrl .objects .create (collection = self .collection , ** fields )
484-
485- # Clear pattern relationships
486- self .delta_urls .clear ()
487- self .curated_urls .clear ()
488-
489-
490- class DeltaDocumentTypePattern (FieldModifyingPattern ):
491- """Pattern for setting document types."""
492-
493- document_type = models .IntegerField (choices = DocumentTypes .choices )
494-
495- def get_field_to_modify (self ) -> str :
496- return "document_type"
497-
498- def get_new_value (self ) -> Any :
499- return self .document_type
500-
501- class Meta (FieldModifyingPattern .Meta ):
502- verbose_name = "Delta Document Type Pattern"
503- verbose_name_plural = "Delta Document Type Patterns"
529+ class Meta :
530+ abstract = True
504531
505532
506- class DeltaDivisionPattern ( FieldModifyingPattern ):
507- """Pattern for setting divisions."""
533+ class DeltaResolvedTitle ( DeltaResolvedTitleBase ):
534+ resolved_title = models . CharField ( blank = True , default = "" )
508535
509- division = models .IntegerField (choices = Divisions .choices )
536+ class Meta :
537+ verbose_name = "Resolved Title"
538+ verbose_name_plural = "Resolved Titles"
510539
511- def get_field_to_modify (self ) -> str :
512- return "division"
540+ def save (self , * args , ** kwargs ):
541+ # Finds the linked delta URL and deletes DeltaResolvedTitleError objects linked to it
542+ DeltaResolvedTitleError .objects .filter (delta_url = self .delta_url ).delete ()
543+ super ().save (* args , ** kwargs )
513544
514- def get_new_value (self ) -> Any :
515- return self .division
516545
517- class Meta ( FieldModifyingPattern . Meta ):
518- verbose_name = "Delta Division Pattern"
519- verbose_name_plural = "Delta Division Patterns"
546+ class DeltaResolvedTitleError ( DeltaResolvedTitleBase ):
547+ error_string = models . TextField ( null = False , blank = False )
548+ http_status_code = models . IntegerField ( null = True , blank = True )
0 commit comments