diff --git a/contracts/admin.py b/contracts/admin.py index 9ba5ea2718babbdeccbef649052413b62301fee1..738d336d2d7e71c3b5dcfa71324d918f228b5cbb 100644 --- a/contracts/admin.py +++ b/contracts/admin.py @@ -1,3 +1,5 @@ +import copy + from dal_admin_filters import AutocompleteFilter from django.contrib import admin from django.utils.html import format_html @@ -111,7 +113,7 @@ class ContractAdmin(FieldsetsInlineMixin, MarkdownxGuardedModelAdmin): "id_number", "types", "summary", - "public_state", + "is_public", "publishing_rejection_comment", "legal_state", "primary_contract", @@ -176,18 +178,23 @@ class ContractAdmin(FieldsetsInlineMixin, MarkdownxGuardedModelAdmin): ] def get_fieldsets(self, request, obj=None): + fieldsets_with_inlines = copy.deepcopy(self.fieldsets_with_inlines) + if request.user.is_superuser or request.user.has_perm("approve", self): - self.fieldsets_with_inlines.insert( + fieldsets_with_inlines.insert( 8, ( "Schválení", { - "fields": ["approval_state"] + "fields": ["is_approved"] } ), ) - return super().get_fieldsets(request, obj) + return [ + self.make_placeholder(index, fieldset) + for index, fieldset in enumerate(fieldsets_with_inlines) + ] def save_model(self, request, obj, form, change) -> None: if obj.created_by is None: @@ -210,9 +217,9 @@ class ContractAdmin(FieldsetsInlineMixin, MarkdownxGuardedModelAdmin): list_filter = ( "types", - "approval_state", + "is_approved", "legal_state", - "public_state", + "is_public", "paper_form_state", ContractAuthorPlaceholderFilter, "issues", @@ -223,8 +230,8 @@ class ContractAdmin(FieldsetsInlineMixin, MarkdownxGuardedModelAdmin): list_display = ( "name", - "approval_state", - "public_state", + "is_approved", + "is_public", ) # END Contracts diff --git a/contracts/migrations/0004_alter_contract_options_and_more.py b/contracts/migrations/0004_alter_contract_options_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..c66a49e0d75e447182d5e323ac456076c103a185 --- /dev/null +++ b/contracts/migrations/0004_alter_contract_options_and_more.py @@ -0,0 +1,59 @@ +# Generated by Django 4.1.4 on 2023-03-20 10:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0003_contractcontracteerepresentative_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='contract', + options={'permissions': (('approve', 'Schválit / zrušit schválení'), ('view_confidential', 'Zobrazit tajné informace')), 'verbose_name': 'Smlouva', 'verbose_name_plural': 'Smlouvy'}, + ), + migrations.AlterModelOptions( + name='contractsigneerepresentative', + options={'verbose_name': 'Zástupce jiné smluvní strany', 'verbose_name_plural': 'Zástupci jiných smluvních stran'}, + ), + migrations.AlterModelOptions( + name='contracttype', + options={'verbose_name': 'Typ smlouvy', 'verbose_name_plural': 'Typy smluv'}, + ), + migrations.AlterModelOptions( + name='signeesignature', + options={'verbose_name': 'Podpis jiné smluvní strany', 'verbose_name_plural': 'Podpisy jiných smluvních stran'}, + ), + migrations.RemoveField( + model_name='contract', + name='approval_state', + ), + migrations.RemoveField( + model_name='contract', + name='public_state', + ), + migrations.AddField( + model_name='contract', + name='is_approved', + field=models.BooleanField(default=True, help_text='Může měnit jen schvalovatel. Pokud je smlouva veřejná, schválením se vypustí ven.', verbose_name='Je schválená'), + preserve_default=False, + ), + migrations.AddField( + model_name='contract', + name='is_public', + field=models.BooleanField(default=True, verbose_name='Je veřejná'), + preserve_default=False, + ), + migrations.AlterField( + model_name='contractee', + name='address_country', + field=models.CharField(default='CZ', max_length=256, verbose_name='Země'), + ), + migrations.AlterField( + model_name='signee', + name='address_country', + field=models.CharField(default='CZ', max_length=256, verbose_name='Země'), + ), + ] diff --git a/contracts/models.py b/contracts/models.py index d1831d70d278aab094ba3d2aae7ff5b433bbaddf..57647fd0cda15d39efea1686ccd8b3f3a3f3c868 100644 --- a/contracts/models.py +++ b/contracts/models.py @@ -1,7 +1,7 @@ -import math - from django.conf import settings from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver from markdownx.models import MarkdownxField from shared.models import NameStrMixin @@ -103,6 +103,13 @@ class Signee(models.Model): verbose_name = "Jiná smluvní strana" verbose_name_plural = "Ostatní smluvní strany" + @property + def entity_has_public_address(self) -> bool: + return self.entity_type in ( + self.EntityTypes.LEGAL_ENTITY, + self.EntityTypes.OTHER + ) + def __str__(self) -> str: result = self.name @@ -263,19 +270,11 @@ class Contract(NameStrMixin, models.Model): # BEGIN Approval fields - class ApprovalStates(models.TextChoices): - NO = "no", "Neschválená" - YES = "yes", "Schválená" - - approval_state = models.CharField( - max_length=7, - choices=ApprovalStates.choices, - blank=True, - null=True, - verbose_name="Stav schválení", + is_approved = models.BooleanField( + verbose_name="Je schválená", help_text=( "Může měnit jen schvalovatel. Pokud je smlouva " - "veřejná, se stavem \"Schválená\" se vypustí ven." + "veřejná, schválením se vypustí ven." ), ) @@ -337,10 +336,8 @@ class Contract(NameStrMixin, models.Model): verbose_name="Stav právního ujednání", ) - public_state = models.CharField( - max_length=7, - choices=PublicStates.choices, - verbose_name="Veřejnost smlouvy", + is_public = models.BooleanField( + verbose_name="Je veřejná", ) paper_form_state = models.CharField( @@ -422,7 +419,7 @@ class Contract(NameStrMixin, models.Model): blank=True, null=True, verbose_name="Poznámky", - help_text="Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat.", + help_text="Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat a pro tajné čtenáře.", ) class Meta: @@ -431,7 +428,10 @@ class Contract(NameStrMixin, models.Model): verbose_name = "Smlouva" verbose_name_plural = "Smlouvy" - permissions = (("approve", "Schválit / zrušit schválení"),) + permissions = ( + ("approve", "Schválit / zrušit schválení"), + ("view_confidential", "Zobrazit tajné informace"), + ) def get_public_files(self): return ContractFile.objects.filter( @@ -439,6 +439,43 @@ class Contract(NameStrMixin, models.Model): is_public=True, ).all() + def get_private_files(self): + return ContractFile.objects.filter( + contract=self, + is_public=False, + ).all() + + def calculate_signing_parties_sign_date(self) -> None: + contractees_latest_sign_date = None + signees_latest_sign_date = None + + for contractee_signature in self.contractee_signatures.all(): + if ( + contractees_latest_sign_date is None + or contractee_signature.date > contractees_latest_sign_date + ): + contractees_latest_sign_date = contractee_signature.date + + for signee_signature in self.signee_signatures.all(): + if ( + signees_latest_sign_date is None + or signee_signature.date > signees_latest_sign_date + ): + signees_latest_sign_date = signee_signature.date + + if ( + contractees_latest_sign_date is not None + and signees_latest_sign_date is not None + ): + self.all_parties_sign_date = max( + ( + contractees_latest_sign_date, + signees_latest_sign_date + ) + ) + + self.save() + class ContractContracteeRepresentative(RepresentativeMixin, models.Model): contract = models.ForeignKey( @@ -597,6 +634,17 @@ class SigneeSignature(models.Model): return f"{str(self.signee)} - {self.date}" +@receiver(post_save, sender=ContracteeSignature) +@receiver(post_save, sender=SigneeSignature) +def signing_parties_post_save_update_dates( + sender, + instance, + *args, + **kwargs +) -> None: + instance.contract.calculate_signing_parties_sign_date() + + class ContractIntent(NameStrMixin, models.Model): name = models.CharField( max_length=128, diff --git a/contracts/templates/contracts/view_contract.html b/contracts/templates/contracts/view_contract.html index ab29a18084b936860d68e2a16f41ad1d5e24e5c6..0b66ae8e40870a113d940f89e32f3ef64fcc0363 100644 --- a/contracts/templates/contracts/view_contract.html +++ b/contracts/templates/contracts/view_contract.html @@ -1,5 +1,5 @@ {% extends "shared/includes/base.html" %} -{% load subtract %} +{% load subtract markdownify %} {% block content %} <h1 class="head-alt-lg mb-10"><i class="ico--file-blank mr-4"></i>{{ contract.name }}</h1> @@ -20,7 +20,7 @@ {% endif %} <tr> <td class="w-1/5 !p-2.5">Typy</td> - <td class="w-4/5 !p-2.5"> + <td class="w-4/5 !pl-2.5 !py-1 !pr-1"> <ul class="flex flex-wrap gap-1.5"> {% for type in contract.types.all %} <li class="flex"> @@ -44,6 +44,17 @@ <td class="w-1/5 !p-2.5">Souhrn</td> <td class="w-4/5 !p-2.5">{{ contract.summary }}</td> </tr> + {% if user.can_view_confidential %} + <tr> + <td class="w-1/5 !p-2.5">Je veřejná</td> + <td class="w-4/5 !p-2.5"> + <i + class="{% if contract.legal_state == contract.LegalStates.VALID %}ico--checkmark{% else %}ico--cross{% endif %} __tooltipped" + aria-label="{{ contract.get_legal_state_display }}" + ></i> + </td> + </tr> + {% endif %} <tr> <td class="w-1/5 !p-2.5">Je platná</td> <td class="w-4/5 !p-2.5"> @@ -53,9 +64,22 @@ ></i> </td> </tr> + {% if contract.cost_amount %} + <tr> + <td class="w-1/5 !p-2.5">Náklady</td> + <td class="w-4/5 !p-2.5"> + {{ contract.cost_amount }} Kč + {% if contract.cost_unit != contract.CostUnits.TOTAL %} + / {{ contract.get_cost_unit_display }} + {% else %} + celkem + {% endif %} + </td> + </tr> + {% endif %} <tr> - <td class="w-1/5 !p-2.5">Soubory</td> - <td class="w-4/5 !p-2.5"> + <td class="w-1/5 !p-2.5">{% if user.can_view_confidential %}Veřejné s{% else %}S{% endif %}oubory</td> + <td class="w-4/5 !pl-2.5 !py-1 !pr-1"> {% with contract.get_public_files as files %} {% if files|length != 0 %} <ul class="flex gap-2"> @@ -71,63 +95,87 @@ {% endwith %} </td> </tr> - </tbody> - <table> - - <table class="table table-auto w-full table--striped table--bordered mb-7"> - <tbody> - <tr> - <td colspan="2" class="text-lg font-bold"> - <i class="ico--clock mr-3"></i>Data - </td> - </tr> - <tr> - <td class="w-1/5 !p-2.5">Začátek účinnosti</td> - <td class="w-4/5 !p-2.5">{{ contract.valid_start_date }}</td> - </tr> - <tr> - <td class="w-1/5 !p-2.5">Konec účinnosti</td> - <td class="w-4/5 !p-2.5"> - {% if contract.valid_end_date %} - {{ contract.valid_end_date }} - {% else %} - <span class="text-grey-200">Neurčitý</span> - {% endif %} - </td> - </tr> + {% if user.can_view_confidential %} + <tr> + <td class="w-1/5 !p-2.5"> + <div class="flex items-center"> + <i + class="ico--eye-off text-red-600 mr-2 __tooltipped" + aria-label="Neveřejný údaj" + ></i> + Neveřejné soubory + </div> + </td> + <td class="w-4/5 !pl-2.5 !py-1 !pr-1"> + {% with contract.get_private_files as files %} + {% if files|length != 0 %} + <ul class="flex gap-2"> + {% for file in files %} + <li class="flex"> + {% include "contracts/includes/tag.html" with url=file.file.url icon="ico--attachment" content=file.name %} + </li> + {% endfor %} + </ul> + {% else %} + <span class="text-grey-200">Žádné</span> + {% endif %} + {% endwith %} + </td> + </tr> + {% endif %} </tbody> </table> - <table class="table table-auto w-full table--striped table--bordered mb-7"> - <tbody> - <tr> - <td colspan="2" class="text-lg font-bold"> - <i class="ico--folder-open mr-3"></i>Fyzický dokument - </td> - </tr> - - <tr> - <td class="w-1/5 !p-2.5">Stav</td> - <td class="w-4/5 !p-2.5">{{ contract.get_paper_form_state_display }}</td> - </tr> - <tr> - <td class="w-1/5 !p-2.5">Spisovna</td> - <td class="w-4/5 !p-2.5"> - <div class="flex gap-3 items-center"> - <div class="flex"> + <div class="flex gap-7"> + <table class="table table-auto w-full table--striped table--bordered mb-7"> + <tbody> + <tr> + <td colspan="2" class="text-lg font-bold"> + <i class="ico--folder-open mr-3"></i>Fyzický dokument + </td> + </tr> + + <tr> + <td class="w-1/5 !p-2.5">Stav</td> + <td class="w-4/5 !p-2.5">{{ contract.get_paper_form_state_display }}</td> + </tr> + <tr> + <td class="w-1/5 !p-2.5">Spisovna</td> + <td class="w-4/5 !pl-2.5 !py-1 !pr-1"> + <div class="flex gap-3 items-center"> {% include "contracts/includes/tag.html" with url="#TODO" icon="ico--drawer" content=contract.filing_area.name %} </div> - <span>|</span> - <span> - zodpovědná osoba - {{ contract.filing_area.person_responsible }} - </span> - </div> - </td> - </tr> - </tbody> - </table> + </td> + </tr> + </tbody> + </table> + + <table class="table table-auto w-full table--striped table--bordered mb-7"> + <tbody> + <tr> + <td colspan="2" class="text-lg font-bold"> + <i class="ico--calendar mr-3"></i>Data + </td> + </tr> + + <tr> + <td class="w-1/5 !p-2.5 whitespace-nowrap">Začátek účinnosti</td> + <td class="w-4/5 !p-2.5">{{ contract.valid_start_date }}</td> + </tr> + <tr> + <td class="w-1/5 !p-2.5 whitespace-nowrap">Konec účinnosti</td> + <td class="w-4/5 !p-2.5"> + {% if contract.valid_end_date %} + {{ contract.valid_end_date }} + {% else %} + <span class="text-grey-200">Neurčitý</span> + {% endif %} + </td> + </tr> + </tbody> + </table> + </div> <table class="table table-auto w-full table--striped table--bordered mb-7"> <tbody> @@ -165,7 +213,7 @@ </tr> <tr> <td class="w-1/5 !p-2.5">Záměry</td> - <td class="w-4/5 !p-2.5"> + <td class="w-4/5 !pl-2.5 !py-1 !pr-1"> {% with contract.intents.all as intents %} {% if intents|length != 0 %} <ul class="flex gap-2"> @@ -184,29 +232,6 @@ </tbody> </table> - {% if contract.cost_amount %} - <table class="table table-auto w-full table--striped table--bordered mb-7"> - <tbody> - <tr> - <td colspan="2" class="text-lg font-bold"> - <i class="ico--calculator mr-3"></i>Finance - </td> - </tr> - <tr> - <td class="w-1/5 !p-2.5">Náklady</td> - <td class="w-4/5 !p-2.5"> - {{ contract.cost_amount }} Kč - {% if contract.cost_unit != contract.CostUnits.TOTAL %} - / {{ contract.get_cost_unit_display }} - {% else %} - celkem - {% endif %} - </td> - </tr> - </tbody> - </table> - {% endif %} - {% with contract.issues.all as issues %} {% if issues|length != 0 %} <table class="table table-auto w-full table--striped table--bordered mb-10"> @@ -218,17 +243,36 @@ </tr> <tr> - <td class="w-1/5 !p-2.5">Poznámky</td> - <td class="w-4/5 !p-2.5"> + <td class="w-1/5 !p-2.5">{% if not user.can_view_confidential %}Poznámky{% else %}Problémy{% endif %}</td> + <td class="w-4/5 !pl-2.5 !py-1 !pr-1"> <ul class="flex gap-2"> {% for issue in issues %} <li class="flex"> - {% include "contracts/includes/tag.html" with url="#TODO" content=issue.name %} + {% include "contracts/includes/tag.html" with url="#TODO" icon="ico--warning" content=issue.name %} </li> {% endfor %} </ul> </td> </tr> + + {% if user.can_view_confidential and contract.notes %} + <tr> + <td class="w-1/5 !p-2.5"> + <div class="flex items-center"> + <i + class="ico--eye-off text-red-600 mr-2 __tooltipped" + aria-label="Neveřejný údaj" + ></i> + Interní poznámky + </div> + </td> + <td class="w-4/5 !p-2.5"> + <p class="prose min-w-none"> + {{ contract.notes|markdownify|safe }} + </p> + </td> + </tr> + {% endif %} </tbody> </table> {% endif %} @@ -236,7 +280,15 @@ <h2 class="text-xl font-bold mb-5"><i class="ico--pencil mr-3"></i>Podpisy</h2> - <h3 class="text-lg font-bold mb-4">Naší smluvní strany</h3> + {% if contract.all_parties_sign_date %} + <p class="text-lg mb-5"> + <i class="ico--calendar mr-3"></i> + Datum podpisu všech smluvních stran: + <strong>{{ contract.all_parties_sign_date }}</strong> + </p> + {% endif %} + + <h3 class="text-lg font-bold mb-4">Podpisy naší smluvní strany</h3> {% with contract.contractee_signatures.all as signatures %} {% if signatures|length != 0 %} @@ -268,16 +320,21 @@ {% endif %} </address> - <div class="block mb-2"> - <p class="block mb-2"> - <strong>Zástupci:</strong> - </p> - <ul class="list-disc ml-6"> - {% for representative in signature.contractee.representatives.all %} - <li>{{ representative.name }}, {{ representative.function }}</li> - {% endfor %} - </ul> - </div> + {% with contract.contractee_representatives.all as representatives %} + {% if representatives %} + <div class="block mb-2"> + <p class="block mb-2"> + <strong>Zástupci:</strong> + </p> + <ul class="list-disc ml-6"> + {% for representative in representatives %} + <li>{{ representative.name }}, {{ representative.function }}</li> + {% endfor %} + </ul> + </div> + {% endif %} + {% endwith %} + <p> <strong>Datum podpisu:</strong> {{ signature.date }} </p> @@ -289,7 +346,7 @@ {% endif %} {% endwith %} - <h3 class="text-lg font-bold mb-5">Ostatních smluvních stran</h3> + <h3 class="text-lg font-bold mb-5">Podpisy otatních smluvních stran</h3> {% with contract.signee_signatures.all as signatures %} {% if signatures|length != 0 %} @@ -315,12 +372,21 @@ <div class="mb-1"> {{ signature.signee.get_entity_type_display }} - {% if signature.signee.entity_type == signature.signee.EntityTypes.NATURAL_PERSON or signature.signee.entity_type == signature.signee.EntityTypes.BUSINESS_NATURAL_PERSON %} - <span class="text-gray-300">(zobrazujeme pouze obec)</span> + {% if not signature.signee.entity_has_public_address %} + {% if user.can_view_confidential %} + <div class="my-2 flex items-center"> + <i + class="ico--eye-off text-red-600 mr-2 __tooltipped" + aria-label="Neveřejný údaj" + ></i><span class="text-gray-500">Máš přístup k celé adrese.</span> + </div> + {% else %} + <span class="text-gray-500">(zobrazujeme pouze obec)</span> + {% endif %} {% endif %} </div> - {% if signature.signee.entity_type == signature.signee.EntityTypes.LEGAL_ENTITY or signature.signee.entity_type == signature.signee.EntityTypes.OTHER %} + {% if user.can_view_confidential or signature.signee.entity_has_public_address %} {{ signature.signee.address_street_with_number }}<br> {{ signature.signee.address_zip }} {{ signature.signee.address_district }}<br> {{ signature.signee.get_address_country_display }}<br> @@ -333,16 +399,21 @@ {% endif %} </address> - <div class="block mb-2"> - <p class="block mb-2"> - <strong>Zástupci:</strong> - </p> - <ul class="list-disc ml-6"> - {% for representative in signature.signee.representatives.all %} - <li>{{ representative.name }}, {{ representative.function }}</li> - {% endfor %} - </ul> - </div> + {% with contract.signee_representatives.all as representatives %} + {% if representatives %} + <div class="block mb-2"> + <p class="block mb-2"> + <strong>Zástupci:</strong> + </p> + <ul class="list-disc ml-6"> + {% for representative in representatives %} + <li>{{ representative.name }}, {{ representative.function }}</li> + {% endfor %} + </ul> + </div> + {% endif %} + {% endwith %} + <p> <strong>Datum podpisu:</strong> {{ signature.date }} </p> diff --git a/contracts/views.py b/contracts/views.py index 8aa3d57b08364fe3aef4198c1faed7bcbee0724e..495b84dcf2f6560916e18135442e732b18d6c43a 100644 --- a/contracts/views.py +++ b/contracts/views.py @@ -1,6 +1,7 @@ from django.conf import settings from django.core.paginator import Paginator from django.shortcuts import render +from guardian.shortcuts import get_objects_for_user from .models import Contract @@ -8,11 +9,16 @@ from .models import Contract def index(request): + filter = { + "is_approved": True + } + + if not request.user.has_perm("contracts.view_confidential"): + filter["is_public"] = True + contracts = ( - Contract.objects.filter( - approval_state=Contract.ApprovalStates.YES, - public_state=Contract.PublicStates.YES, - ) + get_objects_for_user(request.user, "contracts.view_contract") + .filter(**filter) .order_by("valid_start_date") .all() ) @@ -35,10 +41,18 @@ def index(request): def view_contract(request, id: int): - contract = Contract.objects.filter( - approval_state=Contract.ApprovalStates.YES, - public_state=Contract.PublicStates.YES, - ).get(id=id) + filter = { + "is_approved": True + } + + if not request.user.has_perm("contracts.view_confidential"): + filter["is_public"] = True + + contract = ( + get_objects_for_user(request.user, "contracts.view_contract") + .filter(**filter) + .get(id=id) + ) return render( request, @@ -47,7 +61,7 @@ def view_contract(request, id: int): "site_url": settings.SITE_URL, "user": request.user, "title": contract.name, - "description": "", # TODO + "description": contract.summary, "contract": contract, }, ) diff --git a/registry/admin.py b/registry/admin.py index 0168742eec79a3afb0b9ddba14c95172a3388d1a..9681f6708bfc09238725244ccb2f6030f292669a 100644 --- a/registry/admin.py +++ b/registry/admin.py @@ -1,3 +1,5 @@ +import copy + from django_admin_index.models import AppGroup, AppGroupQuerySet from django.conf import settings @@ -23,18 +25,28 @@ def get_app_list(self, request): admin.AdminSite.get_app_list = get_app_list -class CustomOrderAppGroupQuerySet(AppGroupQuerySet): - def as_list(self, request, include_remaining=True): - result = super().as_list(request, include_remaining=include_remaining) +original_as_list_func = copy.deepcopy(AppGroupQuerySet.as_list) + - for item in result: - if app_label not in settings.ADMIN_ORDERING: - continue +def as_list(self, request, include_remaining=True): + result = original_as_list_func( + self, + request, + include_remaining=include_remaining + ) - item["models"].sort( - key=lambda model: settings.ADMIN_ORDERING[app_name].index(model["name"]) + for item in result: + if item["app_label"] not in settings.ADMIN_ORDERING: + continue + + item["models"].sort( + key=lambda model: ( + settings.ADMIN_ORDERING[item["app_label"]] + .index(model["object_name"]) ) + ) + + return result - return result -AppGroup.objects = AppGroupQuerySet.as_manager() +AppGroupQuerySet.as_list = as_list diff --git a/registry/templates/admin/index.html b/registry/templates/admin/index.html index 1a3722a1222423c554d102d00ae14a906680d5b2..7cc49ba468d9ac9cde1dcc1b7f7e9193a367dac1 100644 --- a/registry/templates/admin/index.html +++ b/registry/templates/admin/index.html @@ -6,7 +6,7 @@ <div class="index-action-buttons"> {% if request.user.can_approve_contracts %} <a - href="contracts/contract/?approval_state__exact=no" + href="contracts/contract/?is_approved__exact=0" aria-role="button" >Smlouvy ke schválení ({{ request.user.contracts_to_approve_count }})</a> {% endif %} diff --git a/shared/templatetags/__init__.py b/shared/templatetags/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8d1c8b69c3fce7bea45c73efd06983e3c419a92f --- /dev/null +++ b/shared/templatetags/__init__.py @@ -0,0 +1 @@ + diff --git a/shared/templatetags/markdownify.py b/shared/templatetags/markdownify.py new file mode 100644 index 0000000000000000000000000000000000000000..bffb65a998e0b2b0bd4d8fa4679a4d8eaeea7ccf --- /dev/null +++ b/shared/templatetags/markdownify.py @@ -0,0 +1,11 @@ +import html + +import markdownx +from django import template + +register = template.Library() + + +@register.filter +def markdownify(text: str) -> str: + return markdownx.utils.markdownify(html.escape(text)) diff --git a/users/models.py b/users/models.py index d08f1012e470ad97c4e241016e7870071fbc367f..47a4f820c57c0febd13a30622772c2866446b13c 100644 --- a/users/models.py +++ b/users/models.py @@ -24,6 +24,10 @@ class User(pirates_models.AbstractUser): # TODO: Do we need the superuser check? return self.is_superuser or self.has_perm("contracts.add") + @property + def can_view_confidential(self) -> bool: + return self.is_superuser or self.has_perm("contracts.view_confidential") + @property def contracts_to_approve_count(self) -> int: if not self.can_approve_contracts: