Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/rard/research/migrations/0077_add_bibliographic_works.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 3.2.25 on 2026-06-25 11:52

from django.db import migrations, models


def add_bibliographic_works(apps, schema_editor):
"""Run save on all Antiquarian objects to create
a new bibliographic work for each."""
Antiquarian = apps.get_model("research", "Antiquarian")
Work = apps.get_model("research", "Work")

# Create an explicit bibliographic Work for each Antiquarian.
for antiquarian in Antiquarian.objects.all():
# If this antiquarian already has a bibliographic work, skip
if (
Work.objects.filter(bibliographic=True, antiquarian__pk=antiquarian.pk)
.exists()
):
continue

w = Work.objects.create(name="Bibliographic Work", bibliographic=True)
# attach via the m2m on the Antiquarian instance
antiquarian.works.add(w)
w.save()


class Migration(migrations.Migration):

dependencies = [
('research', '0076_add_folded_text'),
]

operations = [
migrations.AddField(
model_name='historicalwork',
name='bibliographic',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='work',
name='bibliographic',
field=models.BooleanField(default=False),
),
migrations.RunPython(add_bibliographic_works, reverse_code=migrations.RunPython.noop),
]
17 changes: 17 additions & 0 deletions src/rard/research/migrations/0078_alter_worklink_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 3.2.25 on 2026-06-25 21:23

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('research', '0077_add_bibliographic_works'),
]

operations = [
migrations.AlterModelOptions(
name='worklink',
options={'ordering': ['work__unknown', 'work__bibliographic', 'order']},
),
]
51 changes: 44 additions & 7 deletions src/rard/research/models/antiquarian.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
from rard.research.models.mixins import HistoryModelMixin, TextObjectFieldMixin
from rard.utils.basemodel import BaseModel, DatedModel, LockableModel, OrderableModel
from rard.utils.decorators import disable_for_loaddata
from rard.utils.shared_functions import collate_uw_links
from rard.utils.shared_functions import collate_work_links
from rard.utils.text_processors import make_plain_text


class WorkLink(OrderableModel, models.Model):
"""Through-model for Work to Antiquarian, m2m"""

class Meta:
ordering = ["work__unknown", "order"]
ordering = ["work__unknown", "work__bibliographic", "order"]

