diff --git a/Dockerfile b/Dockerfile index 6985c0dd97f345f7008c931855866033f711fdbb..c61507638a5f483b4e35e7ec52d0d6b733337d28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,6 +43,8 @@ RUN DATABASE_URL=postgres://x/x \ DEFAULT_CONTRACTEE_DISTRICT=x \ DEFAULT_CONTRACTEE_ICO_NUMBER=x \ DEFAULT_STAFF_GROUPS=x \ + NASTENKA_API_URL=x \ + NASTENKA_API_TOKEN=x \ python manage.py collectstatic --noinput --settings=registry.settings.production RUN bash -c "adduser --disabled-login --quiet --gecos app app && \ diff --git a/contracts/admin.py b/contracts/admin.py index e2bb97cae131f3988964e04399cb87660127a4bb..b6080662ca94e9486ea19da965061c3a53ba0f9c 100644 --- a/contracts/admin.py +++ b/contracts/admin.py @@ -1,7 +1,11 @@ import copy +import json +import logging import typing +import requests from admin_auto_filters.filters import AutocompleteFilterFactory +from django.conf import settings from django.contrib import admin from django.contrib.auth.models import Permission from django.db import models @@ -33,6 +37,8 @@ from .models import ( SigneeSignatureRepresentative, ) +logger = logging.getLogger() + class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin): def has_module_permission(self, request): @@ -314,6 +320,8 @@ class ContractAdmin( return queryset def save_model(self, request, obj, form, change): + is_new = obj.created_by is None + # Need to generate primary keys first parent_save_response = super().save_model(request, obj, form, change) @@ -333,6 +341,40 @@ class ContractAdmin( obj.valid_start_date = last_signature_date + from users.models import User + + if is_new: + try: + sso_ids = [] + + for user in User.objects.filter(is_staff=True).all(): + if user.is_superuser or user.has_perm("contracts.approve"): + sso_ids.append(user.sso_id) + + requests.post( + settings.NASTENKA_API_URL, + data=json.dumps( + { + "name": f"Nová smlouva ke schválení - {obj.name}", + "description": ( + obj.summary + if obj.summary is not None + else "Bez popisu." + ), + "contract_id": obj.id, + "sso_ids": sso_ids, + } + ), + headers={ + "Authorization": f"Token {settings.NASTENKA_API_TOKEN}", + "Content-Type": "application/json", + }, + ) + except Exception as exception: + logger.error( + "Failed to synchronizace Nástěnka notices: %s", str(exception) + ) + return parent_save_response def has_change_permission(self, request, obj=None): diff --git a/contracts/migrations/0062_contract_paper_form_person_responsible.py b/contracts/migrations/0062_contract_paper_form_person_responsible.py index c73e9cc47cea36375525bec36e908df843f4ed65..1e6d8fea7d684cc890af012a72aee2cc031fb490 100644 --- a/contracts/migrations/0062_contract_paper_form_person_responsible.py +++ b/contracts/migrations/0062_contract_paper_form_person_responsible.py @@ -4,15 +4,19 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('contracts', '0061_alter_contract_id_number'), + ("contracts", "0061_alter_contract_id_number"), ] operations = [ migrations.AddField( - model_name='contract', - name='paper_form_person_responsible', - field=models.CharField(blank=True, max_length=256, null=True, verbose_name='Osoba zodpovědná za doručení'), + model_name="contract", + name="paper_form_person_responsible", + field=models.CharField( + blank=True, + max_length=256, + null=True, + verbose_name="Osoba zodpovědná za doručení", + ), ), ] diff --git a/contracts/models.py b/contracts/models.py index 6e4a7a5bb4cfb2050d709be4e6eada49fef2c843..c948ca56e8065b48a4b6dd3ee1e21c28904ceee5 100644 --- a/contracts/models.py +++ b/contracts/models.py @@ -747,10 +747,11 @@ class Contract(NameStrMixin, models.Model): ) if ( - self.paper_form_state not in ( + self.paper_form_state + not in ( self.PaperFormStates.STORED, self.PaperFormStates.SHREDDED, - self.PaperFormStates.LOST + self.PaperFormStates.LOST, ) and not self.paper_form_person_responsible ): diff --git a/contracts/templates/contracts/includes/contract_list.html b/contracts/templates/contracts/includes/contract_list.html index ab5f20ea4c1cdaea74c08104f647b668aa6e599d..0ecc7ce22aea7a4c278d66afecdfcf90a2eb5804 100644 --- a/contracts/templates/contracts/includes/contract_list.html +++ b/contracts/templates/contracts/includes/contract_list.html @@ -1,72 +1,4 @@ -<div class="hidden md:block"> - <table class="table table-auto w-full table--striped table--bordered"> - <thead> - <tr> - <td class="font-bold">Název</td> - <td>Typy</td> - <td>Platná</td> - <td class="whitespace-nowrap">Účinná od</td> - <td class="whitespace-nowrap">Účinná do</td> - <td class="whitespace-nowrap">Podepsána s</td> - </tr> - </thead> - <tbody> - {% for contract in page %} - <tr> - <td{% if not contract.is_public %} class="!bg-red-100"{% endif %}> - {% if not contract.is_public %} - {% include "contracts/includes/private_info_icon.html" %} - {% endif %} - <a - class="underline" - href="{% url "contracts:view_contract" contract.id %}" - >{{ contract.name }}</a> - </td> - <td{% if not contract.is_public %} class="!bg-red-100"{% endif %}> - <ul class="flex flex-wrap gap-1.5"> - {% for type in contract.types.all %} - <li class="flex"> - {% include "contracts/includes/tag.html" with url=type.url icon="ico--folder" content=type.name public=contract.is_public %} - </li> - {% endfor %} - </ul> - </td> - <td{% if not contract.is_public %} class="!bg-red-100"{% endif %}> - <i - class="{% if contract.is_valid %}ico--checkmark{% else %}ico--cross{% endif %}" - aria-label="{% if contract.is_public %}Ano{% else %}Ne{% endif %}" - ></i> - </td> - <td class="whitespace-nowrap{% if not contract.is_public %} !bg-red-100{% endif %}"> - {{ contract.valid_start_date }} - </td> - <td class="whitespace-nowrap{% if not contract.is_public %} !bg-red-100{% endif %}"> - {% if contract.valid_end_date %} - {{ contract.valid_end_date }} - {% else %} - <span class="text-grey-200">Neurčité</span> - {% endif %} - </td> - <td{% if not contract.is_public %} class="!bg-red-100"{% endif %}> - <ul class="flex flex-wrap gap-1.5"> - {% for signature in contract.signee_signatures.all %} - <li class="flex"> - {% if signature.signee.entity_type == signature.signee.EntityTypes.LEGAL_ENTITY or signature.signee.entity_type == signature.signee.EntityTypes.OTHER %} - {% include "contracts/includes/tag.html" with url=signature.signee.url icon="ico--office" content=signature.signee.name public=contract.is_public %} - {% else %} - {% include "contracts/includes/tag.html" with url=signature.signee.url icon="ico--user" content=signature.signee.name public=contract.is_public %} - {% endif %} - </li> - {% endfor %} - </ul> - </td> - </tr> - {% endfor %} - </tbody> - </table> -</div> - -<ul class="flex-col gap-4 flex md:hidden"> +<ul class="flex-col gap-4 flex"> {% for contract in page %} <li class="card elevation-10{% if not contract.is_public %} bg-red-100{% endif %}"> <div class="card__body p-5"> @@ -96,7 +28,11 @@ <tr> <td class="pt-1.5">Účinná od:</td> <td class="pt-1.5"> - {{ contract.valid_start_date }} + {% if contract.valid_start_date %} + {{ contract.valid_start_date }} + {% else %} + <span class="text-grey-200">Neurčité</span> + {% endif %} </td> </tr> <tr> diff --git a/env.example b/env.example index 6953a74f1702e98fefd374c9e36d12aab239740d..564842df14cd9adec8176efda99a39b4e58a7a13 100644 --- a/env.example +++ b/env.example @@ -20,3 +20,6 @@ DEFAULT_CONTRACTEE_DISTRICT="Praha 2" DEFAULT_CONTRACTEE_ICO_NUMBER="71339698" DEFAULT_STAFF_GROUPS="sso_cen:f,sso_cen:neverejni,sso_cen:smlouvy_ao,sso_cen:smlouvy_po,sso_cen:registr_smluv" + +NASTENKA_API_URL=http://localhost:8009/contracts/api/notices +NASTENKA_API_TOKEN=cirno diff --git a/oidc/auth.py b/oidc/auth.py index 07cea60a7ee8595ccd7f4ee2902823da600fde29..eb5f1b9a1fa6e47918a864e95f3f87ac2da64471 100644 --- a/oidc/auth.py +++ b/oidc/auth.py @@ -53,6 +53,7 @@ class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend): access_token, options={"verify_signature": False} ) + user.preferred_username = decoded_access_token["preferred_username"] user_groups = user.groups.all() self._remove_old_user_groups( diff --git a/registry/settings/base.py b/registry/settings/base.py index 4a468c34cdcf4807397d4929536cc5347e76cd32..a21c828a30449877496e93a9fb213ca01aafed4a 100644 --- a/registry/settings/base.py +++ b/registry/settings/base.py @@ -244,6 +244,9 @@ if SENTRY_DSN != "": ## App-specific +NASTENKA_API_URL = env.str("NASTENKA_API_URL") +NASTENKA_API_TOKEN = env.str("NASTENKA_API_TOKEN") + DEFAULT_CONTRACTEE_NAME = env.str("DEFAULT_CONTRACTEE_NAME") DEFAULT_CONTRACTEE_STREET = env.str("DEFAULT_CONTRACTEE_STREET") DEFAULT_CONTRACTEE_ZIP = env.str("DEFAULT_CONTRACTEE_ZIP") diff --git a/requirements/base.txt b/requirements/base.txt index e4fe2396bd4a6f5a08f1182a3859258f20f97548..3a13eb48f3660711f4672cfcd4b6e3a44d3de508 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -21,3 +21,4 @@ Markdown==3.4.3 postal==1.1.10 PyJWT==2.6.0 PyYAML==6.0 +requests==2.31.0 diff --git a/users/migrations/0004_user_preferred_username_alter_user_sso_id.py b/users/migrations/0004_user_preferred_username_alter_user_sso_id.py new file mode 100644 index 0000000000000000000000000000000000000000..ffdac3cd719afb65d20e04db8dcef467ea8038a1 --- /dev/null +++ b/users/migrations/0004_user_preferred_username_alter_user_sso_id.py @@ -0,0 +1,27 @@ +# Generated by Django 4.1.4 on 2023-06-15 05:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0003_alter_user_is_staff_based_on_group"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="preferred_username", + field=models.CharField( + blank=True, + max_length=64, + null=True, + verbose_name="Username v Chobotnici", + ), + ), + migrations.AlterField( + model_name="user", + name="sso_id", + field=models.CharField(max_length=128, verbose_name="SSO ID"), + ), + ] diff --git a/users/models.py b/users/models.py index 83f21bfc393d3c21aa0790ff0854f06b246fa5e4..ab09f228f296456be463480262c1e102c048a9f6 100644 --- a/users/models.py +++ b/users/models.py @@ -1,3 +1,5 @@ +import uuid + from django.conf import settings from django.contrib.auth.models import Group as AuthGroup from django.contrib.auth.models import Permission @@ -18,6 +20,18 @@ class Group: class User(pirates_models.AbstractUser): + preferred_username = models.CharField( + max_length=64, + verbose_name="Username v Chobotnici", + blank=True, + null=True, + ) + + sso_id = models.CharField( + "SSO ID", + max_length=128, + ) + is_staff_based_on_group = models.BooleanField( default=True, verbose_name="Administrační přístup dle členství ve skupině", @@ -28,6 +42,8 @@ class User(pirates_models.AbstractUser): ), ) + USERNAME_FIELD = "preferred_username" + @property def can_approve_contracts(self) -> bool: return self.has_perm("contracts.approve")