From 160ff0b60aa0569d90b7d2e826a678279718dea6 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 31 Jan 2024 18:12:46 +0100 Subject: [PATCH 01/20] add name and category to letters --- ...002_category_letter_name_lettercategory.py | 83 +++++++++++++++++++ .../migrations/0003_populate_letter_name.py | 20 +++++ .../0004_make_letter_name_unique.py | 22 +++++ backend/letter/models.py | 26 +++++- 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 backend/letter/migrations/0002_category_letter_name_lettercategory.py create mode 100644 backend/letter/migrations/0003_populate_letter_name.py create mode 100644 backend/letter/migrations/0004_make_letter_name_unique.py diff --git a/backend/letter/migrations/0002_category_letter_name_lettercategory.py b/backend/letter/migrations/0002_category_letter_name_lettercategory.py new file mode 100644 index 00000000..cd854a9c --- /dev/null +++ b/backend/letter/migrations/0002_category_letter_name_lettercategory.py @@ -0,0 +1,83 @@ +# Generated by Django 4.2.7 on 2024-01-31 17:02 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + dependencies = [ + ("letter", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("label", models.CharField(max_length=200, unique=True)), + ("description", models.TextField(blank=True)), + ], + ), + migrations.AddField( + model_name="letter", + name="name", + field=models.CharField( + help_text="a unique name to identify this letter in the database", + max_length=200, + null=True, + ), + ), + migrations.CreateModel( + name="LetterCategory", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "certainty", + models.IntegerField( + choices=[ + (0, "uncertain"), + (1, "somewhat certain"), + (2, "certain"), + ], + default=2, + help_text="How certain are you of this value?", + ), + ), + ("note", models.TextField(blank=True, help_text="Additional notes")), + ( + "category", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="letter.category", + ), + ), + ( + "letter", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, to="letter.letter" + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/backend/letter/migrations/0003_populate_letter_name.py b/backend/letter/migrations/0003_populate_letter_name.py new file mode 100644 index 00000000..20438b6a --- /dev/null +++ b/backend/letter/migrations/0003_populate_letter_name.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.7 on 2024-01-31 17:03 + +from django.db import migrations + + +def generate_name(apps, schema_editor): + Letter = apps.get_model("letter", "Letter") + for row in Letter.objects.all(): + row.name = f"letter #{row.id}" + row.save(update_fields=["name"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("letter", "0002_category_letter_name_lettercategory"), + ] + + operations = [ + migrations.RunPython(generate_name, reverse_code=migrations.RunPython.noop) + ] diff --git a/backend/letter/migrations/0004_make_letter_name_unique.py b/backend/letter/migrations/0004_make_letter_name_unique.py new file mode 100644 index 00000000..e450b279 --- /dev/null +++ b/backend/letter/migrations/0004_make_letter_name_unique.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.7 on 2024-01-31 17:03 + +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + dependencies = [ + ("letter", "0003_populate_letter_name"), + ] + + operations = [ + migrations.AlterField( + model_name="letter", + name="name", + field=models.CharField( + help_text="a unique name to identify this letter in the database", + max_length=200, + unique=True, + ), + ), + ] diff --git a/backend/letter/models.py b/backend/letter/models.py index 5da71696..62a3a083 100644 --- a/backend/letter/models.py +++ b/backend/letter/models.py @@ -3,8 +3,32 @@ class Letter(models.Model): + name = models.CharField( + max_length=200, + blank=False, + unique=True, + help_text="a unique name to identify this letter in the database", + ) + + def __str__(self): + return self.name + + +class Category(models.Model): + label = models.CharField(max_length=200, blank=False, null=False, unique=True) + description = models.TextField(blank=True, null=False) + def __str__(self): - return f"letter #{self.id}" + return self.label + + +class LetterCategory(Field, models.Model): + category = models.ForeignKey(to=Category, null=True, on_delete=models.SET_NULL) + letter = models.OneToOneField( + to=Letter, + on_delete=models.CASCADE, + null=False, + ) class LetterMaterial(Field, models.Model): From 5316a072550b6efdfd2699c06a47571d95941a98 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 31 Jan 2024 18:25:39 +0100 Subject: [PATCH 02/20] verbose names and admin --- backend/letter/admin.py | 11 +++++++++++ .../migrations/0005_alter_category_options.py | 17 +++++++++++++++++ backend/letter/models.py | 7 +++++++ 3 files changed, 35 insertions(+) create mode 100644 backend/letter/migrations/0005_alter_category_options.py diff --git a/backend/letter/admin.py b/backend/letter/admin.py index 32a8f026..7dd069a7 100644 --- a/backend/letter/admin.py +++ b/backend/letter/admin.py @@ -2,13 +2,24 @@ from . import models +@admin.register(models.Category) +class CategoryAdmin(admin.ModelAdmin): + fields = ["label", "description"] + + class LetterMaterialAdmin(admin.StackedInline): model = models.LetterMaterial fields = ["surface", "certainty", "note"] +class LetterCategoryAdmin(admin.StackedInline): + model = models.LetterCategory + fields = ["letter", "category"] + + @admin.register(models.Letter) class LetterAdmin(admin.ModelAdmin): inlines = [ + LetterCategoryAdmin, LetterMaterialAdmin, ] diff --git a/backend/letter/migrations/0005_alter_category_options.py b/backend/letter/migrations/0005_alter_category_options.py new file mode 100644 index 00000000..c6cd1b91 --- /dev/null +++ b/backend/letter/migrations/0005_alter_category_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.7 on 2024-01-31 17:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('letter', '0004_make_letter_name_unique'), + ] + + operations = [ + migrations.AlterModelOptions( + name='category', + options={'verbose_name': 'letter category', 'verbose_name_plural': 'letter categories'}, + ), + ] diff --git a/backend/letter/models.py b/backend/letter/models.py index 62a3a083..8fe1d97e 100644 --- a/backend/letter/models.py +++ b/backend/letter/models.py @@ -18,6 +18,10 @@ class Category(models.Model): label = models.CharField(max_length=200, blank=False, null=False, unique=True) description = models.TextField(blank=True, null=False) + class Meta: + verbose_name = "letter category" + verbose_name_plural = "letter categories" + def __str__(self): return self.label @@ -30,6 +34,9 @@ class LetterCategory(Field, models.Model): null=False, ) + def __str__(self): + return f"category of {self.letter}" + class LetterMaterial(Field, models.Model): surface = models.CharField( From 02fd46b8cc61a97ac352a8b69d37c682da4989da Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 31 Jan 2024 18:45:29 +0100 Subject: [PATCH 03/20] add certainty and note to letter category admin --- backend/letter/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/letter/admin.py b/backend/letter/admin.py index 7dd069a7..9e46c847 100644 --- a/backend/letter/admin.py +++ b/backend/letter/admin.py @@ -14,7 +14,7 @@ class LetterMaterialAdmin(admin.StackedInline): class LetterCategoryAdmin(admin.StackedInline): model = models.LetterCategory - fields = ["letter", "category"] + fields = ["letter", "category", "certainty", "note"] @admin.register(models.Letter) From 29aa9a9b6a8297496e1975a1893c62b6d50ba1d5 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 31 Jan 2024 19:22:28 +0100 Subject: [PATCH 04/20] add date properties for letters --- backend/conftest.py | 43 +++++++++++++++++++++- backend/event/tests/test_models.py | 6 +-- backend/letter/models.py | 15 ++++++++ backend/letter/tests/test_letter_models.py | 5 +++ 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 backend/letter/tests/test_letter_models.py diff --git a/backend/conftest.py b/backend/conftest.py index a7471bad..df84f54f 100644 --- a/backend/conftest.py +++ b/backend/conftest.py @@ -1,24 +1,41 @@ import pytest from case_study.models import CaseStudy from letter.models import Letter -from event.models import EpistolaryEvent, LetterAction, LetterActionCategory +from event.models import ( + EpistolaryEvent, + LetterAction, + LetterActionCategory, + LetterEventDate, +) from person.models import Person @pytest.fixture() def letter(db): letter = Letter.objects.create() + letter.name = "letter for testing" + letter.save() return letter @pytest.fixture() def person(db): person = Person.objects.create() + person.name = "Bert" + person.save() return person @pytest.fixture() -def letter_action(db, letter, person): +def person_2(db): + person = Person.objects.create() + person.name = "Ernie" + person.save() + return person + + +@pytest.fixture() +def letter_action_writing(db, letter, person): letter_action = LetterAction.objects.create() letter_action.letters.add(letter) letter_action.actors.add(person) @@ -28,6 +45,28 @@ def letter_action(db, letter, person): value="write", ) + LetterEventDate.objects.create( + year_lower=500, year_upper=500, year_exact=500, letter_action=letter_action + ) + + return letter_action + + +@pytest.fixture() +def letter_action_reading(db, letter, person_2): + letter_action = LetterAction.objects.create() + letter_action.letters.add(letter) + letter_action.actors.add(person_2) + + LetterActionCategory.objects.create( + letter_action=letter_action, + value="read", + ) + + LetterEventDate.objects.create( + year_lower=505, year_upper=510, letter_action=letter_action + ) + return letter_action diff --git a/backend/event/tests/test_models.py b/backend/event/tests/test_models.py index d47705a4..40b18af8 100644 --- a/backend/event/tests/test_models.py +++ b/backend/event/tests/test_models.py @@ -1,4 +1,4 @@ -def test_letter_action_name(letter, letter_action): - action_str = str(letter_action) +def test_letter_action_name(letter, letter_action_writing): + action_str = str(letter_action_writing) - assert str(action_str) == f'writing of {str(letter)}' + assert str(action_str) == f"writing of {str(letter)}" diff --git a/backend/letter/models.py b/backend/letter/models.py index 8fe1d97e..9ccad033 100644 --- a/backend/letter/models.py +++ b/backend/letter/models.py @@ -13,6 +13,21 @@ class Letter(models.Model): def __str__(self): return self.name + def date_written(self): + """Date range in which the letter was written""" + return self._aggregate_dates(self.events.filter(categories__value="write")) + + def date_active(self): + """Date range in which anything happened with the letter""" + return self._aggregate_dates(self.events.all()) + + def _aggregate_dates(actions): + """Calculate a date range based on the dates of related actions""" + dates = [action.date for action in actions] + lower = min(date.year_lower for date in dates) + upper = max(data.year_upper for data in dates) + return lower, upper + class Category(models.Model): label = models.CharField(max_length=200, blank=False, null=False, unique=True) diff --git a/backend/letter/tests/test_letter_models.py b/backend/letter/tests/test_letter_models.py new file mode 100644 index 00000000..7a16e9ff --- /dev/null +++ b/backend/letter/tests/test_letter_models.py @@ -0,0 +1,5 @@ +def test_letter_property_inference( + letter, letter_action_writing, letter_action_reading +): + assert letter.date_written() == (500, 500) + assert letter.date_active() == (500, 510) From 8b28a9bf6f63b61bcfcb9211e1e9640953c05ef6 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 1 Feb 2024 11:47:42 +0100 Subject: [PATCH 05/20] add senders and addressees --- backend/letter/admin.py | 14 +++++++ .../0006_lettersenders_letteraddressees.py | 41 +++++++++++++++++++ backend/letter/models.py | 39 ++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 backend/letter/migrations/0006_lettersenders_letteraddressees.py diff --git a/backend/letter/admin.py b/backend/letter/admin.py index 9e46c847..cf80fa07 100644 --- a/backend/letter/admin.py +++ b/backend/letter/admin.py @@ -17,9 +17,23 @@ class LetterCategoryAdmin(admin.StackedInline): fields = ["letter", "category", "certainty", "note"] +class LetterSenderAdmin(admin.StackedInline): + model = models.LetterSenders + fields = ["letter", "senders", "certainty", "note"] + filter_horizontal = ["senders"] + + +class LetterAddresseesAdmin(admin.StackedInline): + model = models.LetterAddressees + fields = ["letter", "addressees", "certainty", "note"] + filter_horizontal = ["addressees"] + + @admin.register(models.Letter) class LetterAdmin(admin.ModelAdmin): inlines = [ LetterCategoryAdmin, LetterMaterialAdmin, + LetterSenderAdmin, + LetterAddresseesAdmin, ] diff --git a/backend/letter/migrations/0006_lettersenders_letteraddressees.py b/backend/letter/migrations/0006_lettersenders_letteraddressees.py new file mode 100644 index 00000000..10233033 --- /dev/null +++ b/backend/letter/migrations/0006_lettersenders_letteraddressees.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.7 on 2024-02-01 10:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('person', '0001_initial'), + ('letter', '0005_alter_category_options'), + ] + + operations = [ + migrations.CreateModel( + name='LetterSenders', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('certainty', models.IntegerField(choices=[(0, 'uncertain'), (1, 'somewhat certain'), (2, 'certain')], default=2, help_text='How certain are you of this value?')), + ('note', models.TextField(blank=True, help_text='Additional notes')), + ('letter', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='letter.letter')), + ('senders', models.ManyToManyField(blank=True, help_text='persons that the letter names as the sender', to='person.person')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='LetterAddressees', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('certainty', models.IntegerField(choices=[(0, 'uncertain'), (1, 'somewhat certain'), (2, 'certain')], default=2, help_text='How certain are you of this value?')), + ('note', models.TextField(blank=True, help_text='Additional notes')), + ('addressees', models.ManyToManyField(blank=True, help_text='persons that the letter names as the addressee', to='person.person')), + ('letter', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='letter.letter')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/backend/letter/models.py b/backend/letter/models.py index 9ccad033..e4b24294 100644 --- a/backend/letter/models.py +++ b/backend/letter/models.py @@ -1,5 +1,6 @@ from django.db import models from core.models import Field +from person.models import Person class Letter(models.Model): @@ -75,3 +76,41 @@ def __str__(self): return f"material of {self.letter}" else: return f"material #{self.id}" + + +class LetterSenders(Field, models.Model): + senders = models.ManyToManyField( + to=Person, + blank=True, + help_text="persons that the letter names as the sender", + ) + letter = models.OneToOneField( + to=Letter, + on_delete=models.CASCADE, + null=False, + ) + + def __str__(self): + if self.letter: + return f"senders of {self.letter}" + else: + return f"senders #{self.id}" + + +class LetterAddressees(Field, models.Model): + addressees = models.ManyToManyField( + to=Person, + blank=True, + help_text="persons that the letter names as the addressee", + ) + letter = models.OneToOneField( + to=Letter, + on_delete=models.CASCADE, + null=False, + ) + + def __str__(self): + if self.letter: + return f"addressees of {self.letter}" + else: + return f"addressees #{self.id}" From bc92f9fd6e0d16dc3d2637e9a6ef1b813d8e03f5 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 1 Feb 2024 14:09:36 +0100 Subject: [PATCH 06/20] show date ranges in letter admin --- backend/letter/admin.py | 1 + backend/letter/models.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/letter/admin.py b/backend/letter/admin.py index cf80fa07..75bc2eb9 100644 --- a/backend/letter/admin.py +++ b/backend/letter/admin.py @@ -31,6 +31,7 @@ class LetterAddresseesAdmin(admin.StackedInline): @admin.register(models.Letter) class LetterAdmin(admin.ModelAdmin): + readonly_fields = ["date_active", "date_written"] inlines = [ LetterCategoryAdmin, LetterMaterialAdmin, diff --git a/backend/letter/models.py b/backend/letter/models.py index e4b24294..30b17eff 100644 --- a/backend/letter/models.py +++ b/backend/letter/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.contrib import admin from core.models import Field from person.models import Person @@ -14,15 +15,19 @@ class Letter(models.Model): def __str__(self): return self.name - def date_written(self): - """Date range in which the letter was written""" - return self._aggregate_dates(self.events.filter(categories__value="write")) - + @admin.display( + description="Date range of actions involving this letter", + ) def date_active(self): - """Date range in which anything happened with the letter""" return self._aggregate_dates(self.events.all()) - def _aggregate_dates(actions): + @admin.display( + description="Date range in which this letter was written", + ) + def date_written(self): + return self._aggregate_dates(self.events.filter(categories__value="write")) + + def _aggregate_dates(self, actions): """Calculate a date range based on the dates of related actions""" dates = [action.date for action in actions] lower = min(date.year_lower for date in dates) From b17b97aafb7e96d88740c33bf05b5c32ccec8ffe Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Mon, 5 Feb 2024 16:43:40 +0100 Subject: [PATCH 07/20] Added computed property display_date --- backend/core/models.py | 6 ++++++ backend/event/models.py | 7 +++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/core/models.py b/backend/core/models.py index f749ca37..757b9d98 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -53,6 +53,12 @@ class LettercraftDate(models.Model): help_text="The exact year of the value (if known). This will override the values in the lower and upper bounds fields.", ) + @property + def display_date(self): + if self.year_exact: + return str(self.year_exact) + return f"c. {self.year_lower}–{self.year_upper}" + class Meta: abstract = True diff --git a/backend/event/models.py b/backend/event/models.py index 94a6dae0..f6819bb8 100644 --- a/backend/event/models.py +++ b/backend/event/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.core.validators import MinValueValidator, MaxValueValidator from core.models import Field, LettercraftDate from case_study.models import CaseStudy @@ -29,7 +28,7 @@ class EpistolaryEvent(models.Model): note = models.TextField( null=False, blank=True, - help_text="Additional notes that describe the event and what connects the letter actions it comprises." + help_text="Additional notes that describe the event and what connects the letter actions it comprises.", ) def __str__(self): @@ -112,14 +111,14 @@ class LetterEventDate(Field, LettercraftDate, models.Model): ) def __str__(self): - date = self.year_exact or f"{self.year_lower}–{self.year_upper}" - return f"{self.letter_action} in {date}" + return f"{self.letter_action} ({self.display_date})" class Role(Field, models.Model): """ Describes the involvement of a person in a letter action. """ + person = models.ForeignKey( to=Person, on_delete=models.CASCADE, From c864bf6dff8269d97513420eeb09cd2b6abd741e Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Mon, 5 Feb 2024 17:50:31 +0100 Subject: [PATCH 08/20] World events and event triggers --- backend/event/admin.py | 15 +++++- ...stolaryeventtrigger_worldevent_and_more.py | 42 ++++++++++++++++ backend/event/models.py | 50 +++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 backend/event/migrations/0003_epistolaryeventtrigger_worldevent_and_more.py diff --git a/backend/event/admin.py b/backend/event/admin.py index 428a4e78..78964402 100644 --- a/backend/event/admin.py +++ b/backend/event/admin.py @@ -72,10 +72,23 @@ class EpistolaryEventLetterActionInline(admin.StackedInline): verbose_name = "relationship between a epistolary event and a letter action" +class WorldEventInline(admin.StackedInline): + model = models.EpistolaryEvent.triggers.through + extra = 0 + verbose_name = "Triggering world events" + verbose_name_plural = "Triggering world events" + + @admin.register(models.EpistolaryEvent) class EpistolaryEventAdmin(admin.ModelAdmin): fields = ["name", "note"] inlines = [ EpistolaryEventCaseStudyInline, - EpistolaryEventLetterActionInline + EpistolaryEventLetterActionInline, + WorldEventInline, ] + + +@admin.register(models.WorldEvent) +class WorldEventAdmin(admin.ModelAdmin): + fields = ["name", "note", "year_exact", "year_lower", "year_upper"] diff --git a/backend/event/migrations/0003_epistolaryeventtrigger_worldevent_and_more.py b/backend/event/migrations/0003_epistolaryeventtrigger_worldevent_and_more.py new file mode 100644 index 00000000..d4ad6352 --- /dev/null +++ b/backend/event/migrations/0003_epistolaryeventtrigger_worldevent_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.7 on 2024-02-05 16:14 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0002_alter_lettereventdate_year_exact_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='EpistolaryEventTrigger', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('epistolary_event', models.ForeignKey(help_text='The epistolary event that was triggered by a world event', on_delete=django.db.models.deletion.CASCADE, to='event.epistolaryevent')), + ], + ), + migrations.CreateModel( + name='WorldEvent', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('year_lower', models.IntegerField(default=400, help_text='The earliest possible year for this value', validators=[django.core.validators.MinValueValidator(400), django.core.validators.MaxValueValidator(800)])), + ('year_upper', models.IntegerField(default=800, help_text='The latest possible year for this value', validators=[django.core.validators.MinValueValidator(400), django.core.validators.MaxValueValidator(800)])), + ('year_exact', models.IntegerField(blank=True, help_text='The exact year of the value (if known). This will override the values in the lower and upper bounds fields.', null=True, validators=[django.core.validators.MinValueValidator(400), django.core.validators.MaxValueValidator(800)])), + ('name', models.CharField(help_text="The name of the event, e.g. 'The Great Fire of London' or 'The Battle of Hastings'.", max_length=256)), + ('note', models.TextField(blank=True, help_text='Additional notes that describe the event and its relevance to the letters.')), + ('epistolary_events', models.ManyToManyField(related_name='triggers', through='event.EpistolaryEventTrigger', to='event.epistolaryevent')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='epistolaryeventtrigger', + name='world_event', + field=models.ForeignKey(help_text='The world event that triggered an epistolary event', on_delete=django.db.models.deletion.CASCADE, to='event.worldevent'), + ), + ] diff --git a/backend/event/models.py b/backend/event/models.py index f6819bb8..f477789c 100644 --- a/backend/event/models.py +++ b/backend/event/models.py @@ -159,3 +159,53 @@ class Role(Field, models.Model): def __str__(self): return f"role of {self.person} in {self.letter_action}" + + +class WorldEvent(LettercraftDate, models.Model): + """ + World events are events that are not directly related to a specific letter + or letter action, but are relevant to the context of the letters. + """ + + name = models.CharField( + max_length=256, + null=False, + blank=False, + help_text="The name of the event, e.g. 'The Great Fire of London' or 'The Battle of Hastings'.", + ) + + note = models.TextField( + null=False, + blank=True, + help_text="Additional notes that describe the event and its relevance to the letters.", + ) + + epistolary_events = models.ManyToManyField( + to=EpistolaryEvent, + through="EpistolaryEventTrigger", + related_name="triggers", + ) + + def __str__(self): + return f"{self.name} ({self.display_date})" + + +class EpistolaryEventTrigger(models.Model): + """ + A relationship between an epistolary event and a world event that triggered it. + """ + + epistolary_event = models.ForeignKey( + to=EpistolaryEvent, + on_delete=models.CASCADE, + help_text="The epistolary event that was triggered by a world event", + ) + + world_event = models.ForeignKey( + to=WorldEvent, + on_delete=models.CASCADE, + help_text="The world event that triggered an epistolary event", + ) + + def __str__(self): + return f"{self.world_event} triggered {self.epistolary_event}" From 689b8297183be8d70b38b7d5e8d1fd33b3de4f92 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Mon, 5 Feb 2024 17:51:16 +0100 Subject: [PATCH 09/20] Add model tests; rename app in admin --- backend/conftest.py | 15 ++++++-- backend/event/apps.py | 2 +- backend/event/tests/test_event_models.py | 44 +++++++++++++++++++++++- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/backend/conftest.py b/backend/conftest.py index 16944be8..786e8bee 100644 --- a/backend/conftest.py +++ b/backend/conftest.py @@ -1,8 +1,8 @@ import pytest from case_study.models import CaseStudy from letter.models import Letter -from event.models import EpistolaryEvent, LetterAction, LetterActionCategory -from person.models import Person, PersonDateOfBirth, PersonName +from event.models import EpistolaryEvent, LetterAction, LetterActionCategory, WorldEvent +from person.models import Person @pytest.fixture() @@ -40,7 +40,16 @@ def case_study(db): @pytest.fixture() def epistolary_event(db, letter, case_study): epistolary_event = EpistolaryEvent.objects.create( - name="Test Epistolary event", note="Test note", case_studies=[case_study] + name="Test Epistolary event", note="Test note" ) + epistolary_event.case_studies.add(case_study) return epistolary_event + + +@pytest.fixture() +def world_event(db): + world_event = WorldEvent.objects.create( + name="Test World Event", note="Test World Event note", year_exact=612 + ) + return world_event diff --git a/backend/event/apps.py b/backend/event/apps.py index 53dfb4d9..a28c1dbb 100644 --- a/backend/event/apps.py +++ b/backend/event/apps.py @@ -4,4 +4,4 @@ class EventConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'event' - verbose_name = 'epistolary episodes and actions' + verbose_name = 'events and actions' diff --git a/backend/event/tests/test_event_models.py b/backend/event/tests/test_event_models.py index d47705a4..a7d3ef63 100644 --- a/backend/event/tests/test_event_models.py +++ b/backend/event/tests/test_event_models.py @@ -1,4 +1,46 @@ +from event.models import LetterEventDate + + def test_letter_action_name(letter, letter_action): action_str = str(letter_action) - assert str(action_str) == f'writing of {str(letter)}' + assert str(action_str) == f"writing of {str(letter)}" + + +def test_letter_event_date_with_exact_date(letter_action): + letter_event_date = LetterEventDate.objects.create( + letter_action=letter_action, + year_exact=500, + ) + assert ( + str(letter_event_date) + == f"writing of {str(letter_action.letters.first())} (500)" + ) + + +def test_letter_event_date_with_date_range(letter_action): + letter_event_date = LetterEventDate.objects.create( + letter_action=letter_action, + year_lower=500, + year_upper=600, + ) + assert ( + str(letter_event_date) + == f"writing of {str(letter_action.letters.first())} (c. 500–600)" + ) + + +def test_world_event(world_event): + world_event.year_exact = 500 + world_event.save() + assert str(world_event) == "Test World Event (500)" + + +def test_world_event_trigger(world_event, epistolary_event): + world_event.epistolary_events.add(epistolary_event) + world_event.save() + + trigger = world_event.epistolary_events.through.objects.first() + + assert str(trigger) == "Test World Event (612) triggered Test Epistolary event" + From e70279d761bd829e1b4fe3a59e02d3b991f86e8f Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 9 Feb 2024 13:46:05 +0100 Subject: [PATCH 10/20] Add instigator to Role.role choices --- .../event/migrations/0004_alter_role_role.py | 18 ++++++++++++++++++ backend/event/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 backend/event/migrations/0004_alter_role_role.py diff --git a/backend/event/migrations/0004_alter_role_role.py b/backend/event/migrations/0004_alter_role_role.py new file mode 100644 index 00000000..b9c360fb --- /dev/null +++ b/backend/event/migrations/0004_alter_role_role.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2024-02-09 12:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0003_epistolaryeventtrigger_worldevent_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='role', + field=models.CharField(choices=[('author', 'Author'), ('scribe', 'Scribe'), ('reader', 'Reader'), ('witness', 'Witness'), ('messenger', 'Messenger'), ('recipient', 'Recipient'), ('intended_recipient', 'Intended recipient'), ('audience', 'Audience'), ('intended_audience', 'Intended audience'), ('instigator', 'Instigator'), ('other', 'Other')], help_text='Role of this person in the event'), + ), + ] diff --git a/backend/event/models.py b/backend/event/models.py index f477789c..29c1bef9 100644 --- a/backend/event/models.py +++ b/backend/event/models.py @@ -145,6 +145,7 @@ class Role(Field, models.Model): ("intended_recipient", "Intended recipient"), ("audience", "Audience"), ("intended_audience", "Intended audience"), + ("instigator", "Instigator"), ("other", "Other"), ], null=False, From 5be7c0e27dd7ae4e7683ba2028828c311dfddec7 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Fri, 9 Feb 2024 13:05:06 +0100 Subject: [PATCH 11/20] start source app --- backend/lettercraft/settings.py | 29 ++++++++++++++------------- backend/source/__init__.py | 0 backend/source/admin.py | 3 +++ backend/source/apps.py | 6 ++++++ backend/source/migrations/__init__.py | 0 backend/source/models.py | 3 +++ 6 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 backend/source/__init__.py create mode 100644 backend/source/admin.py create mode 100644 backend/source/apps.py create mode 100644 backend/source/migrations/__init__.py create mode 100644 backend/source/models.py diff --git a/backend/lettercraft/settings.py b/backend/lettercraft/settings.py index f3b5cfa8..67e25080 100644 --- a/backend/lettercraft/settings.py +++ b/backend/lettercraft/settings.py @@ -29,20 +29,21 @@ # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'livereload', - 'django.contrib.staticfiles', - 'rest_framework', - 'revproxy', - 'core', - 'case_study', - 'event', - 'letter', - 'person', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "livereload", + "django.contrib.staticfiles", + "rest_framework", + "revproxy", + "core", + "case_study", + "event", + "letter", + "person", + "source", ] MIDDLEWARE = [ diff --git a/backend/source/__init__.py b/backend/source/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/source/admin.py b/backend/source/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/backend/source/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/source/apps.py b/backend/source/apps.py new file mode 100644 index 00000000..d85484bb --- /dev/null +++ b/backend/source/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SourceConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'source' diff --git a/backend/source/migrations/__init__.py b/backend/source/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/source/models.py b/backend/source/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/backend/source/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. From 4307416f4becc921b1586537ccb6a82d57fa1b25 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Fri, 9 Feb 2024 13:12:07 +0100 Subject: [PATCH 12/20] add source model --- backend/source/admin.py | 6 +++++- backend/source/migrations/0001_initial.py | 22 ++++++++++++++++++++++ backend/source/models.py | 16 +++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 backend/source/migrations/0001_initial.py diff --git a/backend/source/admin.py b/backend/source/admin.py index 8c38f3f3..d6366570 100644 --- a/backend/source/admin.py +++ b/backend/source/admin.py @@ -1,3 +1,7 @@ from django.contrib import admin +from . import models -# Register your models here. + +@admin.register(models.Source) +class SourceAdmin(admin.ModelAdmin): + fields = ["name", "bibliographical_info"] diff --git a/backend/source/migrations/0001_initial.py b/backend/source/migrations/0001_initial.py new file mode 100644 index 00000000..07223ace --- /dev/null +++ b/backend/source/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.7 on 2024-02-09 12:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Source', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='a unique name to identify this source in the database', max_length=200, unique=True)), + ('bibliographical_info', models.TextField(blank=True, help_text='bibliographical information to identify this source')), + ], + ), + ] diff --git a/backend/source/models.py b/backend/source/models.py index 71a83623..0c543179 100644 --- a/backend/source/models.py +++ b/backend/source/models.py @@ -1,3 +1,17 @@ from django.db import models -# Create your models here. + +class Source(models.Model): + name = models.CharField( + max_length=200, + blank=False, + unique=True, + help_text="a unique name to identify this source in the database", + ) + + bibliographical_info = models.TextField( + blank=True, help_text="bibliographical information to identify this source" + ) + + def __str__(self): + return self.name From fff75fd8ec00a99b5e986e2302f1bebee64583be Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Fri, 9 Feb 2024 13:42:07 +0100 Subject: [PATCH 13/20] add reference model --- backend/source/migrations/0002_reference.py | 31 +++++++++++ backend/source/models.py | 58 +++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 backend/source/migrations/0002_reference.py diff --git a/backend/source/migrations/0002_reference.py b/backend/source/migrations/0002_reference.py new file mode 100644 index 00000000..b388508a --- /dev/null +++ b/backend/source/migrations/0002_reference.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.7 on 2024-02-09 13:18 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('source', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Reference', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('location', models.CharField(blank=True, help_text='specific location of the reference in the source text', max_length=200)), + ('terminology', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, default=[], help_text='terminology used in the source text to describe this entity', size=5)), + ('mention', models.CharField(blank=True, choices=[('direct', 'directly mentioned'), ('implied', 'implied')], help_text='how is this entity presented in the text?', max_length=32)), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('source', models.ForeignKey(help_text='the source text in which this references occurs', on_delete=django.db.models.deletion.CASCADE, to='source.source')), + ], + options={ + 'indexes': [models.Index(fields=['content_type', 'object_id'], name='source_refe_content_816b0e_idx')], + }, + ), + ] diff --git a/backend/source/models.py b/backend/source/models.py index 0c543179..a1ea67e3 100644 --- a/backend/source/models.py +++ b/backend/source/models.py @@ -1,7 +1,14 @@ from django.db import models +from django.contrib.postgres.fields import ArrayField +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType class Source(models.Model): + """ + A Source is a text of any kind. + """ + name = models.CharField( max_length=200, blank=False, @@ -15,3 +22,54 @@ class Source(models.Model): def __str__(self): return self.name + +class Reference(models.Model): + """ + References link information to sources. + + A Reference describes where and how a source refers to the information presented + in the database object. + """ + + # reference to the object + # c.f. https://docs.djangoproject.com/en/4.2/ref/contrib/contenttypes/#generic-relations + + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + + # reference to a source + + source = models.ForeignKey( + to=Source, + on_delete=models.CASCADE, + help_text="the source text in which this references occurs", + ) + + # description of the reference + + location = models.CharField( + max_length=200, + blank=True, + help_text="specific location of the reference in the source text", + ) + + terminology = ArrayField( + models.CharField( + max_length=200, + ), + default=[], + blank=True, + size=5, + help_text="terminology used in the source text to describe this entity", + ) + + mention = models.CharField( + max_length=32, + blank=True, + choices=[("direct", "directly mentioned"), ("implied", "implied")], + help_text="how is this entity presented in the text?", + ) + + class Meta: + indexes = [models.Index(fields=["content_type", "object_id"])] From 73dbcc1f2a572bcaa385757824533add13a80534 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Fri, 9 Feb 2024 14:31:49 +0100 Subject: [PATCH 14/20] add references to admin --- backend/event/admin.py | 2 ++ backend/letter/admin.py | 2 ++ backend/person/admin.py | 2 ++ backend/source/admin.py | 13 ++++++++++++- backend/source/models.py | 3 +++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/event/admin.py b/backend/event/admin.py index 428a4e78..80429ac0 100644 --- a/backend/event/admin.py +++ b/backend/event/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin +from source.admin import ReferenceInlineAdmin from . import models @@ -46,6 +47,7 @@ class LetterActionAdmin(admin.ModelAdmin): LetterActionCategoryAdmin, EventDateAdmin, RoleAdmin, + ReferenceInlineAdmin, ] exclude = ["letters"] diff --git a/backend/letter/admin.py b/backend/letter/admin.py index 75bc2eb9..b0b681d8 100644 --- a/backend/letter/admin.py +++ b/backend/letter/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin +from source.admin import ReferenceInlineAdmin from . import models @@ -37,4 +38,5 @@ class LetterAdmin(admin.ModelAdmin): LetterMaterialAdmin, LetterSenderAdmin, LetterAddresseesAdmin, + ReferenceInlineAdmin, ] diff --git a/backend/person/admin.py b/backend/person/admin.py index 23600cc1..d098972a 100644 --- a/backend/person/admin.py +++ b/backend/person/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin +from source.admin import ReferenceInlineAdmin from . import models @@ -35,6 +36,7 @@ class PersonAdmin(admin.ModelAdmin): OccupationAdmin, PersonDateOfBirthAdmin, PersonDateOfDeathAdmin, + ReferenceInlineAdmin, ] diff --git a/backend/source/admin.py b/backend/source/admin.py index d6366570..57f97210 100644 --- a/backend/source/admin.py +++ b/backend/source/admin.py @@ -1,7 +1,18 @@ from django.contrib import admin +from django.contrib.contenttypes.admin import GenericStackedInline from . import models - @admin.register(models.Source) class SourceAdmin(admin.ModelAdmin): fields = ["name", "bibliographical_info"] + + +class ReferenceInlineAdmin(GenericStackedInline): + model = models.Reference + fields = [ + "source", + "location", + "terminology", + "mention", + ] + extra = 0 diff --git a/backend/source/models.py b/backend/source/models.py index a1ea67e3..50b45f14 100644 --- a/backend/source/models.py +++ b/backend/source/models.py @@ -71,5 +71,8 @@ class Reference(models.Model): help_text="how is this entity presented in the text?", ) + def __str__(self): + return f"reference to {self.content_object} ({self.content_type.model}) in {self.source}" + class Meta: indexes = [models.Index(fields=["content_type", "object_id"])] From 166ffe298878120dd8ed14a1669bad70d8c5336f Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 9 Feb 2024 15:48:43 +0100 Subject: [PATCH 15/20] Added gift model and migrations --- backend/event/admin.py | 6 +++ .../migrations/0005_letteraction_gifts.py | 19 ++++++++ backend/event/models.py | 8 +++- backend/letter/admin.py | 13 ++++++ backend/letter/migrations/0007_gift.py | 25 +++++++++++ backend/letter/models.py | 44 +++++++++++++++++++ 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 backend/event/migrations/0005_letteraction_gifts.py create mode 100644 backend/letter/migrations/0007_gift.py diff --git a/backend/event/admin.py b/backend/event/admin.py index 78964402..04506a38 100644 --- a/backend/event/admin.py +++ b/backend/event/admin.py @@ -38,12 +38,18 @@ class LetterActionLettersAdmin(admin.StackedInline): verbose_name = "letter" verbose_name_plural = "letters" +class LetterActionGiftsAdmin(admin.StackedInline): + model = models.LetterAction.gifts.through + extra = 0 + verbose_name = "gift" + verbose_name_plural = "gifts" @admin.register(models.LetterAction) class LetterActionAdmin(admin.ModelAdmin): inlines = [ LetterActionLettersAdmin, LetterActionCategoryAdmin, + LetterActionGiftsAdmin, EventDateAdmin, RoleAdmin, ] diff --git a/backend/event/migrations/0005_letteraction_gifts.py b/backend/event/migrations/0005_letteraction_gifts.py new file mode 100644 index 00000000..9ef5edf2 --- /dev/null +++ b/backend/event/migrations/0005_letteraction_gifts.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.7 on 2024-02-09 14:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('letter', '0007_gift'), + ('event', '0004_alter_role_role'), + ] + + operations = [ + migrations.AddField( + model_name='letteraction', + name='gifts', + field=models.ManyToManyField(help_text='Gifts associated to this letter action', related_name='letter_actions', to='letter.gift'), + ), + ] diff --git a/backend/event/models.py b/backend/event/models.py index 29c1bef9..301a8cb5 100644 --- a/backend/event/models.py +++ b/backend/event/models.py @@ -3,7 +3,7 @@ from core.models import Field, LettercraftDate from case_study.models import CaseStudy from person.models import Person -from letter.models import Letter +from letter.models import Gift, Letter class EpistolaryEvent(models.Model): @@ -60,6 +60,12 @@ class LetterAction(models.Model): help_text="epistolary events this letter action belongs to", ) + gifts = models.ManyToManyField( + to=Gift, + related_name="letter_actions", + help_text="Gifts associated to this letter action", + ) + def __str__(self): categories = self.categories.all() category_names = [category.get_value_display() diff --git a/backend/letter/admin.py b/backend/letter/admin.py index 75bc2eb9..25f6844a 100644 --- a/backend/letter/admin.py +++ b/backend/letter/admin.py @@ -38,3 +38,16 @@ class LetterAdmin(admin.ModelAdmin): LetterSenderAdmin, LetterAddresseesAdmin, ] + + +class GiftLetterActionInline(admin.StackedInline): + model = models.Gift.letter_actions.through + extra = 0 + verbose_name_plural = "letter actions" + verbose_name = "relationship between a gift and an associated letter action" + + +@admin.register(models.Gift) +class GiftAdmin(admin.ModelAdmin): + fields = ["name", "description", "material", "gifted_by"] + filter_horizontal = ["letter_actions"] diff --git a/backend/letter/migrations/0007_gift.py b/backend/letter/migrations/0007_gift.py new file mode 100644 index 00000000..4b33c129 --- /dev/null +++ b/backend/letter/migrations/0007_gift.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.7 on 2024-02-09 14:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('person', '0004_persondateofdeath_persondateofbirth'), + ('letter', '0006_lettersenders_letteraddressees'), + ] + + operations = [ + migrations.CreateModel( + name='Gift', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='A short name for the gift (for identification)', max_length=256)), + ('description', models.TextField(blank=True, help_text='A longer description of the gift')), + ('material', models.CharField(choices=[('precious metal', 'precious metal'), ('textile', 'textile'), ('wood', 'wood'), ('glass', 'glass'), ('ceramic', 'ceramic'), ('animal product', 'animal product'), ('livestock', 'livestock'), ('paper', 'paper'), ('other', 'other'), ('unknown', 'unknown')], help_text='The material the gift consists of')), + ('gifted_by', models.ForeignKey(help_text='The person who gave the gift. Leave empty if unknown.', null=True, blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='gifts_given', to='person.person')), + ], + ), + ] diff --git a/backend/letter/models.py b/backend/letter/models.py index 30b17eff..f35f345f 100644 --- a/backend/letter/models.py +++ b/backend/letter/models.py @@ -4,6 +4,50 @@ from person.models import Person +class Gift(models.Model): + """ + A gift presented alongside a letter. + """ + + name = models.CharField( + max_length=256, help_text="A short name for the gift (for identification)" + ) + + description = models.TextField( + blank=True, + help_text="A longer description of the gift", + ) + + material = models.CharField( + choices=[ + ("precious metal", "precious metal"), + ("textile", "textile"), + ("wood", "wood"), + ("glass", "glass"), + ("ceramic", "ceramic"), + ("animal product", "animal product"), + ("livestock", "livestock"), + ("paper", "paper"), + ("other", "other"), + ("unknown", "unknown"), + ], + help_text="The material the gift consists of", + ) + + gifted_by = models.ForeignKey( + to=Person, + on_delete=models.CASCADE, + related_name="gifts_given", + help_text="The person who gave the gift. Leave empty if unknown.", + null=True, + blank=True, + ) + + def __str__(self): + gifter = self.gifted_by.names.first() or "unknown" + return f"{self.name} ({self.material}), gifted by {gifter}" + + class Letter(models.Model): name = models.CharField( max_length=200, From 6a1ca66adfff4a75fd3547c58fa3847e20238519 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 9 Feb 2024 16:02:16 +0100 Subject: [PATCH 16/20] Re-added date to letter actions; update tests --- backend/event/models.py | 2 +- backend/event/tests/test_event_models.py | 31 ++++++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/backend/event/models.py b/backend/event/models.py index 301a8cb5..ce2519c4 100644 --- a/backend/event/models.py +++ b/backend/event/models.py @@ -72,7 +72,7 @@ def __str__(self): for category in categories] category_desc = ", ".join(category_names) letters = ", ".join(letter.__str__() for letter in self.letters.all()) - return f"{category_desc} of {letters}" + return f"{category_desc} of {letters} ({self.date.display_date})" class LetterActionCategory(Field, models.Model): diff --git a/backend/event/tests/test_event_models.py b/backend/event/tests/test_event_models.py index 86769053..52e73afd 100644 --- a/backend/event/tests/test_event_models.py +++ b/backend/event/tests/test_event_models.py @@ -2,31 +2,30 @@ def test_letter_action_name(letter, letter_action_writing): + letter_action_writing.date.year_exact = 500 + letter_action_writing.save() action_str = str(letter_action_writing) - assert str(action_str) == f"writing of {str(letter)}" + assert str(action_str) == f"writing of {str(letter)} (500)" -def test_letter_event_date_with_exact_date(letter_action): - letter_event_date = LetterEventDate.objects.create( - letter_action=letter_action, - year_exact=500, - ) +def test_letter_event_date_with_exact_date(letter_action_reading): + letter_action_reading.date.year_exact = 500 + letter_action_reading.save() assert ( - str(letter_event_date) - == f"writing of {str(letter_action.letters.first())} (500)" + str(letter_action_reading) + == f"reading of {str(letter_action_reading.letters.first())} (500)" ) -def test_letter_event_date_with_date_range(letter_action): - letter_event_date = LetterEventDate.objects.create( - letter_action=letter_action, - year_lower=500, - year_upper=600, - ) +def test_letter_event_date_with_date_range(letter_action_reading): + letter_action_reading.date.year_lower = 500 + letter_action_reading.date.year_upper = 600 + letter_action_reading.save() + assert ( - str(letter_event_date) - == f"writing of {str(letter_action.letters.first())} (c. 500–600)" + str(letter_action_reading) + == f"reading of {str(letter_action_reading.letters.first())} (c. 500–600)" ) From 5cdf2ab405c735778342b87f5cd49ab718b4e387 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 15 Feb 2024 14:10:38 +0100 Subject: [PATCH 17/20] Recursive causal relationships between world events and epistolary events --- backend/event/admin.py | 40 +++++- ...e_worldevent_epistolary_events_and_more.py | 97 +++++++++++++++ backend/event/models.py | 115 ++++++++++++++++-- backend/event/tests/test_event_models.py | 32 ++++- 4 files changed, 266 insertions(+), 18 deletions(-) create mode 100644 backend/event/migrations/0006_remove_worldevent_epistolary_events_and_more.py diff --git a/backend/event/admin.py b/backend/event/admin.py index 04506a38..3887755c 100644 --- a/backend/event/admin.py +++ b/backend/event/admin.py @@ -77,12 +77,21 @@ class EpistolaryEventLetterActionInline(admin.StackedInline): verbose_name_plural = "letter actions" verbose_name = "relationship between a epistolary event and a letter action" + +class EpistolaryEventsTriggeredWorldEventsInline(admin.StackedInline): + model = models.EpistolaryEvent.triggered_world_events.through + fields = ["world_event", "certainty", "note"] + extra = 0 + verbose_name = "World event triggered by this epistolary event" + verbose_name_plural = "World events triggered by this epistolary event" -class WorldEventInline(admin.StackedInline): - model = models.EpistolaryEvent.triggers.through +class EpistolaryEventsTriggeredEpistolaryEventsInline(admin.StackedInline): + model = models.EpistolaryEvent.triggered_epistolary_events.through + fk_name = "triggering_epistolary_event" + fields = ["triggered_epistolary_event", "certainty", "note"] extra = 0 - verbose_name = "Triggering world events" - verbose_name_plural = "Triggering world events" + verbose_name = "Epistolary event triggered by this epistolary event" + verbose_name_plural = "Epistolary events triggered by this epistolary event" @admin.register(models.EpistolaryEvent) @@ -91,10 +100,31 @@ class EpistolaryEventAdmin(admin.ModelAdmin): inlines = [ EpistolaryEventCaseStudyInline, EpistolaryEventLetterActionInline, - WorldEventInline, + EpistolaryEventsTriggeredWorldEventsInline, + EpistolaryEventsTriggeredEpistolaryEventsInline ] +class WorldEventsTriggeredEpistolaryEventsInline(admin.StackedInline): + model = models.WorldEvent.triggered_epistolary_events.through + fields = ["epistolary_event", "certainty", "note"] + extra = 0 + verbose_name = "Epistolary event triggered by this world event" + verbose_name_plural = "Epistolary events triggered by this world event" + + +class WorldEventsTriggeredWorldEventsInline(admin.StackedInline): + model = models.WorldEvent.triggered_world_events.through + fk_name = "triggering_world_event" + fields = ["triggered_world_event", "certainty", "note"] + extra = 0 + verbose_name = "World event triggered by this world event" + verbose_name_plural = "World events triggered by this world event" + @admin.register(models.WorldEvent) class WorldEventAdmin(admin.ModelAdmin): fields = ["name", "note", "year_exact", "year_lower", "year_upper"] + inlines = [ + WorldEventsTriggeredEpistolaryEventsInline, + WorldEventsTriggeredWorldEventsInline + ] diff --git a/backend/event/migrations/0006_remove_worldevent_epistolary_events_and_more.py b/backend/event/migrations/0006_remove_worldevent_epistolary_events_and_more.py new file mode 100644 index 00000000..362d5320 --- /dev/null +++ b/backend/event/migrations/0006_remove_worldevent_epistolary_events_and_more.py @@ -0,0 +1,97 @@ +# Generated by Django 4.2.7 on 2024-02-15 13:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0005_letteraction_gifts'), + ] + + operations = [ + migrations.RemoveField( + model_name='worldevent', + name='epistolary_events', + ), + migrations.AddField( + model_name='epistolaryevent', + name='triggered_world_events', + field=models.ManyToManyField(help_text='World events triggered by this epistolary event', related_name='world_event_triggers', through='event.EpistolaryEventTrigger', to='event.worldevent'), + ), + migrations.AddField( + model_name='epistolaryeventtrigger', + name='certainty', + field=models.IntegerField(choices=[(0, 'uncertain'), (1, 'somewhat certain'), (2, 'certain')], default=2, help_text='How certain are you of this value?'), + ), + migrations.AddField( + model_name='epistolaryeventtrigger', + name='note', + field=models.TextField(blank=True, help_text='Additional notes'), + ), + migrations.AlterField( + model_name='epistolaryeventtrigger', + name='epistolary_event', + field=models.ForeignKey(help_text='The epistolary event that triggered a world event', on_delete=django.db.models.deletion.CASCADE, related_name='triggers_by_epistolary_events', to='event.epistolaryevent'), + ), + migrations.AlterField( + model_name='epistolaryeventtrigger', + name='world_event', + field=models.ForeignKey(help_text='The world event that was triggered by an epistolary event', on_delete=django.db.models.deletion.CASCADE, related_name='triggers_for_world_events', to='event.worldevent'), + ), + migrations.CreateModel( + name='WorldEventTrigger', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('certainty', models.IntegerField(choices=[(0, 'uncertain'), (1, 'somewhat certain'), (2, 'certain')], default=2, help_text='How certain are you of this value?')), + ('note', models.TextField(blank=True, help_text='Additional notes')), + ('epistolary_event', models.ForeignKey(help_text='The epistolary event that was triggered by a world event', on_delete=django.db.models.deletion.CASCADE, related_name='triggers_by_world_events', to='event.epistolaryevent')), + ('world_event', models.ForeignKey(help_text='The world event that triggered an epistolary event', on_delete=django.db.models.deletion.CASCADE, related_name='triggers_for_epistolary_events', to='event.worldevent')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='WorldEventSelfTrigger', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('certainty', models.IntegerField(choices=[(0, 'uncertain'), (1, 'somewhat certain'), (2, 'certain')], default=2, help_text='How certain are you of this value?')), + ('note', models.TextField(blank=True, help_text='Additional notes')), + ('triggered_world_event', models.ForeignKey(help_text='The world event that was triggered by another world event', on_delete=django.db.models.deletion.CASCADE, related_name='self_triggers_for_world_events', to='event.worldevent')), + ('triggering_world_event', models.ForeignKey(help_text='The world event that triggered another world event', on_delete=django.db.models.deletion.CASCADE, related_name='self_triggered_by_world_events', to='event.worldevent')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='EpistolaryEventSelfTrigger', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('certainty', models.IntegerField(choices=[(0, 'uncertain'), (1, 'somewhat certain'), (2, 'certain')], default=2, help_text='How certain are you of this value?')), + ('note', models.TextField(blank=True, help_text='Additional notes')), + ('triggered_epistolary_event', models.ForeignKey(help_text='The epistolary event that was triggered by another epistolary event', on_delete=django.db.models.deletion.CASCADE, related_name='self_triggers_for_epistolary_events', to='event.epistolaryevent')), + ('triggering_epistolary_event', models.ForeignKey(help_text='The epistolary event that triggered another epistolary event', on_delete=django.db.models.deletion.CASCADE, related_name='self_triggered_by_epistolary_events', to='event.epistolaryevent')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='epistolaryevent', + name='triggered_epistolary_events', + field=models.ManyToManyField(help_text='Other epistolary events triggered by this epistolary event', through='event.EpistolaryEventSelfTrigger', to='event.epistolaryevent'), + ), + migrations.AddField( + model_name='worldevent', + name='triggered_epistolary_events', + field=models.ManyToManyField(help_text='Epistolary events triggered by this world event', related_name='epistolary_event_triggers', through='event.WorldEventTrigger', to='event.epistolaryevent'), + ), + migrations.AddField( + model_name='worldevent', + name='triggered_world_events', + field=models.ManyToManyField(help_text='Other world events triggered by this world event', through='event.WorldEventSelfTrigger', to='event.worldevent'), + ), + ] diff --git a/backend/event/models.py b/backend/event/models.py index ce2519c4..cb0733c4 100644 --- a/backend/event/models.py +++ b/backend/event/models.py @@ -31,6 +31,22 @@ class EpistolaryEvent(models.Model): help_text="Additional notes that describe the event and what connects the letter actions it comprises.", ) + triggered_world_events = models.ManyToManyField( + to="WorldEvent", + through="EpistolaryEventTrigger", + symmetrical=False, + related_name="world_event_triggers", + help_text="World events triggered by this epistolary event", + ) + + triggered_epistolary_events = models.ManyToManyField( + to="self", + through="EpistolaryEventSelfTrigger", + through_fields=("triggering_epistolary_event", "triggered_epistolary_event"), + symmetrical=False, + help_text="Other epistolary events triggered by this epistolary event", + ) + def __str__(self): return f"{self.name}" @@ -68,11 +84,13 @@ class LetterAction(models.Model): def __str__(self): categories = self.categories.all() - category_names = [category.get_value_display() - for category in categories] + category_names = [category.get_value_display() for category in categories] category_desc = ", ".join(category_names) letters = ", ".join(letter.__str__() for letter in self.letters.all()) - return f"{category_desc} of {letters} ({self.date.display_date})" + display_date = ( + self.date.display_date if hasattr(self, "date") else "unknown date" + ) + return f"{category_desc} of {letters} ({display_date})" class LetterActionCategory(Field, models.Model): @@ -187,32 +205,113 @@ class WorldEvent(LettercraftDate, models.Model): help_text="Additional notes that describe the event and its relevance to the letters.", ) - epistolary_events = models.ManyToManyField( + triggered_epistolary_events = models.ManyToManyField( to=EpistolaryEvent, - through="EpistolaryEventTrigger", - related_name="triggers", + through="WorldEventTrigger", + symmetrical=False, + related_name="epistolary_event_triggers", + help_text="Epistolary events triggered by this world event", + ) + + triggered_world_events = models.ManyToManyField( + to="self", + through="WorldEventSelfTrigger", + through_fields=("triggering_world_event", "triggered_world_event"), + symmetrical=False, + help_text="Other world events triggered by this world event", ) def __str__(self): return f"{self.name} ({self.display_date})" -class EpistolaryEventTrigger(models.Model): +class WorldEventTrigger(Field, models.Model): """ - A relationship between an epistolary event and a world event that triggered it. + A relationship between an epistolary event and a world event where a world event triggers an epistolary event. """ epistolary_event = models.ForeignKey( to=EpistolaryEvent, on_delete=models.CASCADE, + related_name="triggers_by_world_events", help_text="The epistolary event that was triggered by a world event", ) world_event = models.ForeignKey( to=WorldEvent, on_delete=models.CASCADE, + related_name="triggers_for_epistolary_events", help_text="The world event that triggered an epistolary event", ) def __str__(self): return f"{self.world_event} triggered {self.epistolary_event}" + + +class EpistolaryEventTrigger(Field, models.Model): + """ + A relationship between an epistolary event and a world event where an epistolary event triggers a world event. + """ + + epistolary_event = models.ForeignKey( + to=EpistolaryEvent, + on_delete=models.CASCADE, + related_name="triggers_by_epistolary_events", + help_text="The epistolary event that triggered a world event", + ) + + world_event = models.ForeignKey( + to=WorldEvent, + on_delete=models.CASCADE, + related_name="triggers_for_world_events", + help_text="The world event that was triggered by an epistolary event", + ) + + def __str__(self): + return f"{self.epistolary_event} triggered {self.world_event}" + + +class WorldEventSelfTrigger(Field, models.Model): + """ + A relationship between a world event and another world event where one world event triggers another. + """ + + triggered_world_event = models.ForeignKey( + to=WorldEvent, + on_delete=models.CASCADE, + related_name="self_triggers_for_world_events", + help_text="The world event that was triggered by another world event", + ) + + triggering_world_event = models.ForeignKey( + to=WorldEvent, + on_delete=models.CASCADE, + related_name="self_triggered_by_world_events", + help_text="The world event that triggered another world event", + ) + + def __str__(self): + return f"{self.triggering_world_event} triggered {self.triggered_world_event}" + + +class EpistolaryEventSelfTrigger(Field, models.Model): + """ + A relationship between an epistolary event and another epistolary event where one epistolary event triggers another. + """ + + triggered_epistolary_event = models.ForeignKey( + to=EpistolaryEvent, + on_delete=models.CASCADE, + related_name="self_triggers_for_epistolary_events", + help_text="The epistolary event that was triggered by another epistolary event", + ) + + triggering_epistolary_event = models.ForeignKey( + to=EpistolaryEvent, + on_delete=models.CASCADE, + related_name="self_triggered_by_epistolary_events", + help_text="The epistolary event that triggered another epistolary event", + ) + + def __str__(self): + return f"{self.triggering_epistolary_event} triggered {self.triggered_epistolary_event}" diff --git a/backend/event/tests/test_event_models.py b/backend/event/tests/test_event_models.py index 52e73afd..0e72e23d 100644 --- a/backend/event/tests/test_event_models.py +++ b/backend/event/tests/test_event_models.py @@ -1,4 +1,9 @@ -from event.models import LetterEventDate +from event.models import ( + EpistolaryEvent, + WorldEvent, + WorldEventSelfTrigger, + WorldEventTrigger, +) def test_letter_action_name(letter, letter_action_writing): @@ -35,10 +40,27 @@ def test_world_event(world_event): assert str(world_event) == "Test World Event (500)" -def test_world_event_trigger(world_event, epistolary_event): - world_event.epistolary_events.add(epistolary_event) +def test_world_event_triggers_epistolary_event(world_event, epistolary_event): + world_event.triggered_epistolary_events.add(epistolary_event) world_event.save() - - trigger = world_event.epistolary_events.through.objects.first() + trigger = world_event.triggered_epistolary_events.through.objects.first() assert str(trigger) == "Test World Event (612) triggered Test Epistolary event" + + +def test_world_event_triggers_world_event(world_event): + world_event_2 = WorldEvent.objects.create(name="Test World Event 2", year_exact=700) + world_event_2.save() + world_event.triggered_world_events.add(world_event_2) + trigger = world_event.triggered_world_events.through.objects.first() + + assert str(trigger) == "Test World Event (612) triggered Test World Event 2 (700)" + + +def test_epistolary_event_triggers_epistolary_event(epistolary_event): + epistolary_event_2 = EpistolaryEvent.objects.create(name="Test Epistolary event 2") + epistolary_event_2.save() + epistolary_event.triggered_epistolary_events.add(epistolary_event_2) + trigger = epistolary_event.triggered_epistolary_events.through.objects.first() + + assert str(trigger) == "Test Epistolary event triggered Test Epistolary event 2" From 23890e8d14e5723f35c3cee45f3d6f611e494dda Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 15 Feb 2024 14:52:27 +0100 Subject: [PATCH 18/20] Nullable gifts on letter actions; gifter_name bug fix --- backend/event/admin.py | 1 + .../0007_alter_letteraction_gifts.py | 19 +++++++++++++++++++ backend/event/models.py | 19 ++++++++++++++----- backend/letter/models.py | 6 ++++-- 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 backend/event/migrations/0007_alter_letteraction_gifts.py diff --git a/backend/event/admin.py b/backend/event/admin.py index 3887755c..c636a31b 100644 --- a/backend/event/admin.py +++ b/backend/event/admin.py @@ -46,6 +46,7 @@ class LetterActionGiftsAdmin(admin.StackedInline): @admin.register(models.LetterAction) class LetterActionAdmin(admin.ModelAdmin): + list_display=["description", "display_date"] inlines = [ LetterActionLettersAdmin, LetterActionCategoryAdmin, diff --git a/backend/event/migrations/0007_alter_letteraction_gifts.py b/backend/event/migrations/0007_alter_letteraction_gifts.py new file mode 100644 index 00000000..d196b1ea --- /dev/null +++ b/backend/event/migrations/0007_alter_letteraction_gifts.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.7 on 2024-02-15 13:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('letter', '0007_gift'), + ('event', '0006_remove_worldevent_epistolary_events_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='letteraction', + name='gifts', + field=models.ManyToManyField(blank=True, help_text='Gifts associated to this letter action', related_name='letter_actions', to='letter.gift'), + ), + ] diff --git a/backend/event/models.py b/backend/event/models.py index cb0733c4..69903ee3 100644 --- a/backend/event/models.py +++ b/backend/event/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.contrib import admin from core.models import Field, LettercraftDate from case_study.models import CaseStudy @@ -80,17 +81,25 @@ class LetterAction(models.Model): to=Gift, related_name="letter_actions", help_text="Gifts associated to this letter action", + blank=True, ) - def __str__(self): + @property + @admin.display(description="Date") + def display_date(self): + return self.date.display_date if hasattr(self, "date") else "unknown date" + + @property + @admin.display(description="Description") + def description(self): categories = self.categories.all() category_names = [category.get_value_display() for category in categories] category_desc = ", ".join(category_names) letters = ", ".join(letter.__str__() for letter in self.letters.all()) - display_date = ( - self.date.display_date if hasattr(self, "date") else "unknown date" - ) - return f"{category_desc} of {letters} ({display_date})" + return f"{category_desc} of {letters}" + + def __str__(self): + return f"{self.description} ({self.display_date})" class LetterActionCategory(Field, models.Model): diff --git a/backend/letter/models.py b/backend/letter/models.py index f35f345f..aca0ce25 100644 --- a/backend/letter/models.py +++ b/backend/letter/models.py @@ -44,8 +44,10 @@ class Gift(models.Model): ) def __str__(self): - gifter = self.gifted_by.names.first() or "unknown" - return f"{self.name} ({self.material}), gifted by {gifter}" + gifter_name = ( + self.gifted_by.names.first() if self.gifted_by is not None else "unknown" + ) + return f"{self.name} ({self.material}), gifted by {gifter_name}" class Letter(models.Model): From 90428628c965832c1f03215427b997212099d269 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Fri, 16 Feb 2024 11:55:30 +0100 Subject: [PATCH 19/20] review suggestions --- ...cation_alter_reference_mention_and_more.py | 35 +++++++++++++++++++ backend/source/models.py | 15 ++++---- 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 backend/source/migrations/0003_alter_reference_location_alter_reference_mention_and_more.py diff --git a/backend/source/migrations/0003_alter_reference_location_alter_reference_mention_and_more.py b/backend/source/migrations/0003_alter_reference_location_alter_reference_mention_and_more.py new file mode 100644 index 00000000..77fda39b --- /dev/null +++ b/backend/source/migrations/0003_alter_reference_location_alter_reference_mention_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.7 on 2024-02-16 10:55 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('source', '0002_reference'), + ] + + operations = [ + migrations.AlterField( + model_name='reference', + name='location', + field=models.CharField(blank=True, help_text='Specific location of the reference in the source text', max_length=200), + ), + migrations.AlterField( + model_name='reference', + name='mention', + field=models.CharField(blank=True, choices=[('direct', 'directly mentioned'), ('implied', 'implied')], help_text='How is this information presented in the text?', max_length=32), + ), + migrations.AlterField( + model_name='reference', + name='source', + field=models.ForeignKey(help_text='The source text in which this references occurs', on_delete=django.db.models.deletion.CASCADE, to='source.source'), + ), + migrations.AlterField( + model_name='reference', + name='terminology', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, default=list, help_text='Terminology used in the source text to describe this entity', size=5), + ), + ] diff --git a/backend/source/models.py b/backend/source/models.py index 50b45f14..81221f92 100644 --- a/backend/source/models.py +++ b/backend/source/models.py @@ -43,7 +43,7 @@ class Reference(models.Model): source = models.ForeignKey( to=Source, on_delete=models.CASCADE, - help_text="the source text in which this references occurs", + help_text="The source text in which this references occurs", ) # description of the reference @@ -51,28 +51,31 @@ class Reference(models.Model): location = models.CharField( max_length=200, blank=True, - help_text="specific location of the reference in the source text", + help_text="Specific location of the reference in the source text", ) terminology = ArrayField( models.CharField( max_length=200, ), - default=[], + default=list, blank=True, size=5, - help_text="terminology used in the source text to describe this entity", + help_text="Terminology used in the source text to describe this entity", ) mention = models.CharField( max_length=32, blank=True, choices=[("direct", "directly mentioned"), ("implied", "implied")], - help_text="how is this entity presented in the text?", + help_text="How is this information presented in the text?", ) def __str__(self): - return f"reference to {self.content_object} ({self.content_type.model}) in {self.source}" + object = f"{self.content_object} ({self.content_type.model})" + source = f"{self.source}" + loc = f" ({self.location})" if self.location else "" + return f"reference to {object} in {source}{loc}" class Meta: indexes = [models.Index(fields=["content_type", "object_id"])] From bbd2f8a69c3040efb35e7aabcd2957b963ff559e Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 21 Feb 2024 18:00:07 +0100 Subject: [PATCH 20/20] update version number --- CITATION.cff | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 82e7b37f..d7302252 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,4 +8,6 @@ authors: - name: 'Research Software Lab, Centre for Digital Humanities, Utrecht University' website: 'https://cdh.uu.nl/rsl/' repository-code: 'https://github.com/CentreForDigitalHumanities/lettercraft' -license: BSD-3 +license: BSD-3-Clause +version: 0.1.0 +date-released: '2024-02-21' diff --git a/package.json b/package.json index 46fec7c7..8b1409ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lettercraft", - "version": "0.0.0", + "version": "0.1.0", "description": "Lettercraft & Epistolary Performance in Medieval Europe", "author": "UU Centre for Digital Humanities - Research Software Lab", "license": "BSD-3-Clause",