def related_queryset(self):
return self.__class__.objects.filter(
Expand Down Expand Up @@ -156,13 +156,17 @@ def ordered_works(self):
from rard.research.models import Work

return Work.objects.filter(worklink__antiquarian=self).order_by(
"unknown", "worklink__order"
"unknown", "bibliographic", "worklink__order"
)

@property
def unknown_work(self):
return self.works.filter(unknown=True).first()

@property
def bibliographic_work(self):
return self.works.filter(bibliographic=True).first()

def __str__(self):
return self.name

Expand All @@ -187,7 +191,9 @@ def reindex_work_links(self):
# single db update
with transaction.atomic():
links = WorkLink.objects.filter(antiquarian=self).order_by(
"work__unknown", models.F(("order")).asc(nulls_first=False)
"work__unknown",
"work__bibliographic",
models.F(("order")).asc(nulls_last=True),
)
for count, link in enumerate(links):
if link.order != count:
Expand Down Expand Up @@ -335,6 +341,7 @@ def reindex_fragment_and_testimonium_links(self):
.filter(work__isnull=False)
.order_by(
"-work__unknown",
"-work__bibliographic",
"work__worklink__order",
"work_order",
)
Expand Down Expand Up @@ -396,12 +403,29 @@ def collate_unknown(instance):
designated_unknown = unknown_works.first()
other_unknown_works = unknown_works.exclude(pk=designated_unknown.pk)

collate_uw_links(instance, designated_unknown)
collate_work_links(instance, designated_unknown, other_unknown_works)
other_unknown_works.delete()


@disable_for_loaddata
def create_unknown_work(sender, instance, **kwargs):
def collate_bibliographic(instance):
"""This makes sure there's only one bibliographic work per antiquarian and combines contents if otherwise"""
bibliographic_works = instance.works.filter(bibliographic=True).order_by("pk")

if bibliographic_works.count() > 1:
designated_bibliographic = bibliographic_works.first()
other_bibliographic_works = bibliographic_works.exclude(
pk=designated_bibliographic.pk
)

collate_work_links(
instance, designated_bibliographic, other_bibliographic_works
)
other_bibliographic_works.delete()


@disable_for_loaddata
def create_default_works(sender, instance, **kwargs):
from rard.research.models import Work

if not instance.unknown_work:
Expand All @@ -417,6 +441,19 @@ def create_unknown_work(sender, instance, **kwargs):
instance.unknown_work.antiquarian_set.add(instance)
instance.unknown_work.save()

if not instance.bibliographic_work:
# create bibliographic work if doesn't exist
bibliographic_work = Work.objects.create(
name="Bibliographic Work",
bibliographic=True,
)
bibliographic_work.antiquarian_set.add(instance)
bibliographic_work.save()
else:
# update existing
instance.bibliographic_work.antiquarian_set.add(instance)
instance.bibliographic_work.save()

collate_unknown(instance)


Expand Down Expand Up @@ -445,7 +482,7 @@ def remove_stale_antiquarian_links(sender, instance, **kwargs):

pre_delete.connect(remove_stale_antiquarian_links, sender=Antiquarian)

post_save.connect(create_unknown_work, sender=Antiquarian)
post_save.connect(create_default_works, sender=Antiquarian)


Antiquarian.init_text_object_fields()
Expand Down
6 changes: 4 additions & 2 deletions src/rard/research/models/work.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from rard.research.models.mixins import HistoryModelMixin, TextObjectFieldMixin
from rard.utils.basemodel import BaseModel, DatedModel, LockableModel, OrderableModel
from rard.utils.decorators import disable_for_loaddata
from rard.utils.shared_functions import collate_ub_links
from rard.utils.shared_functions import collate_book_links
from rard.utils.text_processors import make_plain_text


Expand Down Expand Up @@ -53,6 +53,8 @@ class Meta:

unknown = models.BooleanField(default=False)

bibliographic = models.BooleanField(default=False)

introduction = models.OneToOneField(
"TextObjectField",
on_delete=models.SET_NULL,
Expand Down Expand Up @@ -303,7 +305,7 @@ def collate_unknown(instance):
designated_unknown = unknown_books.first()
other_unknown_books = unknown_books.exclude(pk=designated_unknown.pk)

collate_ub_links(instance, designated_unknown)
collate_book_links(instance, designated_unknown, other_unknown_books)
other_unknown_books.delete()


Expand Down
4 changes: 2 additions & 2 deletions src/rard/research/tests/forms/test_fragment.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ def test_required_fields_no_work_selected(self):
self.assertIsNone(form.fields["work"].initial)
self.assertFalse(form.fields["work"].disabled)
self.assertEqual(
form.fields["work"].queryset.count(), 2
) # includes unknown work
form.fields["work"].queryset.count(), 3
) # includes unknown work and bibliographic work
self.assertTrue(form.fields["book"].disabled)
self.assertEqual(form.fields["book"].queryset.count(), 0)

Expand Down
32 changes: 30 additions & 2 deletions src/rard/research/tests/models/test_antiquarian.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ def test_display(self):
def test_no_initial_works(self):
data = {"name": "John Smith", "re_code": "smitre001"}
a = Antiquarian.objects.create(**data)
self.assertEqual(a.works.filter(unknown=False).count(), 0)
self.assertEqual(a.works.filter(unknown=False, bibliographic=False).count(), 0)

def test_can_have_multiple_works(self):
data = {"name": "John Smith", "re_code": "smitre001"}
a = Antiquarian.objects.create(**data)
length = 10
for _ in range(0, length):
a.works.create(name="name")
self.assertEqual(a.works.filter(unknown=False).count(), length)
self.assertEqual(
a.works.filter(unknown=False, bibliographic=False).count(), length
)

def test_introduction_created_with_antiquarian(self):
data = {"name": "John Smith", "re_code": "smitre001"}
Expand Down Expand Up @@ -245,6 +247,32 @@ def test_collate_unknown(self):
self.assertEqual(a.fragmentlinks.all().count(), 1)
self.assertEqual(a.fragmentlinks.first().work, a.unknown_work)

def test_collate_unknown_preserves_other_work_links(self):
data = {"name": "John Smith", "re_code": "smitre002"}
a = Antiquarian.objects.create(**data)
f = Fragment.objects.create(name="fragment")
keep_work = Work.objects.create(name="A Known Work")
a.works.add(keep_work)
FragmentLink.objects.create(antiquarian=a, fragment=f, work=keep_work)

additional_unknown = Work.objects.create(unknown=True, name="Unknown Work")
additional_unknown.antiquarian_set.add(a)
FragmentLink.objects.create(
antiquarian=a,
fragment=f,
work=additional_unknown,
)

self.assertEqual(a.works.filter(unknown=True).count(), 2)
self.assertEqual(a.fragmentlinks.filter(work=keep_work).count(), 1)
self.assertEqual(a.fragmentlinks.filter(work=additional_unknown).count(), 1)

collate_unknown(a)

self.assertEqual(a.works.filter(unknown=True).count(), 1)
self.assertEqual(a.fragmentlinks.filter(work=keep_work).count(), 1)
self.assertEqual(a.fragmentlinks.filter(work=a.unknown_work).count(), 1)


class TestWorkLink(TestCase):
def test_related_queryset(self):
Expand Down
27 changes: 18 additions & 9 deletions src/rard/research/tests/models/test_link_scheme.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,8 @@ def test_ordered_works(self):
a.works.add(Work.objects.create(name=name))

self.assertEqual(
[w.name for w in a.ordered_works.all()], names + ["Unknown Work"]
[w.name for w in a.ordered_works.all()],
names + ["Bibliographic Work", "Unknown Work"],
)
# try moving a work down in the order
link = a.worklink_set.first()
Expand All @@ -431,11 +432,16 @@ def test_ordered_works(self):
"work two",
"work one",
"work three",
"Bibliographic Work",
"Unknown Work",
],
)

