parent
21c26f9e5d
commit
b9ff0e61da
5 changed files with 119 additions and 40 deletions
|
|
@ -12,7 +12,13 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
|
||||
from servala.api.permissions import OSBBasicAuthPermission
|
||||
from servala.core.exoscale import get_exoscale_origin
|
||||
from servala.core.models import BillingEntity, Organization, User
|
||||
from servala.core.models import (
|
||||
BillingEntity,
|
||||
Organization,
|
||||
OrganizationInvitation,
|
||||
OrganizationRole,
|
||||
User,
|
||||
)
|
||||
from servala.core.models.service import Service, ServiceOffering
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -127,8 +133,13 @@ class OSBServiceInstanceView(OSBBasicAuthPermission, View):
|
|||
origin=exoscale_origin,
|
||||
osb_guid=organization_guid,
|
||||
)
|
||||
organization = Organization.create_organization(organization, user)
|
||||
self._send_invitation_email(request, organization, user)
|
||||
organization = Organization.create_organization(organization)
|
||||
invitation = OrganizationInvitation.objects.create(
|
||||
organization=organization,
|
||||
email=user.email.lower(),
|
||||
role=OrganizationRole.OWNER,
|
||||
)
|
||||
invitation.send_invitation_email(request)
|
||||
except Exception:
|
||||
return JsonResponse({"error": "Internal server error"}, status=500)
|
||||
|
||||
|
|
@ -138,28 +149,6 @@ class OSBServiceInstanceView(OSBBasicAuthPermission, View):
|
|||
)
|
||||
return JsonResponse({"message": "Successfully enabled service"}, status=201)
|
||||
|
||||
def _send_invitation_email(self, request, organization, user):
|
||||
subject = f"Welcome to Servala - {organization.name}"
|
||||
url = request.build_absolute_uri(organization.urls.base)
|
||||
message = f"""Hello {user.first_name or user.email},
|
||||
|
||||
You have been invited to join the organization "{organization.name}" on Servala Portal.
|
||||
|
||||
You can access your organization at: {url}
|
||||
|
||||
Please use this email address ({user.email}) when prompted to log in.
|
||||
|
||||
Best regards,
|
||||
The Servala Team"""
|
||||
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=message,
|
||||
from_email=settings.EMAIL_DEFAULT_FROM,
|
||||
recipient_list=[user.email],
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
def _send_service_welcome_email(
|
||||
self, request, organization, user, service, service_offering
|
||||
):
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ class OrganizationInvitationAdmin(admin.ModelAdmin):
|
|||
"updated_at",
|
||||
)
|
||||
date_hierarchy = "created_at"
|
||||
actions = ["send_invitation_emails"]
|
||||
|
||||
def is_accepted(self, obj):
|
||||
return obj.is_accepted
|
||||
|
|
@ -127,6 +128,35 @@ class OrganizationInvitationAdmin(admin.ModelAdmin):
|
|||
is_accepted.boolean = True
|
||||
is_accepted.short_description = _("Accepted")
|
||||
|
||||
def send_invitation_emails(self, request, queryset):
|
||||
pending_invitations = queryset.filter(accepted_by__isnull=True)
|
||||
sent_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for invitation in pending_invitations:
|
||||
try:
|
||||
invitation.send_invitation_email(request)
|
||||
sent_count += 1
|
||||
except Exception as e:
|
||||
failed_count += 1
|
||||
messages.error(
|
||||
request,
|
||||
_(f"Failed to send invitation to {invitation.email}: {str(e)}"),
|
||||
)
|
||||
|
||||
if sent_count > 0:
|
||||
messages.success(
|
||||
request,
|
||||
_(f"Successfully sent {sent_count} invitation email(s)."),
|
||||
)
|
||||
|
||||
if failed_count > 0:
|
||||
messages.warning(
|
||||
request, _(f"Failed to send {failed_count} invitation email(s).")
|
||||
)
|
||||
|
||||
send_invitation_emails.short_description = _("Send invitation emails")
|
||||
|
||||
|
||||
@admin.register(ServiceCategory)
|
||||
class ServiceCategoryAdmin(admin.ModelAdmin):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import secrets
|
|||
import rules
|
||||
import urlman
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.mail import send_mail
|
||||
from django.db import models, transaction
|
||||
from django.http import HttpRequest
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import slugify
|
||||
|
|
@ -112,7 +115,7 @@ class Organization(ServalaModelMixin, models.Model):
|
|||
|
||||
@classmethod
|
||||
@transaction.atomic
|
||||
def create_organization(cls, instance, owner):
|
||||
def create_organization(cls, instance, owner=None):
|
||||
try:
|
||||
instance.origin
|
||||
except Exception:
|
||||
|
|
@ -120,7 +123,8 @@ class Organization(ServalaModelMixin, models.Model):
|
|||
pk=settings.SERVALA_DEFAULT_ORIGIN
|
||||
)
|
||||
instance.save()
|
||||
instance.set_owner(owner)
|
||||
if owner:
|
||||
instance.set_owner(owner)
|
||||
|
||||
if (
|
||||
instance.billing_entity.odoo_company_id
|
||||
|
|
@ -486,3 +490,49 @@ class OrganizationInvitation(ServalaModelMixin, models.Model):
|
|||
@property
|
||||
def can_be_accepted(self):
|
||||
return not self.is_accepted
|
||||
|
||||
def send_invitation_email(self, request=None):
|
||||
subject = _("You're invited to join {organization} on Servala").format(
|
||||
organization=self.organization.name
|
||||
)
|
||||
|
||||
if request:
|
||||
invitation_url = request.build_absolute_uri(self.urls.accept)
|
||||
organization_url = request.build_absolute_uri(self.organization.urls.base)
|
||||
else:
|
||||
fake_request = HttpRequest()
|
||||
fake_request.META["SERVER_NAME"] = get_current_site(None).domain
|
||||
fake_request.META["SERVER_PORT"] = "443"
|
||||
fake_request.META["wsgi.url_scheme"] = "https"
|
||||
invitation_url = fake_request.build_absolute_uri(self.urls.accept)
|
||||
organization_url = fake_request.build_absolute_uri(
|
||||
self.organization.urls.base
|
||||
)
|
||||
|
||||
message = _(
|
||||
"""Hello,
|
||||
|
||||
You have been invited to join the organization "{organization}" on Servala Portal as a {role}.
|
||||
|
||||
To accept this invitation, please click the link below:
|
||||
{invitation_url}
|
||||
|
||||
Once you accept, you'll be able to access the organization at:
|
||||
{organization_url}
|
||||
|
||||
Best regards,
|
||||
The Servala Team"""
|
||||
).format(
|
||||
organization=self.organization.name,
|
||||
role=self.get_role_display(),
|
||||
invitation_url=invitation_url,
|
||||
organization_url=organization_url,
|
||||
)
|
||||
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=message,
|
||||
from_email=settings.EMAIL_DEFAULT_FROM,
|
||||
recipient_list=[self.email],
|
||||
fail_silently=False,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -179,13 +179,24 @@ class OrganizationUpdateView(OrganizationViewMixin, HtmxUpdateView):
|
|||
invitation.created_by = request.user
|
||||
invitation.save()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
_("Invitation sent to {email}. Share this link: {url}").format(
|
||||
email=invitation.email,
|
||||
url=request.build_absolute_uri(invitation.urls.accept),
|
||||
),
|
||||
)
|
||||
try:
|
||||
invitation.send_invitation_email(request)
|
||||
messages.success(
|
||||
request,
|
||||
_(
|
||||
"Invitation sent to {email}. They will receive an email with the invitation link."
|
||||
).format(email=invitation.email),
|
||||
)
|
||||
except Exception:
|
||||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
"Invitation created for {email}, but email failed to send. Share this link manually: {url}"
|
||||
).format(
|
||||
email=invitation.email,
|
||||
url=request.build_absolute_uri(invitation.urls.accept),
|
||||
),
|
||||
)
|
||||
else:
|
||||
for error in form.errors.values():
|
||||
messages.error(request, error.as_text())
|
||||
|
|
|
|||
|
|
@ -73,12 +73,8 @@ def test_successful_onboarding_new_organization(
|
|||
assert org.origin == exoscale_origin
|
||||
assert org.namespace.startswith("org-")
|
||||
|
||||
user = User.objects.get(email="test@example.com")
|
||||
assert user.first_name == "Test"
|
||||
assert user.last_name == "User"
|
||||
with scopes_disabled():
|
||||
membership = org.memberships.get(user=user)
|
||||
assert membership.role == "owner"
|
||||
assert org.invitations.all().filter(email="test@example.com").exists()
|
||||
|
||||
billing_entity = org.billing_entity
|
||||
assert billing_entity.name == "Test Organization Display (Exoscale)"
|
||||
|
|
@ -91,7 +87,10 @@ def test_successful_onboarding_new_organization(
|
|||
|
||||
assert len(mail.outbox) == 2
|
||||
invitation_email = mail.outbox[0]
|
||||
assert invitation_email.subject == "Welcome to Servala - Test Organization Display"
|
||||
assert (
|
||||
invitation_email.subject
|
||||
== "You're invited to join Test Organization Display on Servala"
|
||||
)
|
||||
assert "test@example.com" in invitation_email.to
|
||||
|
||||
welcome_email = mail.outbox[1]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue