From 4408c9575a054a4f6dc189727c9a38bb516820ab Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:45:41 +0200 Subject: [PATCH 1/6] model --- tapir/accounts/models.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tapir/accounts/models.py b/tapir/accounts/models.py index 6a07900f2..1d08b87f0 100644 --- a/tapir/accounts/models.py +++ b/tapir/accounts/models.py @@ -305,3 +305,21 @@ class Meta: fields=["user", "mail_id"], name="user-mail-constraint" ) ] + + +class CoPurchaser(models.Model): + user = models.ForeignKey( + "TapirUser", on_delete=models.CASCADE, related_name="copurchaser" + ) + first_name = models.CharField(blank=True, max_length=255) + last_name = models.CharField(blank=True, max_length=255) + email = models.EmailField(blank=True, max_length=254) + order = models.PositiveIntegerField(default=0) + + class Meta: + verbose_name_plural = _("Co-Purchasers") + verbose_name = _("Co-Purchaser") + ordering = ["order"] + + def get_full_name(self): + return f"{self.first_name} {self.last_name}" From aea8ce2b53f63775577725a576c14aff77317b99 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:58:23 +0200 Subject: [PATCH 2/6] CO_PURCHASER_MAX_CO_PURCHASERS --- tapir/accounts/models.py | 8 ++++++++ tapir/settings.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/tapir/accounts/models.py b/tapir/accounts/models.py index 1d08b87f0..527fec42c 100644 --- a/tapir/accounts/models.py +++ b/tapir/accounts/models.py @@ -323,3 +323,11 @@ class Meta: def get_full_name(self): return f"{self.first_name} {self.last_name}" + + def clean(self): + max_co_purchasers = getattr(settings, "CO_PURCHASER_MAX_CO_PURCHASERS", 2) + + if self.pk is None: + existing_count = CoPurchaser.objects.filter(user=self.user).count() + if existing_count >= max_co_purchasers: + raise ValidationError(_("Maximum numbers of Co-Purchasers exceeded")) diff --git a/tapir/settings.py b/tapir/settings.py index c994200cc..513446bf5 100644 --- a/tapir/settings.py +++ b/tapir/settings.py @@ -436,3 +436,5 @@ SUBDIV_FOR_HOLIDAYS_AUTO_CANCEL = env.str( "SUBDIV_FOR_HOLIDAYS_AUTO_CANCEL", default="BE" ) + +CO_PURCHASER_MAX_CO_PURCHASERS = 2 From 06c330a96077cb86aa19929088d529a3802535c1 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 26 Apr 2026 19:58:57 +0200 Subject: [PATCH 3/6] migration and user_detail.html --- tapir/accounts/migrations/0024_copurchaser.py | 49 ++++++++++++++++++ .../migrations/0025_co_purchaser_model.py | 50 +++++++++++++++++++ .../templates/accounts/user_detail.html | 25 +++++++++- 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tapir/accounts/migrations/0024_copurchaser.py create mode 100644 tapir/accounts/migrations/0025_co_purchaser_model.py diff --git a/tapir/accounts/migrations/0024_copurchaser.py b/tapir/accounts/migrations/0024_copurchaser.py new file mode 100644 index 000000000..da566d0f2 --- /dev/null +++ b/tapir/accounts/migrations/0024_copurchaser.py @@ -0,0 +1,49 @@ +# Generated by Django 5.2.13 on 2026-04-26 17:01 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "accounts", + "0023_updatetapiruserlogentry_accounts_up_old_val_25b95f_gin_and_more", + ), + ] + + operations = [ + migrations.CreateModel( + name="CoPurchaser", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("first_name", models.CharField(blank=True, max_length=255)), + ("last_name", models.CharField(blank=True, max_length=255)), + ("email", models.EmailField(blank=True, max_length=254)), + ("order", models.PositiveIntegerField(default=0)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="copurchaser", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "Co-Purchaser", + "verbose_name_plural": "Co-Purchasers", + "ordering": ["order"], + }, + ), + ] diff --git a/tapir/accounts/migrations/0025_co_purchaser_model.py b/tapir/accounts/migrations/0025_co_purchaser_model.py new file mode 100644 index 000000000..edcb442b1 --- /dev/null +++ b/tapir/accounts/migrations/0025_co_purchaser_model.py @@ -0,0 +1,50 @@ +# Generated by Django 5.2.13 on 2026-04-26 17:02 + +from django.db import migrations + + +def split_name(full_name): + if not full_name: + return "", "" + parts = full_name.strip().split() + if len(parts) == 1: + return parts[0], "" + else: + return " ".join(parts[:-1]), parts[-1] + + +def migrate_co_purchasers(apps, schema_editor): + TapirUser = apps.get_model("accounts", "TapirUser") + CoPurchaser = apps.get_model("accounts", "CoPurchaser") + + for user in TapirUser.objects.all(): + + if user.co_purchaser: + first_name, last_name = split_name(user.co_purchaser) + CoPurchaser.objects.create( + user=user, + first_name=first_name, + last_name=last_name, + email=user.co_purchaser_mail, + order=0, + ) + if user.co_purchaser_2: + first_name, last_name = split_name(user.co_purchaser_2) + CoPurchaser.objects.create( + user=user, + first_name=first_name, + last_name=last_name, + email=user.co_purchaser_2_mail, + order=1, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0024_copurchaser"), + ] + + operations = [ + migrations.RunPython(migrate_co_purchasers), + ] diff --git a/tapir/accounts/templates/accounts/user_detail.html b/tapir/accounts/templates/accounts/user_detail.html index 27602e3fb..ea60f63b9 100644 --- a/tapir/accounts/templates/accounts/user_detail.html +++ b/tapir/accounts/templates/accounts/user_detail.html @@ -153,7 +153,30 @@
{% translate "No co-purchasers registered." %}
+ {% endif %} + {% endwith %} +{% translate "No co-purchasers registered." %}
diff --git a/tapir/accounts/urls.py b/tapir/accounts/urls.py index f576ba261..97ec3b287 100644 --- a/tapir/accounts/urls.py +++ b/tapir/accounts/urls.py @@ -60,6 +60,11 @@ views.CoPurchaserCreateView.as_view(), name="co_purchaser_create", ), + path( + "user/