Repository: pretix/pretix

Issue:
Resend team invite
When inviting users to a team of an Organizer Account it would be useful to be able to resend the email with the invitation in case it got lost somehow. Currently I have to revoke the invitation and send it out again.


Discussion:

Resolving patch:
diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py
--- a/src/pretix/control/logdisplay.py
+++ b/src/pretix/control/logdisplay.py
@@ -389,6 +389,9 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
     if logentry.action_type == 'pretix.team.invite.created':
         return _('{user} has been invited to the team.').format(user=data.get('email'))
 
+    if logentry.action_type == 'pretix.team.invite.resent':
+        return _('Invite for {user} has been resent.').format(user=data.get('email'))
+
     if logentry.action_type == 'pretix.team.invite.deleted':
         return _('The invite for {user} has been revoked.').format(user=data.get('email'))
 
diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py
--- a/src/pretix/control/views/organizer.py
+++ b/src/pretix/control/views/organizer.py
@@ -518,7 +518,7 @@ def post(self, request, *args, **kwargs):
         if 'remove-member' in request.POST:
             try:
                 user = User.objects.get(pk=request.POST.get('remove-member'))
-            except User.DoesNotExist:
+            except (User.DoesNotExist, ValueError):
                 pass
             else:
                 other_admin_teams = self.request.organizer.teams.exclude(pk=self.object.pk).filter(
@@ -542,7 +542,7 @@ def post(self, request, *args, **kwargs):
         elif 'remove-invite' in request.POST:
             try:
                 invite = self.object.invites.get(pk=request.POST.get('remove-invite'))
-            except TeamInvite.DoesNotExist:
+            except (TeamInvite.DoesNotExist, ValueError):
                 messages.error(self.request, _('Invalid invite selected.'))
                 return redirect(self.get_success_url())
             else:
@@ -555,10 +555,26 @@ def post(self, request, *args, **kwargs):
                 messages.success(self.request, _('The invite has been revoked.'))
                 return redirect(self.get_success_url())
 
+        elif 'resend-invite' in request.POST:
+            try:
+                invite = self.object.invites.get(pk=request.POST.get('resend-invite'))
+            except (TeamInvite.DoesNotExist, ValueError):
+                messages.error(self.request, _('Invalid invite selected.'))
+                return redirect(self.get_success_url())
+            else:
+                self._send_invite(invite)
+                self.object.log_action(
+                    'pretix.team.invite.resent', user=self.request.user, data={
+                        'email': invite.email
+                    }
+                )
+                messages.success(self.request, _('The invite has been resent.'))
+                return redirect(self.get_success_url())
+
         elif 'remove-token' in request.POST:
             try:
                 token = self.object.tokens.get(pk=request.POST.get('remove-token'))
-            except TeamAPIToken.DoesNotExist:
+            except (TeamAPIToken.DoesNotExist, ValueError):
                 messages.error(self.request, _('Invalid token selected.'))
                 return redirect(self.get_success_url())
             else:


Test patch:
diff --git a/src/tests/control/test_teams.py b/src/tests/control/test_teams.py
--- a/src/tests/control/test_teams.py
+++ b/src/tests/control/test_teams.py
@@ -103,6 +103,21 @@ def test_team_remove_token(event, admin_user, admin_team, client):
     assert not tk.active
 
 
+@pytest.mark.django_db
+def test_team_resend_invite(event, admin_user, admin_team, client):
+    client.login(email='dummy@dummy.dummy', password='dummy')
+    djmail.outbox = []
+
+    inv = admin_team.invites.create(email='foo@example.org')
+    resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+        'resend-invite': str(inv.pk)
+    }, follow=True)
+    assert 'Admin team' in resp.rendered_content
+    assert admin_user.email in resp.rendered_content
+    assert 'foo@example.org' in resp.rendered_content
+    assert len(djmail.outbox) == 1
+
+
 @pytest.mark.django_db
 def test_team_revoke_invite(event, admin_user, admin_team, client):
     client.login(email='dummy@dummy.dummy', password='dummy')
@@ -225,6 +240,20 @@ def test_remove_last_admin_team(event, admin_user, admin_team, client):
     assert 'alert-danger' in resp.rendered_content
 
 
+@pytest.mark.django_db
+def test_resend_invalid_invite(event, admin_user, admin_team, client):
+    client.login(email='dummy@dummy.dummy', password='dummy')
+    djmail.outbox = []
+
+    inv = admin_team.invites.create(email='foo@example.org')
+    resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
+        'resend-invite': inv.pk + 1
+    }, follow=True)
+    assert b'alert-danger' in resp.content
+    assert b'Invalid invite selected.' in resp.content
+    assert len(djmail.outbox) == 0
+
+
 @pytest.mark.django_db
 def test_invite_invalid_token(event, admin_team, client):
     i = admin_team.invites.create(email='foo@bar.com')