diff --git a/src/servala/frontend/templates/frontend/organizations/invitation_accept.html b/src/servala/frontend/templates/frontend/organizations/invitation_accept.html new file mode 100644 index 0000000..fa0bfd7 --- /dev/null +++ b/src/servala/frontend/templates/frontend/organizations/invitation_accept.html @@ -0,0 +1,42 @@ +{% extends "frontend/base.html" %} +{% load i18n %} +{% block html_title %} + {% block page_title %} + {% translate "Accept Organization Invitation" %} + {% endblock page_title %} +{% endblock html_title %} +{% block content %} +
+
+
+
+
+ + {% blocktranslate with org_name=invitation.organization.name role=invitation.get_role_display %} + You have been invited to join {{ org_name }} as a {{ role }}. + {% endblocktranslate %} +
+ {% if user.email|lower != invitation.email|lower %} +
+ + {% blocktranslate with invitation_email=invitation.email user_email=user.email %} + Note: This invitation was sent to {{ invitation_email }}, + but you are currently logged in as {{ user_email }}. + {% endblocktranslate %} +
+ {% endif %} +
+ {% csrf_token %} +
+ {% translate "Cancel" %} + +
+
+
+
+
+
+{% endblock content %} diff --git a/src/servala/frontend/urls.py b/src/servala/frontend/urls.py index 7790b22..3aa9b08 100644 --- a/src/servala/frontend/urls.py +++ b/src/servala/frontend/urls.py @@ -6,6 +6,11 @@ from servala.frontend import views urlpatterns = [ path("accounts/profile/", views.ProfileView.as_view(), name="profile"), path("accounts/logout/", views.LogoutView.as_view(), name="logout"), + path( + "invitations//accept/", + views.InvitationAcceptView.as_view(), + name="invitation.accept", + ), path( "organizations/", views.OrganizationSelectionView.as_view(), diff --git a/src/servala/frontend/views/__init__.py b/src/servala/frontend/views/__init__.py index 5f11a75..6167221 100644 --- a/src/servala/frontend/views/__init__.py +++ b/src/servala/frontend/views/__init__.py @@ -8,6 +8,7 @@ from .generic import ( custom_500, ) from .organization import ( + InvitationAcceptView, OrganizationCreateView, OrganizationDashboardView, OrganizationUpdateView, @@ -25,6 +26,7 @@ from .support import SupportView __all__ = [ "IndexView", + "InvitationAcceptView", "LogoutView", "OrganizationCreateView", "OrganizationDashboardView", diff --git a/src/servala/frontend/views/organization.py b/src/servala/frontend/views/organization.py index 2f35f76..e56416d 100644 --- a/src/servala/frontend/views/organization.py +++ b/src/servala/frontend/views/organization.py @@ -1,11 +1,17 @@ -from django.shortcuts import redirect +from django.contrib import messages +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse +from django.utils import timezone +from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DetailView +from django.views.generic import CreateView, DetailView, TemplateView +from django_scopes import scopes_disabled from rules.contrib.views import AutoPermissionRequiredMixin from servala.core.models import ( BillingEntity, Organization, + OrganizationInvitation, OrganizationMembership, ServiceInstance, ) @@ -103,3 +109,57 @@ class OrganizationUpdateView(OrganizationViewMixin, HtmxUpdateView): def get_success_url(self): return self.request.path + + +@method_decorator(scopes_disabled(), name="dispatch") +class InvitationAcceptView(TemplateView): + template_name = "frontend/organizations/invitation_accept.html" + + def get_invitation(self): + secret = self.kwargs.get("secret") + return get_object_or_404(OrganizationInvitation, secret=secret) + + def dispatch(self, request, *args, **kwargs): + invitation = self.get_invitation() + + if invitation.is_accepted: + messages.warning( + request, + _("This invitation has already been accepted."), + ) + return redirect("frontend:organization.selection") + if not request.user.is_authenticated: + request.session["invitation_next"] = request.path + messages.info( + request, + _("Please log in or sign up to accept this invitation."), + ) + return redirect(f"{reverse('account_login')}?next={request.path}") + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["invitation"] = self.get_invitation() + return context + + def post(self, request, *args, **kwargs): + invitation = self.get_invitation() + invitation.accepted_by = request.user + invitation.accepted_at = timezone.now() + invitation.save() + + OrganizationMembership.objects.get_or_create( + user=request.user, + organization=invitation.organization, + defaults={"role": invitation.role}, + ) + + messages.success( + request, + _("You have successfully joined {organization}!").format( + organization=invitation.organization.name + ), + ) + + request.session.pop("invitation_next", None) + return redirect(invitation.organization.urls.base)