link = a.worklink_set.exclude(work__unknown=True).last()
link = (
a.worklink_set.exclude(work__unknown=True)
.exclude(work__bibliographic=True)
.last()
)
link.up()

# should have reordered
Expand All @@ -445,6 +451,7 @@ def test_ordered_works(self):
"work two",
"work three",
"work one",
"Bibliographic Work",
"Unknown Work",
],
)
Expand Down Expand Up @@ -596,19 +603,20 @@ def _run_test_add_del_multi_works_updates_links(self, method):
works = Work.objects.all()

# set up creates a work called 'work', we've created
# four others here called 'another' and there is a default Unknown Work
self.assertEqual(ADD + 2, works.count())
# four others here called 'another' and there are default
# Unknown and Bibliographic Works
self.assertEqual(ADD + 3, works.count())

# set the antiquarian works all at once
self.antiquarian.works.set(works)

ant_works = self.antiquarian.works.all()
ant_fragmentlinks = self.antiquarian.fragmentlinks.all()

# check it worked - we should have 5 works in total
# check it worked - we should have 7 works in total
self.assertEqual(works.count(), ant_works.count())

# we should at this point have 5 sets
# we should at this point have 5 sets of 10 fragment links
# 4 linked via the work 'another' and one other work = 5
expected = (self.NUM * ADD) + starting_fragmentlinks_count
self.assertEqual(ant_fragmentlinks.count(), expected)
Expand All @@ -623,7 +631,7 @@ def _run_test_add_del_multi_works_updates_links(self, method):
self.antiquarian.works.set(Work.objects.none()) # set to empty

# antiquarian should now have fewer links directly to it
expected = (ant_works.count()) * self.NUM
expected = (max(ant_works.count() - 1, 0)) * self.NUM
self.assertEqual(ant_fragmentlinks.count(), expected)
# the antiquarian links have been reordered
for count, link in enumerate(
Expand All @@ -636,9 +644,10 @@ def _run_test_add_del_multi_works_updates_links(self, method):
nworks = Work.objects.count()

# there should be no stray links lying around
self.assertEqual(FragmentLink.objects.all().count(), nfragments * nworks)
expected = nfragments * (nworks - 1) # ignore bibliographic work
self.assertEqual(FragmentLink.objects.all().count(), expected)

for work in Work.objects.all():
for work in Work.objects.exclude(bibliographic=True).exclude(unknown=True):
self.assertEqual(FragmentLink.objects.filter(work=work).count(), nfragments)

# check that orphaned works have been reordered
Expand Down
Loading
Loading