diff --git a/contracts/admin.py b/contracts/admin.py index 1eb707c2867e341a0bb44c6f9b8e6607d989a431..64cc7eb142d381d6705c2111e9da99655316eaae 100644 --- a/contracts/admin.py +++ b/contracts/admin.py @@ -2,12 +2,13 @@ from django.contrib import admin from shared.admin import MarkdownxGuardedModelAdmin -from .forms import ContractAdminForm +from .forms import ContractAdminForm, SigneeAdminForm from .models import ( Contract, Contractee, ContracteeRepresentative, ContracteeSignature, + ContractFile, ContractFilingArea, ContractIntent, ContractIssue, @@ -23,13 +24,31 @@ class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin): return False +# BEGIN Contracts + + +class ContractFileInline(admin.TabularInline): + model = ContractFile + extra = 0 + + +class ContractIntentInline(admin.TabularInline): + model = ContractIntent + extra = 0 + + +class ContractIssueInline(admin.TabularInline): + model = Contract.issues.through + extra = 0 + + class ContractAdmin(MarkdownxGuardedModelAdmin): form = ContractAdminForm fields = ( "type", "subtype", - "signee_signature", + "signee_signatures", "contractee_signatures", "valid_start_date", "valid_end_date", @@ -39,11 +58,8 @@ class ContractAdmin(MarkdownxGuardedModelAdmin): "publishing_rejection_comment", "tender_url", "identifier", - "issues", "notes", "summary", - "anonymized_contract_file", - "original_contract_file", "primary_contract", "expected_cost_total", "expected_cost_year", @@ -53,23 +69,62 @@ class ContractAdmin(MarkdownxGuardedModelAdmin): "filing_area", ) + inlines = ( + ContractFileInline, + ContractIntentInline, + ContractIssueInline, + ) + + +# END Contracts + +# BEGIN Signing parties + + +class SigneeRepresentativeInline(admin.TabularInline): + model = SigneeRepresentative + extra = 0 + + +class SigneeAdmin(MarkdownxGuardedModelAdmin): + form = SigneeAdminForm + + inlines = ( + SigneeRepresentativeInline, + ) + + +class ContracteeRepresentativeInline(admin.TabularInline): + model = ContracteeRepresentative + extra = 0 + + +class ContracteeAdmin(MarkdownxGuardedModelAdmin): + inlines = ( + ContracteeRepresentativeInline, + ) + + +# END Signing parties + for model in ( SigneeRepresentative, - SigneeSignature, ContracteeRepresentative, - ContracteeSignature, ContractSubtype, ContractIntent, ): admin.site.register(model, IndexHiddenModelAdmin) for model in ( - Signee, - Contractee, + SigneeSignature, + ContracteeSignature, ContractIssue, ContractFilingArea, ): admin.site.register(model, MarkdownxGuardedModelAdmin) +admin.site.register(Signee, SigneeAdmin) +admin.site.register(Contractee, ContracteeAdmin) + admin.site.register(Contract, ContractAdmin) diff --git a/contracts/forms.py b/contracts/forms.py index f5d1c5aba5672f7faecf17f1d0e4bf5cb6f6b449..f6c1dc01e57493ddf61c1fd9510889ac6a2f3e8e 100644 --- a/contracts/forms.py +++ b/contracts/forms.py @@ -9,3 +9,12 @@ class ContractAdminForm(forms.ModelForm): "shared/shared.js", "shared/admin_contract_form.js", ) + + +class SigneeAdminForm(forms.ModelForm): + class Media: + js = ( + "shared/runtime.js", + "shared/shared.js", + "shared/admin_signee_form.js", + ) diff --git a/contracts/migrations/0001_initial.py b/contracts/migrations/0001_initial.py index fb189ca67c8f7d6faa7cbf673deb88847835635f..466d34d134c7ace29d0b426660db2de08b0323b5 100644 --- a/contracts/migrations/0001_initial.py +++ b/contracts/migrations/0001_initial.py @@ -1,10 +1,11 @@ -# Generated by Django 4.1.4 on 2023-02-15 14:24 +# Generated by Django 4.1.4 on 2023-02-15 17:12 from django.conf import settings from django.db import migrations, models import django.db.models.deletion import django_countries.fields import markdownx.models +import shared.models class Migration(migrations.Migration): @@ -55,7 +56,7 @@ class Migration(migrations.Migration): ('address_country', django_countries.fields.CountryField(default='CZ', max_length=2, verbose_name='Země')), ('ico_number', models.CharField(blank=True, default='71339698', max_length=16, null=True, verbose_name='IČO')), ('department', models.CharField(blank=True, max_length=128, null=True, verbose_name='Organizační složka')), - ('color', models.CharField(max_length=6, verbose_name='Barva')), + ('color', models.CharField(blank=True, max_length=6, null=True, verbose_name='Barva')), ], options={ 'verbose_name': 'Naše smluvní strana', @@ -73,6 +74,7 @@ class Migration(migrations.Migration): 'verbose_name': 'Spisovna', 'verbose_name_plural': 'Spisovny', }, + bases=(shared.models.NameStrMixin, models.Model), ), migrations.CreateModel( name='ContractIssue', @@ -84,6 +86,7 @@ class Migration(migrations.Migration): 'verbose_name': 'Problém se smlouvou', 'verbose_name_plural': 'Problémy se smlouvami', }, + bases=(shared.models.NameStrMixin, models.Model), ), migrations.CreateModel( name='ContractSubtype', @@ -95,6 +98,7 @@ class Migration(migrations.Migration): 'verbose_name': 'Podtyp smlouvy', 'verbose_name_plural': 'Podtypy smlouvy', }, + bases=(shared.models.NameStrMixin, models.Model), ), migrations.CreateModel( name='Signee', @@ -109,10 +113,11 @@ class Migration(migrations.Migration): ('ico_number', models.CharField(blank=True, max_length=16, null=True, verbose_name='IČO')), ('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Datum narození')), ('department', models.CharField(blank=True, max_length=128, null=True, verbose_name='Organizační složka')), + ('color', models.CharField(blank=True, max_length=6, null=True, verbose_name='Barva')), ], options={ - 'verbose_name': 'Druhá smluvní strana', - 'verbose_name_plural': 'Druhé smluvní strany', + 'verbose_name': 'Jiná smluvní strana', + 'verbose_name_plural': 'Ostatní smluvní strany', }, ), migrations.CreateModel( @@ -123,8 +128,8 @@ class Migration(migrations.Migration): ('signee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='signatures', to='contracts.signee', verbose_name='Smluvní strana')), ], options={ - 'verbose_name': 'Podpis druhé smluvní strany', - 'verbose_name_plural': 'Podpisy druhé smluvní strany', + 'verbose_name': 'Podpis jiné smluvní strany', + 'verbose_name_plural': 'Podpisy ostatních smluvních stran', }, ), migrations.CreateModel( @@ -132,12 +137,13 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=256, verbose_name='Jméno')), - ('role', models.CharField(max_length=256, verbose_name='Funkce')), + ('function', models.CharField(blank=True, max_length=256, null=True, verbose_name='Funkce')), + ('role', models.CharField(blank=True, max_length=256, null=True, verbose_name='Role')), ('signee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='contracts.signee', verbose_name='Smluvní strana')), ], options={ - 'verbose_name': 'Zástupce druhé smluvní strany', - 'verbose_name_plural': 'Zástupci druhé smluvní strany', + 'verbose_name': 'Zástupce', + 'verbose_name_plural': 'Zástupci', }, ), migrations.CreateModel( @@ -165,6 +171,7 @@ class Migration(migrations.Migration): 'verbose_name': 'Soubor', 'verbose_name_plural': 'Soubory', }, + bases=(shared.models.NameStrMixin, models.Model), ), migrations.CreateModel( name='ContracteeSignature', @@ -175,7 +182,7 @@ class Migration(migrations.Migration): ], options={ 'verbose_name': 'Podpis naší smluvní strany', - 'verbose_name_plural': 'Podpisy naší smluvní strany', + 'verbose_name_plural': 'Podpisy našich smluvních stran', }, ), migrations.CreateModel( @@ -183,18 +190,19 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=256, verbose_name='Jméno')), - ('role', models.CharField(max_length=256, verbose_name='Funkce')), + ('function', models.CharField(blank=True, max_length=256, null=True, verbose_name='Funkce')), + ('role', models.CharField(blank=True, max_length=256, null=True, verbose_name='Role')), ('contractee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='contracts.contractee', verbose_name='Smluvní strana')), ], options={ - 'verbose_name': 'Zástupce naší smluvní strany', - 'verbose_name_plural': 'Zástupci naší smluvní strany', + 'verbose_name': 'Zástupce', + 'verbose_name_plural': 'Zástupci', }, ), migrations.AddField( model_name='contract', name='contractee_signatures', - field=models.ManyToManyField(to='contracts.contracteesignature', verbose_name='Naše podpisy'), + field=models.ManyToManyField(to='contracts.contracteesignature', verbose_name='Podpisy našich smluvních stran'), ), migrations.AddField( model_name='contract', @@ -218,8 +226,8 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='contract', - name='signee_signature', - field=models.ManyToManyField(to='contracts.signeesignature', verbose_name='Podpisy druhé smluvní strany'), + name='signee_signatures', + field=models.ManyToManyField(to='contracts.signeesignature', verbose_name='Podpisy ostatních smluvních stran'), ), migrations.AddField( model_name='contract', diff --git a/contracts/migrations/0002_alter_contract_all_parties_sign_date_and_more.py b/contracts/migrations/0002_alter_contract_all_parties_sign_date_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..463b58e98d3b4b3b7f94c6c48a609cbff7549eaf --- /dev/null +++ b/contracts/migrations/0002_alter_contract_all_parties_sign_date_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.1.4 on 2023-02-15 17:19 + +from django.db import migrations, models +import markdownx.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='contract', + name='all_parties_sign_date', + field=models.DateField(blank=True, null=True, verbose_name='Datum podpisu všech stran'), + ), + migrations.AlterField( + model_name='contract', + name='notes', + field=markdownx.models.MarkdownxField(blank=True, help_text='Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat.', null=True, verbose_name='Poznámky'), + ), + migrations.AlterField( + model_name='contract', + name='summary', + field=markdownx.models.MarkdownxField(blank=True, help_text='Obsah není veřejně přístupný.', null=True, verbose_name='Rekapitulace'), + ), + ] diff --git a/contracts/migrations/0003_alter_contract_public_status_set_by.py b/contracts/migrations/0003_alter_contract_public_status_set_by.py new file mode 100644 index 0000000000000000000000000000000000000000..b662563116ef5fb2d2c0e87b10a24d9e9c25ac9a --- /dev/null +++ b/contracts/migrations/0003_alter_contract_public_status_set_by.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.4 on 2023-02-15 17:21 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contracts', '0002_alter_contract_all_parties_sign_date_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='contract', + name='public_status_set_by', + field=models.ForeignKey(blank=True, help_text='Obsah není veřejně přístupný.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='public_status_altered_contracts', to=settings.AUTH_USER_MODEL, verbose_name='Zveřejněno / nezveřejněno uživatelem'), + ), + ] diff --git a/contracts/models.py b/contracts/models.py index 10bcbdec50e12ac61452e6e4f80ad1cd9ce66335..49ab18d1ad506811b27da707f3298a4ad20bbfbe 100644 --- a/contracts/models.py +++ b/contracts/models.py @@ -4,6 +4,7 @@ from django_countries.fields import CountryField from markdownx.models import MarkdownxField from users.models import User +from shared.models import NameStrMixin class Signee(models.Model): @@ -58,9 +59,30 @@ class Signee(models.Model): verbose_name="Organizační složka", ) + color = models.CharField( + max_length=6, # e.g. "ffffff" + blank=True, + null=True, + verbose_name="Barva", + ) + class Meta: - verbose_name = "Druhá smluvní strana" - verbose_name_plural = "Druhé smluvní strany" + verbose_name = "Jiná smluvní strana" + verbose_name_plural = "Ostatní smluvní strany" + + def __str__(self) -> str: + result = self.name + + if self.ico_number is not None: + result += f" ({self.ico_number})" + + if self.date_of_birth is not None: + result += f" ({self.date_of_birth})" + + if self.department is not None: + result += f", {self.department}" + + return result class SigneeRepresentative(models.Model): @@ -76,14 +98,34 @@ class SigneeRepresentative(models.Model): verbose_name="Jméno", ) - role = models.CharField( + function = models.CharField( max_length=256, + blank=True, + null=True, verbose_name="Funkce", ) + role = models.CharField( + max_length=256, + blank=True, + null=True, + verbose_name="Role", + ) + class Meta: - verbose_name = "Zástupce druhé smluvní strany" - verbose_name_plural = "Zástupci druhé smluvní strany" + verbose_name = "Zástupce" + verbose_name_plural = "Zástupci" + + def __str__(self) -> str: + result = self.name + + if self.function is not None: + result += f", {self.function}" + + if self.role is not None: + result += f" ({self.role})" + + return result class SigneeSignature(models.Model): @@ -99,8 +141,11 @@ class SigneeSignature(models.Model): ) class Meta: - verbose_name = "Podpis druhé smluvní strany" - verbose_name_plural = "Podpisy druhé smluvní strany" + verbose_name = "Podpis jiné smluvní strany" + verbose_name_plural = "Podpisy ostatních smluvních stran" + + def __str__(self) -> str: + return f"{self.signee.name}, {self.date}" class Contractee(models.Model): @@ -151,6 +196,8 @@ class Contractee(models.Model): # TODO: Input validation color = models.CharField( max_length=6, # e.g. "ffffff" + blank=True, + null=True, verbose_name="Barva", ) @@ -158,6 +205,14 @@ class Contractee(models.Model): verbose_name = "Naše smluvní strana" verbose_name_plural = "Naše smluvní strany" + def __str__(self) -> str: + result = self.name + + if self.department is not None: + result += f", {self.department}" + + return result + class ContracteeRepresentative(models.Model): contractee = models.ForeignKey( @@ -172,14 +227,35 @@ class ContracteeRepresentative(models.Model): verbose_name="Jméno", ) - role = models.CharField( + function = models.CharField( max_length=256, + blank=True, + null=True, verbose_name="Funkce", ) + role = models.CharField( + max_length=256, + blank=True, + null=True, + verbose_name="Role", + ) + class Meta: - verbose_name = "Zástupce naší smluvní strany" - verbose_name_plural = "Zástupci naší smluvní strany" + verbose_name = "Zástupce" + verbose_name_plural = "Zástupci" + + # FIXME: Violates DRY, make a mixin + def __str__(self) -> str: + result = self.name + + if self.function is not None: + result += f", {self.function}" + + if self.role is not None: + result += f" ({self.role})" + + return result class ContracteeSignature(models.Model): @@ -196,10 +272,13 @@ class ContracteeSignature(models.Model): class Meta: verbose_name = "Podpis naší smluvní strany" - verbose_name_plural = "Podpisy naší smluvní strany" + verbose_name_plural = "Podpisy našich smluvních stran" + + def __str__(self) -> str: + return f"{self.contractee.name}, {self.date}" -class ContractSubtype(models.Model): +class ContractSubtype(NameStrMixin, models.Model): name = models.CharField( max_length=32, verbose_name="Jméno", @@ -210,7 +289,7 @@ class ContractSubtype(models.Model): verbose_name_plural = "Podtypy smlouvy" -class ContractIssue(models.Model): +class ContractIssue(NameStrMixin, models.Model): name = models.CharField( max_length=32, verbose_name="Jméno", @@ -221,7 +300,7 @@ class ContractIssue(models.Model): verbose_name_plural = "Problémy se smlouvami" -class ContractFilingArea(models.Model): +class ContractFilingArea(NameStrMixin, models.Model): name = models.CharField( max_length=32, verbose_name="Jméno", @@ -260,18 +339,20 @@ class Contract(models.Model): verbose_name="Obsahuje NDA", ) - signee_signature = models.ManyToManyField( + signee_signatures = models.ManyToManyField( SigneeSignature, - verbose_name="Podpisy druhé smluvní strany", + verbose_name="Podpisy ostatních smluvních stran", ) contractee_signatures = models.ManyToManyField( ContracteeSignature, - verbose_name="Naše podpisy", + verbose_name="Podpisy našich smluvních stran", ) all_parties_sign_date = models.DateField( verbose_name="Datum podpisu všech stran", + blank=True, + null=True, ) # WARNING: Exclude in admin, autofill valid_start_date = models.DateField( @@ -327,6 +408,8 @@ class Contract(models.Model): public_status_set_by = models.ForeignKey( User, on_delete=models.CASCADE, + blank=True, + null=True, related_name="public_status_altered_contracts", verbose_name="Zveřejněno / nezveřejněno uživatelem", help_text="Obsah není veřejně přístupný.", @@ -359,12 +442,11 @@ class Contract(models.Model): notes = MarkdownxField( blank=True, null=True, - verbose_name="Obsah", + verbose_name="Poznámky", help_text="Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat.", ) - summary = models.CharField( - max_length=65536, + summary = MarkdownxField( blank=True, null=True, verbose_name="Rekapitulace", @@ -409,8 +491,11 @@ class Contract(models.Model): verbose_name = "Smlouva" verbose_name_plural = "Smlouvy" + def __str__(self) -> str: + return self.identifier + -class ContractFile(models.Model): +class ContractFile(NameStrMixin, models.Model): name = models.CharField( max_length=128, blank=True, @@ -457,3 +542,6 @@ class ContractIntent(models.Model): class Meta: verbose_name = "Záměr" verbose_name_plural = "Záměry" + + def __str__(self) -> str: + return self.url diff --git a/shared/models.py b/shared/models.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ee10b8b8e3b8e01efdbdf4e251b94b37c35863a2 100644 --- a/shared/models.py +++ b/shared/models.py @@ -0,0 +1,5 @@ +class NameStrMixin: + name = "" + + def __str__(self) -> str: + return self.name diff --git a/static_src/admin/signee_form.js b/static_src/admin/signee_form.js new file mode 100644 index 0000000000000000000000000000000000000000..d03c7b527efc43d7b3e8a57826295de882e2c907 --- /dev/null +++ b/static_src/admin/signee_form.js @@ -0,0 +1,28 @@ +import $ from "jquery"; + +$(window).ready( + () => { + $(".field-date_of_birth"). + css( + "display", + ( + ($("#id_is_legal_entity").is(":checked")) ? + "none": "block" + ) + ); + + $("#id_is_legal_entity").on( + "change", + event => { + $(".field-date_of_birth"). + css( + "display", + ( + (event.target.checked) ? + "none" : "block" + ) + ); + } + ); + } +); diff --git a/webpack.config.js b/webpack.config.js index 579381ee0904acfb069aebc042f5fe9d47d32d9c..ed6269a0038bbebde84fda4b50c1f3c26b47da46 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,6 +13,10 @@ module.exports = { import: path.resolve("static_src", "admin", "contract_form.js"), dependOn: "shared", }, + admin_signee_form: { + import: path.resolve("static_src", "admin", "signee_form.js"), + dependOn: "shared", + }, shared: ["jquery"], }, output: {