Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow creating invitations with teams #5619

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion temba/orgs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1524,7 +1524,7 @@ class Invitation(SmartModel):
team = models.ForeignKey("tickets.Team", on_delete=models.PROTECT, null=True, related_name="invitations")

@classmethod
def create(cls, org, user, email: str, role: OrgRole, team=None):
def create(cls, org, user, email: str, role: OrgRole, *, team=None):
assert not team or org == team.org

return cls.objects.create(
Expand Down
25 changes: 25 additions & 0 deletions temba/orgs/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4116,6 +4116,31 @@ def test_create(self):
form_errors={"email": "User has already been invited to this workspace."},
)

# invite an agent (defaults to default team)
self.assertCreateSubmit(
create_url,
self.admin,
{"email": "[email protected]", "role": "T"},
new_obj_query=Invitation.objects.filter(
org=self.org, email="[email protected]", role_code="T", team=self.org.default_ticket_team
),
)

# if we have a teams feature, we can select a team
self.org.features += [Org.FEATURE_TEAMS]
self.org.save(update_fields=("features",))
sales = Team.create(self.org, self.admin, "New Team", topics=[])

self.assertCreateFetch(create_url, [self.admin], form_fields={"email": None, "role": "E", "team": None})
self.assertCreateSubmit(
create_url,
self.admin,
{"email": "[email protected]", "role": "T", "team": sales.id},
new_obj_query=Invitation.objects.filter(
org=self.org, email="[email protected]", role_code="T", team=sales
),
)

def test_delete(self):
inv1 = Invitation.create(self.org, self.admin, "[email protected]", OrgRole.EDITOR)
inv2 = Invitation.create(self.org, self.admin, "[email protected]", OrgRole.AGENT)
Expand Down
24 changes: 19 additions & 5 deletions temba/orgs/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from temba.formax import FormaxMixin
from temba.notifications.mixins import NotificationTargetMixin
from temba.orgs.tasks import send_user_verification_email
from temba.tickets.models import Team
from temba.utils import analytics, json, languages, on_transaction_commit, str_to_bool
from temba.utils.email import EmailSender, parse_smtp_url
from temba.utils.fields import (
Expand Down Expand Up @@ -2140,7 +2141,13 @@ class List(RequireFeatureMixin, ContextMenuMixin, BaseListView):
default_order = ("-created_on",)

def build_context_menu(self, menu):
menu.add_modax(_("New"), "invite-create", reverse("orgs.invitation_create"), as_button=True)
menu.add_modax(
_("New"),
"invitation-create",
reverse("orgs.invitation_create"),
title=_("New Invitation"),
as_button=True,
)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
Expand All @@ -2153,12 +2160,15 @@ class Form(forms.ModelForm):
role = forms.ChoiceField(
choices=OrgRole.choices(), initial=OrgRole.EDITOR.code, label=_("Role"), widget=SelectWidget()
)
team = forms.ModelChoiceField(queryset=Team.objects.none(), required=False, widget=SelectWidget())

def __init__(self, org, *args, **kwargs):
self.org = org

super().__init__(*args, **kwargs)

self.fields["team"].queryset = org.teams.filter(is_active=True).order_by(Lower("name"))

def clean_email(self):
email = self.cleaned_data["email"]

Expand All @@ -2172,14 +2182,17 @@ def clean_email(self):

class Meta:
model = Invitation
fields = ("email", "role")
fields = ("email", "role", "team")

form_class = Form
require_feature = Org.FEATURE_USERS
title = ""
submit_button_name = _("Send")
success_url = "@orgs.invitation_list"

def derive_exclude(self):
return [] if Org.FEATURE_TEAMS in self.request.org.features else ["team"]

def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["org"] = self.request.org
Expand All @@ -2191,9 +2204,10 @@ def get_context_data(self, **kwargs):
return context

def save(self, obj):
self.object = Invitation.create(
self.request.org, self.request.user, obj.email, OrgRole.from_code(self.form.cleaned_data["role"])
)
role = OrgRole.from_code(self.form.cleaned_data["role"])
team = (obj.team or self.request.org.default_ticket_team) if role == OrgRole.AGENT else None

self.object = Invitation.create(self.request.org, self.request.user, obj.email, role, team=team)

def post_save(self, obj):
obj.send()
Expand Down
2 changes: 1 addition & 1 deletion templates/flows/flow_create.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% extends 'includes/modax.html' %}
{% extends "includes/modax.html" %}
{% load smartmin i18n %}

{% block fields %}
Expand Down
28 changes: 27 additions & 1 deletion templates/orgs/invitation_create.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "includes/modax.html" %}
{% load i18n %}
{% load i18n smartmin %}

{% block pre-form %}
<div class="mb-8">
Expand All @@ -8,3 +8,29 @@
{% endblocktrans %}
</div>
{% endblock pre-form %}
{% block fields %}
{% render_field 'email' %}
{% render_field 'role' %}
{% if form.fields.team %}
<div class="team hidden">{% render_field 'team' %}</div>
{% endif %}
{% endblock fields %}
{% block modal-script %}
{{ block.super }}
<script type="text/javascript">
var modalBody = getModax("#invitation-create").shadowRoot;
var roleSelect = modalBody.querySelector("temba-select[name='role']");
var team = modalBody.querySelector(".team");
var teamSelect = modalBody.querySelector("temba-select[name='team']");

roleSelect.addEventListener("change", function(evt) {
var selected = evt.target.values[0];
if (selected.value === "T") {
team.classList.remove("hidden");
} else {
team.classList.add("hidden");
teamSelect.clear();
}
});
</script>
{% endblock modal-script %}
5 changes: 4 additions & 1 deletion templates/orgs/invitation_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
{% for obj in object_list %}
<tr>
<td>{{ obj.email }}</td>
<td>{{ obj.role.display }}</td>
<td>
{{ obj.role.display }}
{% if obj.team %}({{ obj.team.name }}){% endif %}
</td>
<td>{{ obj.created_on|day }}</td>
<td class="w-10">
<temba-icon name="delete"
Expand Down
Loading