diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py index 42a202f7..dc9e336f 100644 --- a/compensation/models/eco_account.py +++ b/compensation/models/eco_account.py @@ -160,7 +160,7 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix Returns: """ - return reverse("compensation:acc:share", args=(self.id, self.access_token)) + return reverse("compensation:acc:share-token", args=(self.id, self.access_token)) def send_notification_mail_on_deduction_change(self, data_change: dict): """ Sends notification mails for changes on the deduction diff --git a/compensation/templates/compensation/detail/eco_account/includes/controls.html b/compensation/templates/compensation/detail/eco_account/includes/controls.html index 42ce6067..c26883aa 100644 --- a/compensation/templates/compensation/detail/eco_account/includes/controls.html +++ b/compensation/templates/compensation/detail/eco_account/includes/controls.html @@ -15,7 +15,7 @@ - {% if is_ets_member %} diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py index af430a93..beaae8d9 100644 --- a/compensation/urls/eco_account.py +++ b/compensation/urls/eco_account.py @@ -20,7 +20,7 @@ from compensation.views.eco_account.action import NewEcoAccountActionView, EditE RemoveEcoAccountActionView from compensation.views.eco_account.deadline import NewEcoAccountDeadlineView, EditEcoAccountDeadlineView, \ RemoveEcoAccountDeadlineView -from compensation.views.eco_account.share import share_view, create_share_view +from compensation.views.eco_account.share import EcoAccountShareByTokenView, EcoAccountShareFormView from compensation.views.eco_account.document import GetEcoAccountDocumentView, NewEcoAccountDocumentView, \ EditEcoAccountDocumentView, RemoveEcoAccountDocumentView from compensation.views.eco_account.deduction import NewEcoAccountDeductionView, EditEcoAccountDeductionView, \ @@ -51,8 +51,8 @@ urlpatterns = [ path('/deadline//edit', EditEcoAccountDeadlineView.as_view(), name='deadline-edit'), path('/deadline//remove', RemoveEcoAccountDeadlineView.as_view(), name='deadline-remove'), - path('/share/', share_view, name='share'), - path('/share', create_share_view, name='share-create'), + path('/share/', EcoAccountShareByTokenView.as_view(), name='share-token'), + path('/share', EcoAccountShareFormView.as_view(), name='share-form'), # Documents path('/document/new/', NewEcoAccountDocumentView.as_view(), name='new-doc'), diff --git a/compensation/views/eco_account/share.py b/compensation/views/eco_account/share.py index 7b1916e4..c2f2e53f 100644 --- a/compensation/views/eco_account/share.py +++ b/compensation/views/eco_account/share.py @@ -5,74 +5,28 @@ Contact: ksp-servicestelle@sgdnord.rlp.de Created on: 19.08.22 """ -from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.http import HttpRequest -from django.shortcuts import get_object_or_404, redirect -from django.utils.translation import gettext_lazy as _ +from django.utils.decorators import method_decorator from compensation.models import EcoAccount -from intervention.forms.modals.share import ShareModalForm from konova.decorators import shared_access_required, default_group_required +from konova.views.share import AbstractShareByTokenView, AbstractShareFormView -@login_required -def share_view(request: HttpRequest, id: str, token: str): - """ Performs sharing of an eco account +class EcoAccountShareByTokenView(AbstractShareByTokenView): + model = EcoAccount + redirect_url = "compensation:acc:detail" - If token given in url is not valid, the user will be redirected to the dashboard - - Args: - request (HttpRequest): The incoming request - id (str): EcoAccount's id - token (str): Access token for EcoAccount - - Returns: - - """ - user = request.user - obj = get_object_or_404(EcoAccount, id=id) - # Check tokens - if obj.access_token == token: - # Send different messages in case user has already been added to list of sharing users - if obj.is_shared_with(user): - messages.info( - request, - _("{} has already been shared with you").format(obj.identifier) - ) - else: - messages.success( - request, - _("{} has been shared with you").format(obj.identifier) - ) - obj.share_with_user(user) - return redirect("compensation:acc:detail", id=id) - else: - messages.error( - request, - _("Share link invalid"), - extra_tags="danger", - ) - return redirect("home") + @method_decorator(login_required) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) -@login_required -@default_group_required -@shared_access_required(EcoAccount, "id") -def create_share_view(request: HttpRequest, id: str): - """ Renders sharing form for an eco account - - Args: - request (HttpRequest): The incoming request - id (str): EcoAccount's id - - Returns: - - """ - obj = get_object_or_404(EcoAccount, id=id) - form = ShareModalForm(request.POST or None, instance=obj, request=request) - return form.process_request( - request, - msg_success=_("Share settings updated") - ) +class EcoAccountShareFormView(AbstractShareFormView): + model = EcoAccount + @method_decorator(login_required) + @method_decorator(default_group_required) + @method_decorator(shared_access_required(EcoAccount, "id")) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) diff --git a/ema/models/ema.py b/ema/models/ema.py index abec7c43..a7172da8 100644 --- a/ema/models/ema.py +++ b/ema/models/ema.py @@ -103,7 +103,7 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, Pik Returns: """ - return reverse("ema:share", args=(self.id, self.access_token)) + return reverse("ema:share-token", args=(self.id, self.access_token)) class EmaDocument(AbstractDocument): diff --git a/ema/templates/ema/detail/includes/controls.html b/ema/templates/ema/detail/includes/controls.html index a16071bf..182be481 100644 --- a/ema/templates/ema/detail/includes/controls.html +++ b/ema/templates/ema/detail/includes/controls.html @@ -15,7 +15,7 @@ - {% if is_ets_member %} diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py index b0cba9a3..627753ac 100644 --- a/ema/tests/test_views.py +++ b/ema/tests/test_views.py @@ -49,8 +49,8 @@ class EmaViewTestCase(CompensationViewTestCase): self.log_url = reverse("ema:log", args=(self.ema.id,)) self.edit_url = reverse("ema:edit", args=(self.ema.id,)) self.remove_url = reverse("ema:remove", args=(self.ema.id,)) - self.share_url = reverse("ema:share", args=(self.ema.id, self.ema.access_token,)) - self.share_create_url = reverse("ema:share-create", args=(self.ema.id,)) + self.share_url = reverse("ema:share-token", args=(self.ema.id, self.ema.access_token,)) + self.share_create_url = reverse("ema:share-form", args=(self.ema.id,)) self.record_url = reverse("ema:record", args=(self.ema.id,)) self.report_url = reverse("ema:report", args=(self.ema.id,)) self.new_doc_url = reverse("ema:new-doc", args=(self.ema.id,)) diff --git a/ema/urls.py b/ema/urls.py index a9c6dc41..bff7c41d 100644 --- a/ema/urls.py +++ b/ema/urls.py @@ -15,7 +15,7 @@ from ema.views.log import EmaLogView from ema.views.record import EmaRecordView from ema.views.report import report_view from ema.views.resubmission import EmaResubmissionView -from ema.views.share import share_view, create_share_view +from ema.views.share import EmaShareFormView, EmaShareByTokenView from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView app_name = "ema" @@ -43,8 +43,8 @@ urlpatterns = [ path('/deadline//edit', EditEmaDeadlineView.as_view(), name='deadline-edit'), path('/deadline//remove', RemoveEmaDeadlineView.as_view(), name='deadline-remove'), - path('/share/', share_view, name='share'), - path('/share', create_share_view, name='share-create'), + path('/share/', EmaShareByTokenView.as_view(), name='share-token'), + path('/share', EmaShareFormView.as_view(), name='share-form'), # Documents path('/document/new/', NewEmaDocumentView.as_view(), name='new-doc'), diff --git a/ema/views/share.py b/ema/views/share.py index 83aae91d..536fe31e 100644 --- a/ema/views/share.py +++ b/ema/views/share.py @@ -5,73 +5,28 @@ Contact: ksp-servicestelle@sgdnord.rlp.de Created on: 19.08.22 """ -from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.http import HttpRequest -from django.shortcuts import get_object_or_404, redirect -from django.utils.translation import gettext_lazy as _ +from django.utils.decorators import method_decorator from ema.models import Ema -from intervention.forms.modals.share import ShareModalForm from konova.decorators import conservation_office_group_required, shared_access_required +from konova.views.share import AbstractShareByTokenView, AbstractShareFormView -@login_required -def share_view(request: HttpRequest, id: str, token: str): - """ Performs sharing of an ema +class EmaShareByTokenView(AbstractShareByTokenView): + model = Ema + redirect_url = "ema:detail" - If token given in url is not valid, the user will be redirected to the dashboard - - Args: - request (HttpRequest): The incoming request - id (str): EMA's id - token (str): Access token for EMA - - Returns: - - """ - user = request.user - obj = get_object_or_404(Ema, id=id) - # Check tokens - if obj.access_token == token: - # Send different messages in case user has already been added to list of sharing users - if obj.is_shared_with(user): - messages.info( - request, - _("{} has already been shared with you").format(obj.identifier) - ) - else: - messages.success( - request, - _("{} has been shared with you").format(obj.identifier) - ) - obj.share_with_user(user) - return redirect("ema:detail", id=id) - else: - messages.error( - request, - _("Share link invalid"), - extra_tags="danger", - ) - return redirect("home") + @method_decorator(login_required) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) -@login_required -@conservation_office_group_required -@shared_access_required(Ema, "id") -def create_share_view(request: HttpRequest, id: str): - """ Renders sharing form for an Ema +class EmaShareFormView(AbstractShareFormView): + model = Ema - Args: - request (HttpRequest): The incoming request - id (str): Ema's id - - Returns: - - """ - obj = get_object_or_404(Ema, id=id) - form = ShareModalForm(request.POST or None, instance=obj, request=request) - return form.process_request( - request, - msg_success=_("Share settings updated") - ) + @method_decorator(login_required) + @method_decorator(conservation_office_group_required) + @method_decorator(shared_access_required(Ema, "id")) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py index ea561c5b..91c0ddb6 100644 --- a/intervention/models/intervention.py +++ b/intervention/models/intervention.py @@ -337,7 +337,7 @@ class Intervention(BaseObject, Returns: """ - return reverse("intervention:share", args=(self.id, self.access_token)) + return reverse("intervention:share-token", args=(self.id, self.access_token)) def remove_payment(self, form): """ Removes a Payment from the intervention diff --git a/intervention/templates/intervention/detail/includes/controls.html b/intervention/templates/intervention/detail/includes/controls.html index 7af2165b..7008c101 100644 --- a/intervention/templates/intervention/detail/includes/controls.html +++ b/intervention/templates/intervention/detail/includes/controls.html @@ -15,7 +15,7 @@ - {% if is_zb_member %} diff --git a/intervention/tests/test_views.py b/intervention/tests/test_views.py index a049f3e7..e552dbe6 100644 --- a/intervention/tests/test_views.py +++ b/intervention/tests/test_views.py @@ -31,8 +31,8 @@ class InterventionViewTestCase(BaseViewTestCase): self.log_url = reverse("intervention:log", args=(self.intervention.id,)) self.edit_url = reverse("intervention:edit", args=(self.intervention.id,)) self.remove_url = reverse("intervention:remove", args=(self.intervention.id,)) - self.share_url = reverse("intervention:share", args=(self.intervention.id, self.intervention.access_token,)) - self.share_create_url = reverse("intervention:share-create", args=(self.intervention.id,)) + self.share_url = reverse("intervention:share-token", args=(self.intervention.id, self.intervention.access_token,)) + self.share_create_url = reverse("intervention:share-form", args=(self.intervention.id,)) self.run_check_url = reverse("intervention:check", args=(self.intervention.id,)) self.record_url = reverse("intervention:record", args=(self.intervention.id,)) self.report_url = reverse("intervention:report", args=(self.intervention.id,)) diff --git a/intervention/urls.py b/intervention/urls.py index 69c5e767..8a148197 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -21,7 +21,7 @@ from intervention.views.report import report_view from intervention.views.resubmission import InterventionResubmissionView from intervention.views.revocation import new_revocation_view, edit_revocation_view, remove_revocation_view, \ get_revocation_view -from intervention.views.share import share_view, create_share_view +from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView app_name = "intervention" urlpatterns = [ @@ -32,8 +32,8 @@ urlpatterns = [ path('/log', InterventionLogView.as_view(), name='log'), path('/edit', edit_view, name='edit'), path('/remove', remove_view, name='remove'), - path('/share/', share_view, name='share'), - path('/share', create_share_view, name='share-create'), + path('/share/', InterventionShareByTokenView.as_view(), name='share-token'), + path('/share', InterventionShareFormView.as_view(), name='share-form'), path('/check', check_view, name='check'), path('/record', InterventionRecordView.as_view(), name='record'), path('/report', report_view, name='report'), diff --git a/intervention/views/share.py b/intervention/views/share.py index ba9cdd3a..f78d65b2 100644 --- a/intervention/views/share.py +++ b/intervention/views/share.py @@ -5,74 +5,28 @@ Contact: ksp-servicestelle@sgdnord.rlp.de Created on: 19.08.22 """ -from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.http import HttpRequest -from django.shortcuts import get_object_or_404, redirect -from django.utils.translation import gettext_lazy as _ +from django.utils.decorators import method_decorator -from intervention.forms.modals.share import ShareModalForm from intervention.models import Intervention from konova.decorators import default_group_required, shared_access_required +from konova.views.share import AbstractShareByTokenView, AbstractShareFormView -@login_required -def share_view(request: HttpRequest, id: str, token: str): - """ Performs sharing of an intervention +class InterventionShareByTokenView(AbstractShareByTokenView): + model = Intervention + redirect_url = "intervention:detail" - If token given in url is not valid, the user will be redirected to the dashboard - - Args: - request (HttpRequest): The incoming request - id (str): Intervention's id - token (str): Access token for intervention - - Returns: - - """ - user = request.user - intervention = get_object_or_404(Intervention, id=id) - # Check tokens - if intervention.access_token == token: - # Send different messages in case user has already been added to list of sharing users - if intervention.is_shared_with(user): - messages.info( - request, - _("{} has already been shared with you").format(intervention.identifier) - ) - else: - messages.success( - request, - _("{} has been shared with you").format(intervention.identifier) - ) - intervention.share_with_user(user) - return redirect("intervention:detail", id=id) - else: - messages.error( - request, - _("Share link invalid"), - extra_tags="danger", - ) - return redirect("home") + @method_decorator(login_required) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) -@login_required -@default_group_required -@shared_access_required(Intervention, "id") -def create_share_view(request: HttpRequest, id: str): - """ Renders sharing form for an intervention - - Args: - request (HttpRequest): The incoming request - id (str): Intervention's id - - Returns: - - """ - intervention = get_object_or_404(Intervention, id=id) - form = ShareModalForm(request.POST or None, instance=intervention, request=request) - return form.process_request( - request, - msg_success=_("Share settings updated") - ) +class InterventionShareFormView(AbstractShareFormView): + model = Intervention + @method_decorator(login_required) + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index e144a97f..6ddbba99 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -22,6 +22,7 @@ RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs # SHARE DATA_UNSHARED = _("This data is not shared with you") DATA_UNSHARED_EXPLANATION = _("Remember: This data has not been shared with you, yet. This means you can only read but can not edit or perform any actions like running a check or recording.") +DATA_SHARE_SET = _("Share settings updated") # FILES FILE_TYPE_UNSUPPORTED = _("Unsupported file type") diff --git a/konova/views/share.py b/konova/views/share.py new file mode 100644 index 00000000..abcbecaa --- /dev/null +++ b/konova/views/share.py @@ -0,0 +1,88 @@ +""" +Author: Michel Peltriaux +Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany +Contact: ksp-servicestelle@sgdnord.rlp.de +Created on: 22.08.22 + +""" +from django.contrib import messages +from django.shortcuts import get_object_or_404, redirect +from django.views import View +from django.utils.translation import gettext_lazy as _ + +from intervention.forms.modals.share import ShareModalForm +from konova.utils.message_templates import DATA_SHARE_SET + + +class AbstractShareByTokenView(View): + model = None + redirect_url = None + + class Meta: + abstract = True + + def get(self, request, id: str, token: str): + + """ Performs sharing of an intervention + + If token given in url is not valid, the user will be redirected to the dashboard + + Args: + request (HttpRequest): The incoming request + id (str): Object's id + token (str): Access token for object + + Returns: + + """ + user = request.user + obj = get_object_or_404(self.model, id=id) + # Check tokens + if obj.access_token == token: + # Send different messages in case user has already been added to list of sharing users + if obj.is_shared_with(user): + messages.info( + request, + _("{} has already been shared with you").format(obj.identifier) + ) + else: + messages.success( + request, + _("{} has been shared with you").format(obj.identifier) + ) + obj.share_with_user(user) + return redirect(self.redirect_url, id=id) + else: + messages.error( + request, + _("Share link invalid"), + extra_tags="danger", + ) + return redirect("home") + + +class AbstractShareFormView(View): + model = None + + class Meta: + abstract = True + + def get(self, request, id: str): + """ Renders sharing form + + Args: + request (HttpRequest): The incoming request + id (str): Object's id + + Returns: + + """ + obj = get_object_or_404(self.model, id=id) + form = ShareModalForm(request.POST or None, instance=obj, request=request) + return form.process_request( + request, + msg_success=DATA_SHARE_SET + ) + + def post(self, request, id: str): + return self.get(request, id) diff --git a/user/autocomplete/share.py b/user/autocomplete/share.py index de634111..9331873d 100644 --- a/user/autocomplete/share.py +++ b/user/autocomplete/share.py @@ -42,9 +42,11 @@ class ShareTeamAutocomplete(Select2QuerySetView): ) if self.q: # Due to privacy concerns only a full username match will return the proper user entry - qs = qs.filter( - name__icontains=self.q - ) + q_parts = self.q.split(" ") + q = Q() + for part in q_parts: + q &= Q(name__icontains=part) + qs = qs.filter(q) qs = qs.order_by( "name" )