From 9cb41d35ad4a3774465fb8198f07bd2a42210718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org> Date: Fri, 31 Mar 2023 00:58:50 +0200 Subject: [PATCH] allow authors to view own contracts despite unapproval / unpublishing, default is_public to true, textfield unapproval reasoning, validation --- contracts/admin.py | 12 ++++- ...other_alter_contract_cost_unit_and_more.py | 28 ++++++++++ .../0023_alter_contractfile_is_public.py | 18 +++++++ .../0024_alter_contract_is_approved.py | 18 +++++++ contracts/models.py | 52 ++++++++++++++++++- contracts/views.py | 43 +++++++++------ 6 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 contracts/migrations/0022_contract_cost_amount_other_alter_contract_cost_unit_and_more.py create mode 100644 contracts/migrations/0023_alter_contractfile_is_public.py create mode 100644 contracts/migrations/0024_alter_contract_is_approved.py diff --git a/contracts/admin.py b/contracts/admin.py index 0d7f8d0..7b4aa4a 100644 --- a/contracts/admin.py +++ b/contracts/admin.py @@ -3,6 +3,7 @@ import typing from django.contrib import admin from django.contrib.auth.models import Permission +from django.db import models from django.utils.html import format_html from import_export import resources from nested_admin import NestedModelAdmin, NestedStackedInline, NestedTabularInline @@ -300,10 +301,17 @@ class ContractAdmin( queryset = super().get_queryset(request) if not request.user.has_perm("contracts.view_confidential"): - queryset = queryset.filter(is_public=True) + # Allow user to view their own objects, even if not public + queryset = queryset.filter( + models.Q(is_public=True) + | models.Q(created_by=request.user) + ) if not request.user.has_perm("contracts.approve"): - queryset = queryset.filter(is_approved=True) + queryset = queryset.filter( + models.Q(is_approved=True) + | models.Q(created_by=request.user) + ) return queryset diff --git a/contracts/migrations/0022_contract_cost_amount_other_alter_contract_cost_unit_and_more.py b/contracts/migrations/0022_contract_cost_amount_other_alter_contract_cost_unit_and_more.py new file mode 100644 index 0000000..8613bcd --- /dev/null +++ b/contracts/migrations/0022_contract_cost_amount_other_alter_contract_cost_unit_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.4 on 2023-03-30 21:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0021_contract_updated_on_alter_contractee_address_country_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='contract', + name='cost_amount_other', + field=models.CharField(blank=True, help_text="Je nutno vyplnit v případě, že máš vybranou možnost 'jiné' v jednotce nákladů.", max_length=128, null=True, verbose_name='Jednotka nákladů (jiné)'), + ), + migrations.AlterField( + model_name='contract', + name='cost_unit', + field=models.CharField(blank=True, choices=[('hour', 'Hodina'), ('month', 'Měsíc'), ('year', 'Rok'), ('total', 'Celkem'), ('other', 'Jiné')], max_length=5, null=True, verbose_name='Jednotka nákladů'), + ), + migrations.AlterField( + model_name='contract', + name='publishing_rejection_comment', + field=models.TextField(blank=True, help_text='Obsah není veřejně přístupný.', max_length=65536, null=True, verbose_name='Důvod nezveřejnění'), + ), + ] diff --git a/contracts/migrations/0023_alter_contractfile_is_public.py b/contracts/migrations/0023_alter_contractfile_is_public.py new file mode 100644 index 0000000..b948a41 --- /dev/null +++ b/contracts/migrations/0023_alter_contractfile_is_public.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2023-03-30 21:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0022_contract_cost_amount_other_alter_contract_cost_unit_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='contractfile', + name='is_public', + field=models.BooleanField(default=True, verbose_name='Veřejně dostupný'), + ), + ] diff --git a/contracts/migrations/0024_alter_contract_is_approved.py b/contracts/migrations/0024_alter_contract_is_approved.py new file mode 100644 index 0000000..d3bfc0e --- /dev/null +++ b/contracts/migrations/0024_alter_contract_is_approved.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2023-03-30 22:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0023_alter_contractfile_is_public'), + ] + + operations = [ + migrations.AlterField( + model_name='contract', + name='is_approved', + field=models.BooleanField(default=False, help_text='Mohou měnit jen schvalovatelé. Pokud je smlouva veřejná, schválením se vypustí ven.', verbose_name='Je schválená'), + ), + ] diff --git a/contracts/models.py b/contracts/models.py index eedfe6d..a3d10c8 100644 --- a/contracts/models.py +++ b/contracts/models.py @@ -374,6 +374,7 @@ class Contract(NameStrMixin, models.Model): is_approved = models.BooleanField( verbose_name="Je schválená", + default=False, help_text=( "Mohou měnit jen schvalovatelé. Pokud je " "smlouva veřejná, schválením se vypustí ven." @@ -452,7 +453,7 @@ class Contract(NameStrMixin, models.Model): verbose_name="Stav fyzického dokumentu", ) - publishing_rejection_comment = models.CharField( + publishing_rejection_comment = models.TextField( max_length=65536, blank=True, null=True, @@ -487,6 +488,7 @@ class Contract(NameStrMixin, models.Model): MONTH = "month", "Měsíc" YEAR = "year", "Rok" TOTAL = "total", "Celkem" + OTHER = "other", "Jiné" cost_amount = models.PositiveIntegerField( blank=True, null=True, verbose_name="Náklady (Kč)" @@ -500,6 +502,14 @@ class Contract(NameStrMixin, models.Model): verbose_name="Jednotka nákladů", ) + cost_amount_other = models.CharField( + max_length=128, + verbose_name="Jednotka nákladů (jiné)", + help_text="Je nutno vyplnit v případě, že máš vybranou možnost 'jiné' v jednotce nákladů.", + blank=True, + null=True, + ) + filing_area = models.ForeignKey( ContractFilingArea, on_delete=models.SET_NULL, @@ -585,6 +595,44 @@ class Contract(NameStrMixin, models.Model): self.save() def clean(self): + if ( + not self.is_public + and self.publishing_rejection_comment is None + ): + raise ValidationError( + { + "publishing_rejection_comment": "Pokud smlouva není veřejná, toto pole musí být vyplněné." + } + ) + elif ( + self.is_public + and self.publishing_rejection_comment is not None + ): + raise ValidationError( + { + "publishing_rejection_comment": "Nemůže být definováno, pokud je smlouva veřejná." + } + ) + + if ( + self.cost_unit == self.CostUnits.OTHER[1] + and self.cost_amount_other is None + ): + raise ValidationError( + { + "cost_amount_other": "Musí být definováno, pokud je vybrána jednotka nákladů 'jiné'." + } + ) + elif ( + self.cost_unit != self.CostUnits.OTHER[1] + and self.cost_amount_other is not None + ): + raise ValidationError( + { + "cost_amount_other": "Nemůže být definováno, pokud není vybrána jednotka nákladů 'jiné'." + } + ) + if ( self.primary_contract is not None and self.is_public @@ -625,7 +673,7 @@ class ContractFile(NameStrMixin, models.Model): is_public = models.BooleanField( verbose_name="Veřejně dostupný", - default=False, + default=True, ) file = models.FileField( diff --git a/contracts/views.py b/contracts/views.py index 2c57b6f..0fefc00 100644 --- a/contracts/views.py +++ b/contracts/views.py @@ -1,8 +1,7 @@ -import typing - import requests from django.conf import settings from django.core.paginator import Paginator +from django.db import models from django.http import HttpResponse from django.shortcuts import get_object_or_404, render from django_downloadview import ObjectDownloadView @@ -46,18 +45,25 @@ def get_pagination(request, objects) -> tuple: return page, paginator -def get_paginated_contracts(request, filter: typing.Union[None, dict] = None) -> tuple: +def get_paginated_contracts(request, filter=None) -> tuple: if filter is None: - filter = {} + filter = models.Q() - filter["is_approved"] = True + filter = models.Q(is_approved=True) if not request.user.has_perm("contracts.view_confidential"): - filter["is_public"] = True + filter = filter & ( + models.Q(is_public=True) | + ( + models.Q(created_by=request.user) + if not request.user.is_anonymous + else True + ) + ) contracts = ( get_objects_for_user(request.user, "contracts.view_contract") - .filter(**filter) + .filter(filter) .order_by("valid_start_date") .all() ) @@ -84,15 +90,22 @@ def index(request): def view_contract(request, id: int): - filter = {"is_approved": True} + filter = models.Q(is_approved=True) if not request.user.has_perm("contracts.view_confidential"): - filter["is_public"] = True + filter = filter & ( + models.Q(is_public=True) | + ( + models.Q(created_by=request.user) + if not request.user.is_anonymous + else True + ) + ) contract = get_object_or_404( ( get_objects_for_user(request.user, "contracts.view_contract") - .filter(**filter) + .filter(filter) ), id=id ) @@ -119,7 +132,7 @@ def view_contract_filing_area(request, id: int): ) contracts_page, contracts_paginator = get_paginated_contracts( - request, {"filing_area": filing_area} + request, models.Q(filing_area=filing_area) ) return render( @@ -146,7 +159,7 @@ def view_contract_issue(request, id: int): ) contracts_page, contracts_paginator = get_paginated_contracts( - request, {"issues": issue} + request, models.Q(issues=issue) ) return render( @@ -170,7 +183,7 @@ def view_contract_type(request, id: int): ) contracts_page, contracts_paginator = get_paginated_contracts( - request, {"types": type_} + request, models.Q(types=type_) ) return render( @@ -194,7 +207,7 @@ def view_contractee(request, id: int): ) contracts_page, contracts_paginator = get_paginated_contracts( - request, {"contractee_signatures__contractee": contractee} + request, models.Q(contractee_signatures__contractee=contractee) ) return render( @@ -218,7 +231,7 @@ def view_signee(request, id: int): ) contracts_page, contracts_paginator = get_paginated_contracts( - request, {"signee_signatures__signee": signee} + request, models.Q(signee_signatures__signee=signee) ) return render( -- GitLab