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 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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)