diff --git a/src/backoffice/templates/index.html b/src/backoffice/templates/index.html
index 0df1db01..a6cc4231 100644
--- a/src/backoffice/templates/index.html
+++ b/src/backoffice/templates/index.html
@@ -135,6 +135,10 @@
Shop Ticket Overview
Use this to list shop tickets
+
+ Edit and Release Held Emails
+ Use this view to edit and release OutgoingEmailQueue objects on hold.
+
{% endif %}
{% if perms.camps.economyteam_permission %}
diff --git a/src/backoffice/templates/outgoing_email_mass_update.html b/src/backoffice/templates/outgoing_email_mass_update.html
new file mode 100644
index 00000000..44f983e2
--- /dev/null
+++ b/src/backoffice/templates/outgoing_email_mass_update.html
@@ -0,0 +1,63 @@
+{% extends 'program_base.html' %}
+{% load bootstrap3 %}
+{% load bornhack %}
+
+{% block title %}
+Edit and Release Held Emails | {{ block.super }}
+{% endblock %}
+
+{% block content %}
+
+
+
Edit and Release Held Emails ({{ formset|length }})
+
+
+
+{% endblock %}
+
diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py
index b8a3d688..e96a49eb 100644
--- a/src/backoffice/urls.py
+++ b/src/backoffice/urls.py
@@ -58,6 +58,7 @@ from .views import (
FacilityUpdateView,
MerchandiseOrdersView,
MerchandiseToOrderView,
+ OutgoingEmailMassUpdateView,
PendingProposalsView,
ProductHandoutView,
ReimbursementCreateUserSelectView,
@@ -625,4 +626,10 @@ urlpatterns = [
]
),
),
+ # release held emails
+ path(
+ "release_emails",
+ OutgoingEmailMassUpdateView.as_view(),
+ name="outgoing_email_release",
+ ),
]
diff --git a/src/backoffice/views.py b/src/backoffice/views.py
index ffea3203..e8acfab9 100644
--- a/src/backoffice/views.py
+++ b/src/backoffice/views.py
@@ -46,6 +46,7 @@ from program.utils import save_speaker_availability
from shop.models import Order, OrderProductRelation
from teams.models import Team
from tickets.models import DiscountTicket, ShopTicket, SponsorTicket, TicketType
+from utils.models import OutgoingEmail
from .forms import (
AutoScheduleApplyForm,
@@ -1904,3 +1905,54 @@ class BackofficeProxyView(CampViewMixin, RaisePermissionRequiredMixin, TemplateV
context = super().get_context_data(*args, **kwargs)
context["urls"] = settings.BACKOFFICE_PROXY_URLS
return context
+
+
+################################
+# UPDATE HELD OUTGOING EMAILS
+
+
+class OutgoingEmailMassUpdateView(CampViewMixin, OrgaTeamPermissionMixin, FormView):
+ """
+ This view shows a list with forms to edit OutgoingEmail objects with hold=True
+ """
+
+ template_name = "outgoing_email_mass_update.html"
+
+ def setup(self, *args, **kwargs):
+ """Get emails with no team and emails with a team for the current camp."""
+ super().setup(*args, **kwargs)
+ self.queryset = OutgoingEmail.objects.filter(
+ hold=True, responsible_team__isnull=True
+ ).prefetch_related("responsible_team") | OutgoingEmail.objects.filter(
+ hold=True, responsible_team__camp=self.camp
+ ).prefetch_related(
+ "responsible_team"
+ )
+ self.form_class = modelformset_factory(
+ OutgoingEmail,
+ fields=["subject", "text_template", "html_template", "hold"],
+ min_num=self.queryset.count(),
+ validate_min=True,
+ max_num=self.queryset.count(),
+ validate_max=True,
+ extra=0,
+ )
+
+ def get_context_data(self, *args, **kwargs):
+ """Include the formset in the context."""
+ context = super().get_context_data(*args, **kwargs)
+ context["formset"] = self.form_class(queryset=self.queryset)
+ return context
+
+ def form_valid(self, form):
+ """Show a message saying how many objects were updated."""
+ form.save()
+ if form.changed_objects:
+ messages.success(
+ self.request, f"Updated {len(form.changed_objects)} OutgoingEmails"
+ )
+ return redirect(self.get_success_url())
+
+ def get_success_url(self, *args, **kwargs):
+ """Return to the backoffice index."""
+ return reverse("backoffice:index", kwargs={"camp_slug": self.camp.slug})
diff --git a/src/bornhack/settings.py b/src/bornhack/settings.py
index 115bf4f3..4e4c9ec3 100644
--- a/src/bornhack/settings.py
+++ b/src/bornhack/settings.py
@@ -195,3 +195,6 @@ LEAFLET_CONFIG = {
# used to find the economy team
ECONOMY_TEAM_NAME = "Economy"
+
+# we have some large formsets sometimes
+DATA_UPLOAD_MAX_NUMBER_FIELDS = 5000