Skip to content

Commit 3e0f399

Browse files
committed
move all Title logic into delta_patterns.py
1 parent a1bd63e commit 3e0f399

File tree

2 files changed

+166
-165
lines changed

2 files changed

+166
-165
lines changed

sde_collections/models/delta_patterns.py

Lines changed: 165 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
205351
def 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)

sde_collections/models/delta_url.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.db import models
55

66
from .collection_choice_fields import Divisions, DocumentTypes
7-
from .delta_patterns import DeltaExcludePattern, DeltaIncludePattern, DeltaTitlePattern
7+
from .delta_patterns import DeltaExcludePattern, DeltaIncludePattern
88

99

1010
class DeltaUrlQuerySet(models.QuerySet):
@@ -174,31 +174,3 @@ class Meta:
174174
verbose_name = "Curated Urls"
175175
verbose_name_plural = "Curated Urls"
176176
ordering = ["url"]
177-
178-
179-
class DeltaResolvedTitleBase(models.Model):
180-
# TODO: need to understand this logic and whether we need to have thess match to CuratedUrls as well
181-
title_pattern = models.ForeignKey(DeltaTitlePattern, on_delete=models.CASCADE)
182-
delta_url = models.OneToOneField(DeltaUrl, on_delete=models.CASCADE)
183-
created_at = models.DateTimeField(auto_now_add=True)
184-
185-
class Meta:
186-
abstract = True
187-
188-
189-
class DeltaResolvedTitle(DeltaResolvedTitleBase):
190-
resolved_title = models.CharField(blank=True, default="")
191-
192-
class Meta:
193-
verbose_name = "Resolved Title"
194-
verbose_name_plural = "Resolved Titles"
195-
196-
def save(self, *args, **kwargs):
197-
# Finds the linked delta URL and deletes DeltaResolvedTitleError objects linked to it
198-
DeltaResolvedTitleError.objects.filter(delta_url=self.delta_url).delete()
199-
super().save(*args, **kwargs)
200-
201-
202-
class DeltaResolvedTitleError(DeltaResolvedTitleBase):
203-
error_string = models.TextField(null=False, blank=False)
204-
http_status_code = models.IntegerField(null=True, blank=True)

0 commit comments

Comments
 (0)