diff --git a/compensation/views/compensation/compensation.py b/compensation/views/compensation/compensation.py index 098849d8..257eaf8b 100644 --- a/compensation/views/compensation/compensation.py +++ b/compensation/views/compensation/compensation.py @@ -59,7 +59,7 @@ class NewCompensationFormView(BaseNewSpatialLocatedObjectFormView): intervention = get_object_or_404(Intervention, id=intervention_id) return intervention.is_shared_with(user) - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # User has to be an ets user return user.is_default_user() @@ -88,7 +88,7 @@ class EditCompensationFormView(BaseEditSpatialLocatedObjectFormView): _TEMPLATE = "compensation/form/view.html" _REDIRECT_URL = "compensation:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # User has to be a default user return user.is_default_user() @@ -170,5 +170,5 @@ class RemoveCompensationView(LoginRequiredMixin, BaseRemoveModalFormView): _FORM_CLS = RemoveModalForm _REDIRECT_URL = "compensation:index" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() diff --git a/compensation/views/eco_account/eco_account.py b/compensation/views/eco_account/eco_account.py index f9c50ab8..9a40b0b1 100644 --- a/compensation/views/eco_account/eco_account.py +++ b/compensation/views/eco_account/eco_account.py @@ -49,7 +49,7 @@ class NewEcoAccountFormView(BaseNewSpatialLocatedObjectFormView): _TAB_TITLE = _("New Eco-Account") _REDIRECT_URL = "compensation:acc:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # User has to be a default user return user.is_default_user() @@ -60,7 +60,7 @@ class EditEcoAccountFormView(BaseEditSpatialLocatedObjectFormView): _TEMPLATE = "compensation/form/view.html" _REDIRECT_URL = "compensation:acc:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # User has to be a default user return user.is_default_user() @@ -260,5 +260,5 @@ class RemoveEcoAccountView(LoginRequiredMixin, BaseRemoveModalFormView): _FORM_CLS = RemoveEcoAccountModalForm _REDIRECT_URL = "compensation:acc:index" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() diff --git a/compensation/views/payment.py b/compensation/views/payment.py index 49ea8ede..57957870 100644 --- a/compensation/views/payment.py +++ b/compensation/views/payment.py @@ -24,7 +24,7 @@ class BasePaymentView(LoginRequiredMixin, BaseModalFormView): url = super()._get_redirect_url(*args, **kwargs) return f"{url}#related_data" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() diff --git a/ema/views/action.py b/ema/views/action.py index e09511fd..8fc6c595 100644 --- a/ema/views/action.py +++ b/ema/views/action.py @@ -16,14 +16,14 @@ class NewEmaActionView(AbstractNewCompensationActionView): _MODEL_CLS = Ema _REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() class EditEmaActionView(AbstractEditCompensationActionView): _MODEL_CLS = Ema _REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() @@ -31,5 +31,5 @@ class RemoveEmaActionView(AbstractRemoveCompensationActionView): _MODEL_CLS = Ema _REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() diff --git a/ema/views/deadline.py b/ema/views/deadline.py index 475d1ffa..21ad0bf2 100644 --- a/ema/views/deadline.py +++ b/ema/views/deadline.py @@ -14,7 +14,7 @@ class NewEmaDeadlineView(AbstractNewDeadlineView): _MODEL_CLS = Ema _REDIRECT_URL = _EMA_DETAIL_URL_NAME - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() @@ -22,7 +22,7 @@ class EditEmaDeadlineView(AbstractEditDeadlineView): _MODEL_CLS = Ema _REDIRECT_URL = _EMA_DETAIL_URL_NAME - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() @@ -30,5 +30,5 @@ class RemoveEmaDeadlineView(AbstractRemoveDeadlineView): _MODEL_CLS = Ema _REDIRECT_URL = _EMA_DETAIL_URL_NAME - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() diff --git a/ema/views/document.py b/ema/views/document.py index 6f94723a..002304f0 100644 --- a/ema/views/document.py +++ b/ema/views/document.py @@ -16,14 +16,14 @@ class NewEmaDocumentView(AbstractNewDocumentView): _FORM_CLS = NewEmaDocumentModalForm _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() class GetEmaDocumentView(AbstractGetDocumentView): _MODEL_CLS = Ema _DOCUMENT_CLS = EmaDocument - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() class RemoveEmaDocumentView(AbstractRemoveDocumentView): @@ -32,7 +32,7 @@ class RemoveEmaDocumentView(AbstractRemoveDocumentView): _FORM_CLS = RemoveEmaDocumentModalForm _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() class EditEmaDocumentView(AbstractEditDocumentView): @@ -41,5 +41,5 @@ class EditEmaDocumentView(AbstractEditDocumentView): _DOCUMENT_CLS = EmaDocument _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() diff --git a/ema/views/ema.py b/ema/views/ema.py index 6057a95c..c211b799 100644 --- a/ema/views/ema.py +++ b/ema/views/ema.py @@ -38,7 +38,7 @@ class NewEmaFormView(BaseNewSpatialLocatedObjectFormView): _TAB_TITLE = _("New EMA") _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # User has to be an ets user return user.is_ets_user() @@ -50,7 +50,7 @@ class EditEmaFormView(BaseEditSpatialLocatedObjectFormView): _REDIRECT_URL = "ema:detail" _TAB_TITLE = _("Edit {}") - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # User has to be an ets user return user.is_ets_user() @@ -59,7 +59,7 @@ class EmaIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView _MODEL_CLS = Ema _REDIRECT_URL = "ema:index" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() @@ -112,5 +112,5 @@ class RemoveEmaView(LoginRequiredMixin, BaseRemoveModalFormView): _MODEL_CLS = Ema _REDIRECT_URL = "ema:index" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() diff --git a/ema/views/log.py b/ema/views/log.py index 3f0ca939..3baf3886 100644 --- a/ema/views/log.py +++ b/ema/views/log.py @@ -14,5 +14,5 @@ from konova.views.log import AbstractLogView class EmaLogView(LoginRequiredMixin, AbstractLogView): _MODEL_CLS = Ema - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() diff --git a/ema/views/resubmission.py b/ema/views/resubmission.py index 76ea60bd..fb499859 100644 --- a/ema/views/resubmission.py +++ b/ema/views/resubmission.py @@ -16,5 +16,5 @@ class EmaResubmissionView(AbstractResubmissionView): _REDIRECT_URL = "ema:detail" action_url = "ema:resubmission-create" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() diff --git a/ema/views/share.py b/ema/views/share.py index 84ebfdbf..39fc109d 100644 --- a/ema/views/share.py +++ b/ema/views/share.py @@ -17,5 +17,5 @@ class EmaShareFormView(AbstractShareFormView): _MODEL_CLS = Ema _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() \ No newline at end of file diff --git a/ema/views/state.py b/ema/views/state.py index 4c3009ed..f28ca698 100644 --- a/ema/views/state.py +++ b/ema/views/state.py @@ -14,7 +14,7 @@ class NewEmaStateView(AbstractNewCompensationStateView): _MODEL_CLS = Ema _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() @@ -22,7 +22,7 @@ class EditEmaStateView(AbstractEditCompensationStateView): _MODEL_CLS = Ema _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() @@ -30,5 +30,5 @@ class RemoveEmaStateView(AbstractRemoveCompensationStateView): _MODEL_CLS = Ema _REDIRECT_URL = "ema:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() diff --git a/intervention/views/check.py b/intervention/views/check.py index 07387913..09efc105 100644 --- a/intervention/views/check.py +++ b/intervention/views/check.py @@ -19,7 +19,7 @@ class InterventionCheckView(LoginRequiredMixin, BaseModalFormView): _MSG_SUCCESS = _("Check performed") _REDIRECT_URL = "intervention:detail" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_zb_user() def _get_redirect_url(self, *args, **kwargs): diff --git a/intervention/views/revocation.py b/intervention/views/revocation.py index a32bedf5..26f30c05 100644 --- a/intervention/views/revocation.py +++ b/intervention/views/revocation.py @@ -25,7 +25,7 @@ class BaseRevocationView(LoginRequiredMixin, BaseModalFormView): class Meta: abstract = True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _get_redirect_url(self, *args, **kwargs): @@ -63,7 +63,7 @@ class GetRevocationDocumentView(LoginRequiredMixin, BaseView): return redirect("intervention:detail", id=doc.instance.id) return get_document(doc) - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _user_has_shared_access(self, user, **kwargs): diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index a7e05c53..8e16fb41 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -20,6 +20,12 @@ ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office us MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") CHECK_STATE_RESET = _("Status of Checked reset") +# USER | TEAM +TEAM_ADDED = _("New team added") +TEAM_EDITED = _("Team edited") +TEAM_REMOVED = _("Team removed") +TEAM_LEFT = _("Left Team") + # REMOVED GENERIC_REMOVED_TEMPLATE = _("{} removed") diff --git a/konova/views/action.py b/konova/views/action.py index cd00187d..63b44995 100644 --- a/konova/views/action.py +++ b/konova/views/action.py @@ -21,7 +21,7 @@ class AbstractCompensationActionView(LoginRequiredMixin, BaseModalFormView): class Meta: abstract = True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _get_redirect_url(self, *args, **kwargs): diff --git a/konova/views/base.py b/konova/views/base.py index 5f9db037..0c7532ee 100644 --- a/konova/views/base.py +++ b/konova/views/base.py @@ -8,6 +8,7 @@ from abc import abstractmethod from bootstrap_modal_forms.mixins import is_ajax from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.exceptions import ObjectDoesNotExist from django.http import HttpRequest, JsonResponse, HttpResponseRedirect from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse @@ -24,18 +25,37 @@ from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHAR class BaseView(View): - _TEMPLATE: str = "CHANGE_ME" - _TAB_TITLE: str = "CHANGE_ME" - _REDIRECT_URL: str = "CHANGE_ME" - _REDIRECT_URL_ERROR: str = "home" + """ An abstract base view + + This class represents the root of all views on this project. It defines private variables which have to be used + by inheriting classes for proper generic inheriting. + + """ + _TEMPLATE: str = "CHANGE_ME" # Path to template file + _TAB_TITLE: str = "CHANGE_ME" # Title displayed on browser tab + _REDIRECT_URL: str = "CHANGE_ME" # Default URL to redirect after processing (notation as django url "namespace:endpoint") + _REDIRECT_URL_ERROR: str = "home" # Default URL to redirect in case of an error (same notation) class Meta: abstract = True def dispatch(self, request, *args, **kwargs): + """ Dispatching requests before forwarding them into GET or POST endpoints. + + Defines basic checks which need to be done before a user can get access to any view inheriting from + this class. + + Args: + request (HttpRequest): The incoming request + *args (): + **kwargs (): + + Returns: + + """ request = check_user_is_in_any_group(request) - if not self._user_has_permission(request.user): + if not self._user_has_permission(request.user, **kwargs): messages.info(request, MISSING_GROUP_PERMISSION) return redirect(reverse(self._REDIRECT_URL_ERROR)) @@ -46,37 +66,68 @@ class BaseView(View): return super().dispatch(request, *args, **kwargs) @abstractmethod - def _user_has_permission(self, user): - """ Has to be implemented properly by inheriting classes + def _user_has_permission(self, user, **kwargs): + """ Checks whether the user has permission to get this view rendered. + + If no specific check is needed, this method can be overwritten with a simple True returning. Args: - user (): + user (User): The performing user + **kwargs (): Returns: - + has_permission (bool): Whether the user has permission to see this view """ raise NotImplementedError("User permission not checked!") @abstractmethod def _user_has_shared_access(self, user, **kwargs): - """ Has to be implemented properly by inheriting classes + """ Checks whether the user has shared access to this object. + + If no shared-access-check is needed, this method can be overwritten with a simple True returning. Args: - user (): + user (User): The performing user + **kwargs (): Returns: - + has_shared_access (bool): Whether the user has shared access """ raise NotImplementedError("Shared access not checked!") def _get_redirect_url(self, *args, **kwargs): + """ Getter to construct a more specific, data dependant redirect URL + + By default the method simply returns the pre-defined redirect URL. + + Args: + *args (): + **kwargs (): + + Returns: + url (str): Reversed redirect url + """ return self._REDIRECT_URL def _get_redirect_url_error(self, *args, **kwargs): + """ Getter to construct a more specific, data dependant redirect URL in error cases + + By default the method simply returns the pre-defined redirect URL for errors. + + Args: + *args (): + **kwargs (): + + Returns: + url (str): Reversed redirect url + """ return self._REDIRECT_URL_ERROR class BaseModalFormView(BaseView): - _TEMPLATE = "modal/modal_form.html" + """ Abstract base view providing logic to perform most modal form based view renderings + + """ + _TEMPLATE: str = "modal/modal_form.html" _MODEL_CLS = None _FORM_CLS = None _MSG_SUCCESS = None @@ -85,12 +136,45 @@ class BaseModalFormView(BaseView): abstract = True def _user_has_shared_access(self, user, **kwargs): + """ Checks whether the user has shared access to this object. + + For objects inheriting from BaseObject class the method 'is_shared_with()' is a handy + wrapper for checking shared access. For any other circumstances this method should be overwritten + to provide custom shared-access-checking logic. + + If no shared-access-check is needed, this method can be overwritten with a simple True returning. + + Args: + user (User): The performing user + **kwargs (): + + Returns: + has_shared_access (bool): Whether the user has shared access + """ obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get("id")) return obj.is_shared_with(user) - def get(self, request: HttpRequest, id: str, *args, **kwargs): - obj = self._MODEL_CLS.objects.get(id=id) - self._check_for_recorded_instance(obj) + def get(self, request: HttpRequest, *args, **kwargs): + """ GET endpoint for rendering a view holding a modal form + + Args: + request (HttpRequest): The incoming request + *args (): + **kwargs (): + + Returns: + + """ + # If there is an id provided as mapped parameter from the URL take it ... + _id = kwargs.pop("id", None) + try: + # ... and try to resolve it into a record + obj = self._MODEL_CLS.objects.get(id=_id) + self._check_for_recorded_instance(obj) + except ObjectDoesNotExist: + # ... If there is none, maybe we are currently processing + # the creation of a new object (therefore no id yet), so let's continue + obj = None form = self._FORM_CLS( request.POST or None, request.FILES or None, @@ -104,9 +188,27 @@ class BaseModalFormView(BaseView): context = BaseContext(request, context).context return render(request, self._TEMPLATE, context) - def post(self, request: HttpRequest, id: str, *args, **kwargs): - obj = self._MODEL_CLS.objects.get(id=id) - self._check_for_recorded_instance(obj) + def post(self, request: HttpRequest, *args, **kwargs): + """ POST endpoint for processing form contents of a view + + Args: + request (HttpRequest): The incoming request + *args (): + **kwargs (): + + Returns: + + """ + # If there is an id provided as mapped parameter from the URL take it ... + _id = kwargs.pop("id", None) + try: + # ... and try to resolve it into a record + obj = self._MODEL_CLS.objects.get(id=_id) + self._check_for_recorded_instance(obj) + except ObjectDoesNotExist: + # ... If there is none, maybe we are currently processing + # the creation of a new object (therefore no id yet), so let's continue + obj = None form = self._FORM_CLS( request.POST or None, request.FILES or None, @@ -114,13 +216,15 @@ class BaseModalFormView(BaseView): request=request, **kwargs ) + # Get now the redirect url and take specifics of the obj into account for that. + # We do not do this after saving the form to avoid side effects due to possibly changed data redirect_url = self._get_redirect_url(obj=obj) if form.is_valid(): + # Modal forms send one POST for checking on data validity. This is used to evaluate possible errors + # on the form. The second POST (if no errors have been found) is the 'proper' one, + # which we want to process by saving/commiting of the data to the database. if not is_ajax(request.META): - # Modal forms send one POST for checking on data validity. This can be used to return possible errors - # on the form. A second POST (if no errors occurs) is sent afterward and needs to process the - # saving/commiting of the data to the database. is_ajax() performs this check. The first request is - # an ajax call, the second is a regular form POST. + # Get now the success message and take specifics of the obj into account for that msg_success = self._get_msg_success(obj=obj, *args, **kwargs) form.save() messages.success( @@ -136,20 +240,41 @@ class BaseModalFormView(BaseView): return render(request, self._TEMPLATE, context) def _get_redirect_url(self, *args, **kwargs): + """ Getter to construct a more specific, data dependant redirect URL (if needed) + + Args: + *args (): + **kwargs (): + + Returns: + url (str): Reversed redirect url + """ obj = kwargs.get("obj", None) - assert obj is not None - return reverse(self._REDIRECT_URL, args=(obj.id,)) + if obj: + return reverse(self._REDIRECT_URL, args=(obj.id,)) + else: + return reverse(self._REDIRECT_URL) def _get_msg_success(self, *args, **kwargs): + """ Getter to construct a more specific, data dependant success message + + Args: + *args (): + **kwargs (): + + Returns: + + """ return self._MSG_SUCCESS def _check_for_recorded_instance(self, obj): - """ Checks if the object on this view is recorded and runs some special logic if yes + """ Checks if the object on this view is recorded and runs some special logic if so If the instance is recorded, the view should provide some information about why the user can not edit anything. + This behaviour is only intended to mask any form for instances based on the BaseObject class. - There are situations where the form should be rendered regularly, - e.g deduction forms for (recorded) eco accounts. + There are situations where the form should be rendered regularly, despite the instance being recorded, + e.g. for rendering deduction form contents on (recorded) eco accounts. Returns: @@ -162,29 +287,32 @@ class BaseModalFormView(BaseView): return if obj.is_recorded: - self._block_form() + # Replace default template with a blocking one + self._TEMPLATE = "form/recorded_no_edit.html" - def _block_form(self): - """ - Overwrites template, providing no actions - - Returns: - - """ - self._TEMPLATE = "form/recorded_no_edit.html" class BaseIndexView(BaseView): - """ Base class for index views + """ Abstract base class for index views """ - _TEMPLATE = "generic_index.html" + _TEMPLATE: str = "generic_index.html" _INDEX_TABLE_CLS = None - _REDIRECT_URL = "home" + _REDIRECT_URL: str = "home" class Meta: abstract = True - def get(self, request: HttpRequest): + def get(self, request: HttpRequest, *args, **kwargs): + """ GET endpoint for rendering index views + + Args: + request (HttpRequest): The incoming request + *args (): + **kwargs (): + + Returns: + + """ qs = self._get_queryset() table = self._INDEX_TABLE_CLS( request=request, @@ -199,9 +327,14 @@ class BaseIndexView(BaseView): @abstractmethod def _get_queryset(self): + """ Generic getter for the queryset of objects which shall be processed on this view + + Returns: + + """ raise NotImplementedError - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # No specific permissions needed for opening base index view return True @@ -228,7 +361,7 @@ class BaseIdentifierGeneratorView(BaseView): } ) - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): """ Should be overwritten in inheriting classes! Args: @@ -245,6 +378,9 @@ class BaseIdentifierGeneratorView(BaseView): class BaseFormView(BaseView): + """ Abstract base class for rendering form views + + """ _MODEL_CLS = None _FORM_CLS = None @@ -252,18 +388,21 @@ class BaseFormView(BaseView): abstract = True def _get_additional_context(self, **kwargs): - """ + """ Getter for additional data, which is needed to properly render the current view Args: **kwargs (): Returns: - + context (dict): Additional context data for rendering """ return {} class BaseSpatialLocatedObjectFormView(LoginRequiredMixin, BaseFormView): + """ Abstract base view for processing objects with spatial data + + """ _GEOMETRY_FORM_CLS = SimpleGeomForm class Meta: @@ -271,8 +410,11 @@ class BaseSpatialLocatedObjectFormView(LoginRequiredMixin, BaseFormView): class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): + """ Base view for creating new spatial data related to objects - def _user_has_permission(self, user): + """ + + def _user_has_permission(self, user, **kwargs): # User has to have default privilege to call this endpoint return user.is_default_user() @@ -280,10 +422,21 @@ class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): # There is no shared access control since nothing exists yet return True - def get(self, request: HttpRequest, **kwargs): + def get(self, request: HttpRequest, *args, **kwargs): + """ GET endpoint for rendering a form view where object data and spatial data are processed + + Args: + request (HttpRequest): The incoming request + **kwargs (): + + Returns: + + """ + # First initialize the regular object form and the geometry form based on request-bound data form: BaseForm = self._FORM_CLS(None, **kwargs, user=request.user) geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, user=request.user, read_only=False) + # Get some additional context and put everything into the rendering pipeline context = self._get_additional_context() context = BaseContext(request, additional_context=context).context context.update( @@ -295,16 +448,30 @@ class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): ) return render(request, self._TEMPLATE, context) - def post(self, request: HttpRequest, **kwargs): + def post(self, request: HttpRequest, *args, **kwargs): + """ POST endpoint for processing object and spatial data provided by forms + + Args: + request (HttpRequest): The incoming request + **kwargs (): + + Returns: + + """ + # First initialize the regular object form and the geometry form based on request-bound data form: BaseForm = self._FORM_CLS(request.POST or None, **kwargs, user=request.user) geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, user=request.user, read_only=False) + # Only continue if both forms are without errors if form.is_valid() and geom_form.is_valid(): obj = form.save(request.user, geom_form) obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,)) generated_identifier = form.cleaned_data.get("identifier", None) + # There is a rare chance that an identifier has been taken already between sending the form and processing + # the data. If the identifier can not be used anymore, we have to inform the user that another identifier + # had to be generated if generated_identifier != obj.identifier: messages.info( request, @@ -314,12 +481,18 @@ class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): ) ) messages.success(request, _("{} added").format(obj.identifier)) + # Very complex geometries have to be simplified automatically while processing the spatial data. If this + # is the case, the user has to be informed. (They might want to check whether the stored geometry still + # fits their needs) if geom_form.has_geometry_simplified(): messages.info( request, GEOMETRY_SIMPLIFIED ) + # If certain parts of the geometry do not pass the quality check (e.g. way too small and therefore more like + # cutting errors) we need to inform the user that some parts have been removed/ignored while storing the + # geometry num_ignored_geometries = geom_form.get_num_geometries_ignored() if num_ignored_geometries > 0: messages.info( @@ -329,6 +502,7 @@ class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): return redirect(obj_redirect_url) else: + # Something was not properly entered on the forms, so we have to inform the user context = self._get_additional_context() messages.error(request, FORM_INVALID, extra_tags="danger",) @@ -344,15 +518,30 @@ class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): + """ Base view for editing new spatial data related to objects + + """ _TAB_TITLE = _("Edit {}") - def get(self, request: HttpRequest, id: str): + def get(self, request: HttpRequest, id: str, *args, **kwargs): + """ GET endpoint for rendering a form view where object data and spatial data are processed + + Args: + request (HttpRequest): The incoming request + id (str): The id of the object (not the geometry) + + Returns: + + """ + # First fetch the object identified by the id obj = get_object_or_404( self._MODEL_CLS, id=id ) obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,)) + # Check whether the object is recorded. If so - we can redirect the user and inform about the un-editability + # of this entry if obj.is_recorded: messages.info( request, @@ -360,9 +549,11 @@ class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): ) return redirect(obj_redirect_url) + # Seems like the object is not recorded. Good - initialize the forms based on the obj and request-bound data form: BaseForm = self._FORM_CLS(None, instance=obj, user=request.user) geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, instance=obj, read_only=False) + # Get additional context for rendering and put everything in the rendering pipeline context = self._get_additional_context() context = BaseContext(request, additional_context=context).context context.update( @@ -374,13 +565,33 @@ class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): ) return render(request, self._TEMPLATE, context) - def post(self, request: HttpRequest, id: str): + def post(self, request: HttpRequest, id: str, *args, **kwargs): + """ POST endpoint for processing object and spatial data provided by forms + + Args: + request (HttpRequest): The incoming request + id (str): The object's id + *args (): + **kwargs (): + + Returns: + + """ obj = get_object_or_404( self._MODEL_CLS, id=id ) obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,)) + # If the object is recorded, we abort the processing directly and inform the user + if obj.is_recorded: + messages.info( + request, + RECORDED_BLOCKS_EDIT + ) + return redirect(obj_redirect_url) + + # Initialize forms with obj and request-bound data form: BaseForm = self._FORM_CLS(request.POST or None, instance=obj, user=request.user) geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, instance=obj, read_only=False) @@ -388,12 +599,18 @@ class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): obj = form.save(request.user, geom_form) messages.success(request, _("{} edited").format(obj.identifier)) + # Very complex geometries have to be simplified automatically while processing the spatial data. If this + # is the case, the user has to be informed. (They might want to check whether the stored geometry still + # fits their needs) if geom_form.has_geometry_simplified(): messages.info( request, GEOMETRY_SIMPLIFIED ) + # If certain parts of the geometry do not pass the quality check (e.g. way too small and therefore more like + # cutting errors) we need to inform the user that some parts have been removed/ignored while storing the + # geometry num_ignored_geometries = geom_form.get_num_geometries_ignored() if num_ignored_geometries > 0: messages.info( @@ -420,5 +637,5 @@ class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView): obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get('id', None)) return obj.is_shared_with(user) - def _user_has_permission(self, user): - return user.is_default_user() \ No newline at end of file + def _user_has_permission(self, user, **kwargs): + return user.is_default_user() diff --git a/konova/views/deadline.py b/konova/views/deadline.py index 382c5f77..5258e72f 100644 --- a/konova/views/deadline.py +++ b/konova/views/deadline.py @@ -25,7 +25,7 @@ class AbstractNewDeadlineView(LoginRequiredMixin, BaseModalFormView): def _get_redirect_url(self, *args, **kwargs): return super()._get_redirect_url(*args, **kwargs) + "#related_data" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() @@ -41,7 +41,7 @@ class AbstractEditDeadlineView(LoginRequiredMixin, BaseModalFormView): def _get_redirect_url(self, *args, **kwargs): return super()._get_redirect_url(*args, **kwargs) + "#related_data" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() @@ -57,5 +57,5 @@ class AbstractRemoveDeadlineView(LoginRequiredMixin, BaseModalFormView): def _get_redirect_url(self, *args, **kwargs): return super()._get_redirect_url(*args, **kwargs) + "#related_data" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() diff --git a/konova/views/deduction.py b/konova/views/deduction.py index b7b9968c..73bc9817 100644 --- a/konova/views/deduction.py +++ b/konova/views/deduction.py @@ -28,7 +28,7 @@ class AbstractDeductionView(BaseModalFormView): """ pass - def _user_has_permission(self, user) -> bool: + def _user_has_permission(self, user, **kwargs) -> bool: """ Args: diff --git a/konova/views/detail.py b/konova/views/detail.py index 72b4ad17..e324231f 100644 --- a/konova/views/detail.py +++ b/konova/views/detail.py @@ -42,7 +42,7 @@ class BaseDetailView(LoginRequiredMixin, BaseView): # Access to an entry's detail view is not restricted by the state of being-shared or not return True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # Detail views have no restrictions return True diff --git a/konova/views/document.py b/konova/views/document.py index 09df39c7..f80e399d 100644 --- a/konova/views/document.py +++ b/konova/views/document.py @@ -27,7 +27,7 @@ class AbstractNewDocumentView(LoginRequiredMixin, BaseModalFormView): def _get_redirect_url(self, *args, **kwargs): return super()._get_redirect_url(*args, **kwargs) + "#related_data" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() @@ -58,7 +58,7 @@ class AbstractGetDocumentView(LoginRequiredMixin, BaseView): def post(self, request, id: str, doc_id: str): return self.get(request, id, doc_id) - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _user_has_shared_access(self, user, **kwargs): @@ -80,7 +80,7 @@ class AbstractRemoveDocumentView(LoginRequiredMixin, BaseModalFormView): def _get_redirect_url(self, *args, **kwargs): return super()._get_redirect_url(*args, **kwargs) + "#related_data" - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _get_msg_success(self, *args, **kwargs): @@ -100,7 +100,7 @@ class AbstractEditDocumentView(LoginRequiredMixin, BaseModalFormView): class Meta: abstract = True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _get_redirect_url(self, *args, **kwargs): diff --git a/konova/views/geometry.py b/konova/views/geometry.py index 087c5af3..1c25131f 100644 --- a/konova/views/geometry.py +++ b/konova/views/geometry.py @@ -110,7 +110,7 @@ class GeomParcelsView(BaseView): def _user_has_shared_access(self, user, **kwargs): return True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return True @@ -160,5 +160,5 @@ class GeomParcelsContentView(BaseView): def _user_has_shared_access(self, user, **kwargs): return True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return True diff --git a/konova/views/home.py b/konova/views/home.py index 35cbcb1b..0c0772ea 100644 --- a/konova/views/home.py +++ b/konova/views/home.py @@ -74,7 +74,7 @@ class HomeView(LoginRequiredMixin, BaseView): context = BaseContext(request, additional_context).context return render(request, self._TEMPLATE, context) - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # No specific permission needed for home view return True diff --git a/konova/views/log.py b/konova/views/log.py index df898808..ab1a0498 100644 --- a/konova/views/log.py +++ b/konova/views/log.py @@ -46,5 +46,5 @@ class AbstractLogView(BaseView): obj = get_object_or_404(self._MODEL_CLS, id=obj_id) return obj.is_shared_with(user) - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() diff --git a/konova/views/record.py b/konova/views/record.py index cf780acb..f4dd9e19 100644 --- a/konova/views/record.py +++ b/konova/views/record.py @@ -15,7 +15,7 @@ class AbstractRecordView(BaseModalFormView): _FORM_CLS = RecordModalForm _MSG_SUCCESS = None - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_ets_user() def _get_msg_success(self, *args, **kwargs): diff --git a/konova/views/remove.py b/konova/views/remove.py index 82b1e57a..32b3928a 100644 --- a/konova/views/remove.py +++ b/konova/views/remove.py @@ -16,7 +16,7 @@ class BaseRemoveModalFormView(BaseModalFormView): _MSG_SUCCESS = GENERIC_REMOVED_TEMPLATE _REDIRECT_URL = None - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _get_redirect_url(self, *args, **kwargs): diff --git a/konova/views/report.py b/konova/views/report.py index 2dca0d15..c98b6ead 100644 --- a/konova/views/report.py +++ b/konova/views/report.py @@ -97,7 +97,7 @@ class BaseReportView(BaseView): """ raise NotImplementedError - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # Reports do not need specific permissions to be callable return True diff --git a/konova/views/resubmission.py b/konova/views/resubmission.py index 940d09f7..9cf26bd8 100644 --- a/konova/views/resubmission.py +++ b/konova/views/resubmission.py @@ -20,7 +20,7 @@ class AbstractResubmissionView(LoginRequiredMixin, BaseModalFormView): class Meta: abstract = True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _check_for_recorded_instance(self, obj): diff --git a/konova/views/share.py b/konova/views/share.py index 482e62f7..e9757d9f 100644 --- a/konova/views/share.py +++ b/konova/views/share.py @@ -60,7 +60,7 @@ class AbstractShareByTokenView(LoginRequiredMixin, BaseView): ) return redirect("home") - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): # No permissions are needed to get shared access via token return True @@ -77,5 +77,5 @@ class AbstractShareFormView(LoginRequiredMixin, BaseModalFormView): class Meta: abstract = True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() diff --git a/konova/views/state.py b/konova/views/state.py index 419c573b..4170aef0 100644 --- a/konova/views/state.py +++ b/konova/views/state.py @@ -23,7 +23,7 @@ class AbstractCompensationStateView(LoginRequiredMixin, BaseModalFormView): class Meta: abstract = True - def _user_has_permission(self, user): + def _user_has_permission(self, user, **kwargs): return user.is_default_user() def _get_redirect_url(self, *args, **kwargs): diff --git a/user/urls.py b/user/urls.py index ce8616fd..7712b837 100644 --- a/user/urls.py +++ b/user/urls.py @@ -11,7 +11,9 @@ from user.autocomplete.share import ShareUserAutocomplete, ShareTeamAutocomplete from user.autocomplete.team import TeamAdminAutocomplete from user.views.api_token import APITokenView, new_api_token_view from user.views.propagate import PropagateUserView -from user.views.views import * +from user.views.teams import TeamIndexView, NewTeamView, TeamDetailModalView, EditTeamView, RemoveTeamView, \ + LeaveTeamView +from user.views.users import UserDetailView, NotificationsView, ContactView app_name = "user" urlpatterns = [ @@ -22,11 +24,11 @@ urlpatterns = [ path("token/api/new", new_api_token_view, name="api-token-new"), path("contact/", ContactView.as_view(), name="contact"), path("team/", TeamIndexView.as_view(), name="team-index"), - path("team/new", new_team_view, name="team-new"), + path("team/new", NewTeamView.as_view(), name="team-new"), path("team/", TeamDetailModalView.as_view(), name="team-data"), - path("team//edit", edit_team_view, name="team-edit"), - path("team//remove", remove_team_view, name="team-remove"), - path("team//leave", leave_team_view, name="team-leave"), + path("team//edit", EditTeamView.as_view(), name="team-edit"), + path("team//remove", RemoveTeamView.as_view(), name="team-remove"), + path("team//leave", LeaveTeamView.as_view(), name="team-leave"), # Autocomplete urls path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"), diff --git a/user/views/teams.py b/user/views/teams.py new file mode 100644 index 00000000..bde72b93 --- /dev/null +++ b/user/views/teams.py @@ -0,0 +1,105 @@ +""" +Author: Michel Peltriaux +Created on: 05.11.25 + +""" +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import Http404, HttpRequest +from django.shortcuts import get_object_or_404, render +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ + +from konova.contexts import BaseContext +from konova.utils.message_templates import TEAM_LEFT, TEAM_REMOVED, TEAM_EDITED, TEAM_ADDED +from konova.views.base import BaseModalFormView +from user.forms.modals.team import LeaveTeamModalForm, RemoveTeamModalForm, EditTeamModalForm, NewTeamModalForm +from user.forms.team import TeamDataForm +from user.models import Team +from user.views.users import UserBaseView + + +class TeamDetailModalView(LoginRequiredMixin, BaseModalFormView): + _FORM_CLS = TeamDataForm + _MODEL_CLS = Team + + def _user_has_shared_access(self, user, **kwargs): + # No specific constraints + return True + + def _user_has_permission(self, user, **kwargs): + # No specific constraints + return True + + +class TeamIndexView(LoginRequiredMixin, UserBaseView): + _TEMPLATE = "user/team/index.html" + _TAB_TITLE = _("Teams") + + def get(self, request: HttpRequest): + user = request.user + context = { + "teams": user.shared_teams, + "tab_title": self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + +class BaseTeamView(LoginRequiredMixin, BaseModalFormView): + _REDIRECT_URL = "user:team-index" + _MODEL_CLS = Team + + class Meta: + abstract = True + + def _user_has_permission(self, user, **kwargs): + # Nothing to check here - just pass the test + return True + + def _user_has_shared_access(self, user, **kwargs): + # Nothing to check here - just pass the test + return True + + def _get_redirect_url(self, *args, **kwargs): + return reverse(self._REDIRECT_URL) + +class NewTeamView(BaseTeamView): + _FORM_CLS = NewTeamModalForm + _MSG_SUCCESS = TEAM_ADDED + +class EditTeamView(BaseTeamView): + _FORM_CLS = EditTeamModalForm + _MSG_SUCCESS = TEAM_EDITED + + def _user_has_permission(self, user, **kwargs): + team = get_object_or_404(Team, id=kwargs.get("id")) + user_is_admin = team.is_user_admin(user) + if not user_is_admin: + # If user is not an admin, we act as if there is no such team on the database + raise Http404() + return user_is_admin + + +class RemoveTeamView(BaseTeamView): + _FORM_CLS = RemoveTeamModalForm + _MSG_SUCCESS = TEAM_REMOVED + + def _user_has_permission(self, user, **kwargs): + team_id = kwargs.get("id") + team = get_object_or_404(Team, id=team_id) + user_is_admin = team.is_user_admin(user) + if not user_is_admin: + raise Http404() + return True + +class LeaveTeamView(BaseTeamView): + _FORM_CLS = LeaveTeamModalForm + _MSG_SUCCESS = TEAM_LEFT + + def _user_has_shared_access(self, user, **kwargs): + team_id = kwargs.get("id") + team = get_object_or_404(self._MODEL_CLS, id=team_id) + is_user_team_member = team.users.filter(id=user.id).exists() + if not is_user_team_member: + raise Http404() + return True diff --git a/user/views/users.py b/user/views/users.py new file mode 100644 index 00000000..186eee47 --- /dev/null +++ b/user/views/users.py @@ -0,0 +1,81 @@ +from django.contrib import messages +from django.contrib.auth.mixins import LoginRequiredMixin + +from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER +from konova.views.base import BaseView, BaseModalFormView +from user.forms.modals.user import UserContactForm +from user.forms.user import UserNotificationForm +from user.models import User +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.utils.translation import gettext_lazy as _ + +from konova.contexts import BaseContext + + +class UserBaseView(BaseView): + def _user_has_shared_access(self, user, **kwargs): + return True + + def _user_has_permission(self, user, **kwargs): + return True + + +class UserDetailView(LoginRequiredMixin, UserBaseView): + _TEMPLATE = "user/index.html" + _TAB_TITLE = _("User settings") + + def get(self, request: HttpRequest): + context = { + "user": request.user, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + +class NotificationsView(LoginRequiredMixin, UserBaseView): + _TEMPLATE = "user/notifications.html" + _TAB_TITLE = _("User notifications") + + def get(self, request: HttpRequest): + user = request.user + form = UserNotificationForm(user=user, data=None) + context = { + "user": user, + "form": form, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + def post(self, request: HttpRequest): + user = request.user + form = UserNotificationForm(user=user, data=request.POST) + if form.is_valid(): + form.save() + messages.success( + request, + _("Notifications edited") + ) + return redirect("user:detail") + context = { + "user": user, + "form": form, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + +class ContactView(LoginRequiredMixin, BaseModalFormView): + _FORM_CLS = UserContactForm + _MODEL_CLS = User + + def _user_has_shared_access(self, user, **kwargs): + # No specific constraints + return True + + def _user_has_permission(self, user, **kwargs): + # No specific constraints + return True diff --git a/user/views/views.py b/user/views/views.py deleted file mode 100644 index 953c6fe3..00000000 --- a/user/views/views.py +++ /dev/null @@ -1,206 +0,0 @@ -from django.contrib import messages -from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import LoginRequiredMixin -from django.urls import reverse - -from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER -from konova.views.base import BaseView, BaseModalFormView -from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm -from user.forms.modals.user import UserContactForm -from user.forms.team import TeamDataForm -from user.forms.user import UserNotificationForm -from user.models import User, Team -from django.http import HttpRequest, Http404 -from django.shortcuts import render, redirect, get_object_or_404 -from django.utils.translation import gettext_lazy as _ - -from konova.contexts import BaseContext -from konova.decorators import login_required_modal - - -class UserBaseView(BaseView): - def _user_has_shared_access(self, user, **kwargs): - return True - - def _user_has_permission(self, user): - return True - - -class UserDetailView(LoginRequiredMixin, UserBaseView): - _TEMPLATE = "user/index.html" - _TAB_TITLE = _("User settings") - - def get(self, request: HttpRequest): - context = { - "user": request.user, - TAB_TITLE_IDENTIFIER: self._TAB_TITLE, - } - context = BaseContext(request, context).context - return render(request, self._TEMPLATE, context) - - -class NotificationsView(LoginRequiredMixin, UserBaseView): - _TEMPLATE = "user/notifications.html" - _TAB_TITLE = _("User notifications") - - def get(self, request: HttpRequest): - user = request.user - form = UserNotificationForm(user=user, data=None) - context = { - "user": user, - "form": form, - TAB_TITLE_IDENTIFIER: self._TAB_TITLE, - } - context = BaseContext(request, context).context - return render(request, self._TEMPLATE, context) - - def post(self, request: HttpRequest): - user = request.user - form = UserNotificationForm(user=user, data=request.POST) - if form.is_valid(): - form.save() - messages.success( - request, - _("Notifications edited") - ) - return redirect("user:detail") - context = { - "user": user, - "form": form, - TAB_TITLE_IDENTIFIER: self._TAB_TITLE, - } - context = BaseContext(request, context).context - return render(request, self._TEMPLATE, context) - - -class ContactView(LoginRequiredMixin, BaseModalFormView): - def get(self, request: HttpRequest, id: str): - """ Renders contact modal view of a users contact data - - Args: - request (HttpRequest): The incoming request - id (str): The user's id - - Returns: - - """ - user = get_object_or_404(User, id=id) - form = UserContactForm(request.POST or None, instance=user, request=request) - context = { - "form": form, - } - context = BaseContext(request, context).context - return render(request, self._TEMPLATE, context) - - def _user_has_shared_access(self, user, **kwargs): - # No specific constraints - return True - - def _user_has_permission(self, user): - # No specific constraints - return True - - -class TeamDetailModalView(LoginRequiredMixin, BaseModalFormView): - def get(self, request: HttpRequest, id: str): - """ Renders team data - - Args: - request (HttpRequest): The incoming request - id (str): The team's id - - Returns: - - """ - team = get_object_or_404(Team, id=id) - form = TeamDataForm(request.POST or None, instance=team, request=request) - context = { - "form": form, - } - context = BaseContext(request, context).context - return render(request, self._TEMPLATE, context) - - def _user_has_shared_access(self, user, **kwargs): - # No specific constraints - return True - - def _user_has_permission(self, user): - # No specific constraints - return True - - -class TeamIndexView(LoginRequiredMixin, UserBaseView): - _TEMPLATE = "user/team/index.html" - _TAB_TITLE = _("Teams") - - def get(self, request: HttpRequest): - user = request.user - context = { - "teams": user.shared_teams, - "tab_title": self._TAB_TITLE, - } - context = BaseContext(request, context).context - return render(request, self._TEMPLATE, context) - - -@login_required_modal -@login_required -def new_team_view(request: HttpRequest): - form = NewTeamModalForm(request.POST or None, request=request) - return form.process_request( - request, - _("New team added"), - redirect_url=reverse("user:team-index") - ) - - -@login_required_modal -@login_required -def edit_team_view(request: HttpRequest, id: str): - team = get_object_or_404(Team, id=id) - user_is_admin = team.is_user_admin(request.user) - if not user_is_admin: - raise Http404() - form = EditTeamModalForm(request.POST or None, instance=team, request=request) - return form.process_request( - request, - _("Team edited"), - redirect_url=reverse("user:team-index") - ) - - -@login_required_modal -@login_required -def remove_team_view(request: HttpRequest, id: str): - team = get_object_or_404(Team, id=id) - user_is_admin = team.is_user_admin(request.user) - if not user_is_admin: - raise Http404() - form = RemoveTeamModalForm(request.POST or None, instance=team, request=request) - return form.process_request( - request, - _("Team removed"), - redirect_url=reverse("user:team-index") - ) - - -@login_required_modal -@login_required -def leave_team_view(request: HttpRequest, id: str): - team = get_object_or_404(Team, id=id) - user = request.user - - is_user_team_member = team.users.filter(id=user.id).exists() - if not is_user_team_member: - messages.info( - request, - _("You are not a member of this team") - ) - return redirect("user:team-index") - - form = LeaveTeamModalForm(request.POST or None, instance=team, request=request) - return form.process_request( - request, - _("Left Team"), - redirect_url=reverse("user:team-index") - )