mirror of
https://github.com/pretix/pretix.git
synced 2026-10-02 19:24:42 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7abf79f2db | ||
|
|
0ef196d622 | ||
|
|
84858bc048 | ||
|
|
e8c091741b | ||
|
|
ebdfd21b0f | ||
|
|
ed0b3cab7f |
@@ -1683,7 +1683,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
@action(detail=True, methods=['POST'])
|
||||
def confirm(self, request, **kwargs):
|
||||
payment = self.get_object()
|
||||
force = request.data.get('force', False)
|
||||
force = request.data.get('force', False) if request.data else False
|
||||
send_mail = request.data.get('send_email', True) if request.data else True
|
||||
|
||||
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
|
||||
|
||||
@@ -71,8 +71,8 @@ class EmailTransmissionType(TransmissionType):
|
||||
|
||||
def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
|
||||
return {
|
||||
"transmission_email_other": bool(transmission_info.get("transmission_email_address")),
|
||||
"transmission_email_address": transmission_info.get("transmission_email_address"),
|
||||
"transmission_email_other": bool((transmission_info or {}).get("transmission_email_address")),
|
||||
"transmission_email_address": (transmission_info or {}).get("transmission_email_address"),
|
||||
}
|
||||
|
||||
def form_data_to_transmission_info(self, form_data: dict) -> dict:
|
||||
|
||||
@@ -1197,10 +1197,6 @@ class ItemAddOnForm(I18nModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['addon_category'].queryset = self.event.categories.all()
|
||||
self.fields['addon_category'].help_text = format_html('<a href="{}" data-iframe-dialog="true" data-iframe-dialog-target="<[name$=addon_category]" target="_blank">{}</a>', reverse('control:event.items.categories.add', kwargs={
|
||||
'event': self.event.slug,
|
||||
'organizer': self.event.organizer.slug,
|
||||
}), _("Create a new category"))
|
||||
self.fields['addon_category'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'generic',
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{% extends "error.html" %}
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% load urlreplace %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Please wait..." %}</h1>
|
||||
{{ notify_info|json_script:"notify_info" }}
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/notify_parent.js" %}"></script>
|
||||
{% endblock %}
|
||||
@@ -22,12 +22,9 @@
|
||||
import collections.abc
|
||||
import warnings
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.paginator import (
|
||||
EmptyPage, PageNotAnInteger, UnorderedObjectListWarning,
|
||||
)
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import edit
|
||||
|
||||
@@ -41,21 +38,7 @@ class EventBasedFormMixin:
|
||||
return kwargs
|
||||
|
||||
|
||||
class TellParentFormMixin:
|
||||
|
||||
def form_valid(self, form):
|
||||
result = super().form_valid(form)
|
||||
if self.request.GET.get('notify_parent') and isinstance(result, HttpResponseRedirect):
|
||||
message_store = messages.get_messages(self.request)
|
||||
msgs = [{'level': msg.level_tag, 'message': msg.message} for msg in message_store]
|
||||
return render(self.request, 'pretixcontrol/notify_parent.html', {
|
||||
'notify_info': {'object': self.object.pk, 'object_str': str(self.object), 'redirect_url': result.url, 'messages': msgs},
|
||||
})
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
class CreateView(EventBasedFormMixin, TellParentFormMixin, edit.CreateView):
|
||||
class CreateView(EventBasedFormMixin, edit.CreateView):
|
||||
"""
|
||||
Like Django's default CreateView, but passes the optional event
|
||||
argument to the form. This is necessary for I18nModelForms to work
|
||||
@@ -64,7 +47,7 @@ class CreateView(EventBasedFormMixin, TellParentFormMixin, edit.CreateView):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateView(EventBasedFormMixin, TellParentFormMixin, edit.UpdateView):
|
||||
class UpdateView(EventBasedFormMixin, edit.UpdateView):
|
||||
"""
|
||||
Like Django's default UpdateView, but passes the optional event
|
||||
argument to the form. This is necessary for I18nModelForms to work
|
||||
|
||||
@@ -161,7 +161,12 @@ class OrderMailForm(BaseMailForm):
|
||||
),
|
||||
label=pgettext_lazy('sendmail_form', 'Restrict to products'),
|
||||
required=True,
|
||||
queryset=Item.objects.none()
|
||||
queryset=Item.objects.none(),
|
||||
help_text=pgettext_lazy(
|
||||
'sendmail_form',
|
||||
'There may be multiple mails sent out to the same mail address if one order contains multiple attendee '
|
||||
'products for it, if you restrict to products while also restricting mails to attendees only. '
|
||||
'This is intended, as every one of those get linked to their own separate order page restricted to only that product.')
|
||||
)
|
||||
filter_checkins = forms.BooleanField(
|
||||
label=_('Filter check-in status'),
|
||||
@@ -374,6 +379,11 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
|
||||
del self.fields['subevent']
|
||||
|
||||
self.fields['limit_products'].queryset = Item.objects.filter(event=self.event)
|
||||
self.fields['limit_products'].help_text = pgettext_lazy(
|
||||
'sendmail_form',
|
||||
'There may be multiple mails sent out to the same mail address if one order contains multiple attendee '
|
||||
'products for it, if you restrict to products while also restricting mails to attendees only. '
|
||||
'This is intended, as every one of those get linked to their own separate order page restricted to only that product.')
|
||||
|
||||
self.fields['schedule_type'] = forms.ChoiceField(
|
||||
label=_('Type of schedule time'),
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
from datetime import datetime, time, timedelta
|
||||
|
||||
from dateutil.tz import datetime_exists
|
||||
@@ -39,6 +40,8 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.base import LoggingMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScheduledMail(models.Model):
|
||||
STATE_SCHEDULED = 'scheduled'
|
||||
@@ -115,7 +118,7 @@ class ScheduledMail(models.Model):
|
||||
|
||||
filter_orders_by_op = False
|
||||
op_qs = OrderPosition.objects.filter(
|
||||
order__event=self.event,
|
||||
order__event=e,
|
||||
canceled=False,
|
||||
)
|
||||
|
||||
@@ -167,14 +170,58 @@ class ScheduledMail(models.Model):
|
||||
for o in orders:
|
||||
with language(o.locale, e.settings.region):
|
||||
positions = list(o.positions.all())
|
||||
o_sent = False
|
||||
send_to_order = send_to_orders
|
||||
|
||||
try:
|
||||
ia = o.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = InvoiceAddress(order=o)
|
||||
|
||||
if send_to_orders and o.email:
|
||||
if send_to_attendees:
|
||||
parent_op = None
|
||||
sent_to_positions = set()
|
||||
for p in positions:
|
||||
if p.addon_to_id is None:
|
||||
# this op might have matching add-ons, so save for later
|
||||
parent_op = p
|
||||
elif not parent_op or p.addon_to_id != parent_op.id:
|
||||
# this op is an add-on, but not to the current parent_op
|
||||
# something got mixed up as add-ons should always come directly after their parent
|
||||
logger.warning(f"Add-ons are mixed up for position #{p.positionid} in order {o.full_code}")
|
||||
continue
|
||||
|
||||
if p.id not in position_ids:
|
||||
# not a matching op, just there for parent_op
|
||||
continue
|
||||
|
||||
if p.addon_to_id and (
|
||||
not p.attendee_email or p.attendee_email == parent_op.attendee_email
|
||||
):
|
||||
# if op is addon and either has no mail or the same as parent => send to parent
|
||||
p = parent_op
|
||||
|
||||
if p.pk in sent_to_positions:
|
||||
# this position already got an email
|
||||
continue
|
||||
|
||||
if not p.attendee_email or p.attendee_email == o.email:
|
||||
# no email or same as order => send to order
|
||||
send_to_order = True
|
||||
continue
|
||||
|
||||
email_ctx = get_email_context(
|
||||
event=e,
|
||||
order=o,
|
||||
invoice_address=ia,
|
||||
position=p,
|
||||
event_or_subevent=self.subevent or e,
|
||||
)
|
||||
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||
attach_ical=self.rule.attach_ical,
|
||||
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
|
||||
sent_to_positions.add(p.pk)
|
||||
|
||||
if send_to_order and o.email:
|
||||
email_ctx = get_email_context(
|
||||
event=e,
|
||||
order=o,
|
||||
@@ -184,38 +231,6 @@ class ScheduledMail(models.Model):
|
||||
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||
attach_ical=self.rule.attach_ical,
|
||||
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
||||
o_sent = True
|
||||
|
||||
if send_to_attendees:
|
||||
if not self.rule.all_products:
|
||||
positions = [p for p in positions if p.item_id in limit_products]
|
||||
if self.subevent_id:
|
||||
positions = [p for p in positions if p.subevent_id == self.subevent_id]
|
||||
|
||||
for p in positions:
|
||||
if p.id in position_ids:
|
||||
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
|
||||
email_ctx = get_email_context(
|
||||
event=e,
|
||||
order=o,
|
||||
invoice_address=ia,
|
||||
position=p,
|
||||
event_or_subevent=self.subevent or e,
|
||||
)
|
||||
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||
attach_ical=self.rule.attach_ical,
|
||||
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
|
||||
elif not o_sent and o.email:
|
||||
email_ctx = get_email_context(
|
||||
event=e,
|
||||
order=o,
|
||||
invoice_address=ia,
|
||||
event_or_subevent=self.subevent or e,
|
||||
)
|
||||
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||
attach_ical=self.rule.attach_ical,
|
||||
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
||||
o_sent = True
|
||||
|
||||
self.last_successful_order_id = o.pk
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
@@ -43,6 +44,8 @@ from pretix.base.services.mail import mail
|
||||
from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.celery_app import app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _chunks(lst, n):
|
||||
"""
|
||||
@@ -70,6 +73,8 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = InvoiceAddress(order=o)
|
||||
|
||||
parent_op = None
|
||||
sent_to_positions = set()
|
||||
if recipients in ('both', 'attendees'):
|
||||
for p in o.positions.annotate(
|
||||
any_checkins=Exists(
|
||||
@@ -85,10 +90,16 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
|
||||
)
|
||||
),
|
||||
).prefetch_related('addons', 'subevent'):
|
||||
if p.addon_to_id is not None:
|
||||
|
||||
if p.addon_to_id is None:
|
||||
parent_op = p
|
||||
elif not parent_op or p.addon_to_id != parent_op.id:
|
||||
# this op is an add-on, but not to the current parent_op
|
||||
# something got mixed up as add-ons should always come directly after their parent
|
||||
logger.warning(f"Add-ons are mixed up for position #{p.positionid} in order {o.full_code}")
|
||||
continue
|
||||
|
||||
if p.item_id not in items and not any(a.item_id in items for a in p.addons.all()):
|
||||
if p.item_id not in items:
|
||||
continue
|
||||
|
||||
if filter_checkins:
|
||||
@@ -99,13 +110,11 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
|
||||
if not allowed:
|
||||
continue
|
||||
|
||||
if not p.attendee_email:
|
||||
if recipients == 'attendees':
|
||||
send_to_order = True
|
||||
continue
|
||||
|
||||
if p.attendee_email == o.email and send_to_order:
|
||||
continue
|
||||
if p.addon_to_id and (
|
||||
not p.attendee_email or p.attendee_email == parent_op.attendee_email
|
||||
):
|
||||
# if op is addon and either has no mail or the same as parent => send to parent
|
||||
p = parent_op
|
||||
|
||||
if subevent and p.subevent_id != subevent:
|
||||
continue
|
||||
@@ -116,6 +125,18 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
|
||||
if subevents_to and p.subevent.date_from >= subevents_to:
|
||||
continue
|
||||
|
||||
if not p.attendee_email:
|
||||
send_to_order = True
|
||||
continue
|
||||
|
||||
if p.pk in sent_to_positions:
|
||||
# this position already got an email
|
||||
continue
|
||||
|
||||
if p.attendee_email == o.email:
|
||||
send_to_order = True
|
||||
continue
|
||||
|
||||
with language(o.locale, event.settings.region):
|
||||
email_context = get_email_context(event=event, order=o, invoice_address=ia, position=p)
|
||||
outgoing_mail = mail(
|
||||
@@ -137,6 +158,7 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
|
||||
user=user,
|
||||
data=outgoing_mail.log_data(),
|
||||
)
|
||||
sent_to_positions.add(p.pk)
|
||||
|
||||
if send_to_order and o.email:
|
||||
with language(o.locale, event.settings.region):
|
||||
|
||||
@@ -183,7 +183,6 @@ class OrderPositionDetailMixin(NoSearchIndexViewMixin):
|
||||
def position(self):
|
||||
qs = OrderPosition.objects.filter(
|
||||
order__event=self.request.event,
|
||||
addon_to__isnull=True,
|
||||
order__code=self.kwargs['order'],
|
||||
positionid=self.kwargs['position']
|
||||
).select_related('order', 'order__event')
|
||||
|
||||
@@ -87,8 +87,6 @@ dialog::backdrop {
|
||||
100% { transform: skewX(0deg); }
|
||||
}
|
||||
|
||||
.modal-card.no-padding, .modal-card.no-padding .modal-card-content { padding: 0; }
|
||||
.modal-card.no-scroll { overflow: hidden; }
|
||||
|
||||
|
||||
/* Legacy dialogs (still used for #ajaxerr and #popupmodal) */
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'pretix:notify-parent',
|
||||
data: JSON.parse(document.getElementById('notify_info').textContent),
|
||||
}, location.origin)
|
||||
} catch (e) {
|
||||
console.error('Could not post message to parent.', e)
|
||||
}
|
||||
@@ -72,10 +72,6 @@ $(document).ajaxError(function (event, jqXHR, settings, thrownError) {
|
||||
})
|
||||
|
||||
let form_handlers = function (el) {
|
||||
// Prevent running multiple times on the same elements
|
||||
if (el.is('[data-formset-form-handlers-created]')) return
|
||||
el.find('[data-formset-form]').attr('data-formset-form-handlers-created', 'true')
|
||||
|
||||
el.trigger('rescan.areYouSure')
|
||||
el.find('[data-formset]').formset(
|
||||
{
|
||||
@@ -446,15 +442,6 @@ let form_handlers = function (el) {
|
||||
})
|
||||
})
|
||||
|
||||
function addClearButton($input) {
|
||||
var $btn = $("<button class='clear-input-button'><span class='fa fa-times'></span></button>").insertAfter($input).on('click', function(e) {
|
||||
$input.val('').trigger('input').trigger('change'); e.preventDefault()
|
||||
}).toggle($input.val() !== '')
|
||||
$input.on('input', function() {
|
||||
$btn.toggle($input.val() !== '')
|
||||
})
|
||||
}
|
||||
|
||||
el.find('div.scrolling-choice:not(.no-search)').each(function () {
|
||||
if ($(this).find('input[type=text]').length > 0) {
|
||||
return
|
||||
@@ -462,7 +449,6 @@ let form_handlers = function (el) {
|
||||
let $menu = $('<div>').addClass('choice-options-menu')
|
||||
let $inp_search = $('<input>').addClass('form-control').attr('type', 'text').attr('placeholder', gettext('Search query'))
|
||||
$menu.append($inp_search)
|
||||
addClearButton($inp_search)
|
||||
$(this).prepend($menu)
|
||||
|
||||
$inp_search.on('keyup change', function (e) {
|
||||
@@ -491,7 +477,6 @@ let form_handlers = function (el) {
|
||||
$menu.append($lbl_tgl)
|
||||
}
|
||||
$(this).prepend($menu)
|
||||
addClearButton($inp_search)
|
||||
|
||||
$(this).find('.choice-options-none').click(function (e) {
|
||||
$(this).closest('.scrolling-multiple-choice').find('input[type=checkbox]:not(.menu-checkbox)').prop('checked', false)
|
||||
@@ -751,27 +736,6 @@ let form_handlers = function (el) {
|
||||
questions_toggle_dependent()
|
||||
questions_init_photos(el)
|
||||
|
||||
el.find("[data-iframe-dialog]").on("click", function(e) {
|
||||
let url;
|
||||
if (this.tagName === "A" && !this.hasAttribute("data-iframe-dialog-url")) {
|
||||
url = this.getAttribute("href")
|
||||
url += (url.includes('?') ? '&' : '?') + 'notify_parent=true'
|
||||
} else {
|
||||
url = this.getAttribute("data-iframe-dialog-url")
|
||||
}
|
||||
show_iframe_dialog(url, (data) => {
|
||||
if (this.getAttribute("data-iframe-dialog-target")) {
|
||||
var $target = findDependency(this.getAttribute("data-iframe-dialog-target"), this)
|
||||
if ($target.is('select')) {
|
||||
$target.append(new Option(data.object_str, data.object, false, true)).trigger('change')
|
||||
}
|
||||
}
|
||||
})
|
||||
e.preventDefault()
|
||||
})
|
||||
}
|
||||
|
||||
function setup_placeholders() {
|
||||
let lastFocusedInput
|
||||
$(document).on('focusin', 'input, textarea', function (e) {
|
||||
lastFocusedInput = e.target
|
||||
@@ -849,7 +813,13 @@ function setup_basics (el) {
|
||||
return
|
||||
}
|
||||
|
||||
$('<li><a href="#' + scrollTarget.id + '">' + $.trim(label) + '</a> – ' + description + '</li>')
|
||||
$('<li></li>')
|
||||
.append(
|
||||
$('<a></a>').attr("href", "#" + scrollTarget.id).text($.trim(label))
|
||||
)
|
||||
.append(
|
||||
document.createTextNode(' - ' + description)
|
||||
)
|
||||
.appendTo(alert.querySelector('ul') || $('<ul>').appendTo(alert))
|
||||
.find('a').on('click', function (e) {
|
||||
$panel.collapse('show')
|
||||
@@ -1115,7 +1085,6 @@ $(function () {
|
||||
|
||||
setup_basics($('body'))
|
||||
form_handlers($('body'))
|
||||
setup_placeholders()
|
||||
$(document).trigger('pretix:bind-forms')
|
||||
|
||||
$('#ajaxerr').on('click', '.ajaxerr-close', ajaxErrDialog.hide)
|
||||
@@ -1158,66 +1127,3 @@ $(function () {
|
||||
return $(this).find('button:not([type=button]), input[type=submit]').length > 0
|
||||
}).areYouSure({ message: gettext('You have unsaved changes!') })
|
||||
})
|
||||
function show_iframe_dialog(url, callback) {
|
||||
function messageEvent(e) {
|
||||
if (e.origin === location.origin && e.data.type === 'pretix:dialog-loaded') {
|
||||
$dlg.find("iframe").attr("height", Math.min(window.innerHeight - 120, e.data.contentHeight|0)).css("visibility", "visible")
|
||||
$dlg.find("center").remove()
|
||||
}
|
||||
if (e.origin === location.origin && e.data.type === 'pretix:notify-parent') {
|
||||
$dlg[0].close()
|
||||
if (!callback(e.data.data)) {
|
||||
if (e.data.data.messages?.length) {
|
||||
alert(e.data.data.messages.map(m => m.message).join('\n\n'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var $dlg = $('<dialog class="modal-card no-padding no-scroll" closedby="any"><center><i class="fa fa-cog big-rotating-icon"></i></center><iframe height="100" width="100%"></iframe></div>')
|
||||
.css('max-width', '60em')
|
||||
$dlg.find("iframe").attr("src", url).css("visibility", "hidden").css("border", "0")
|
||||
window.addEventListener('message', messageEvent)
|
||||
$dlg.appendTo("body")
|
||||
$dlg.on('close', function() {
|
||||
window.removeEventListener('message', messageEvent)
|
||||
$dlg.remove()
|
||||
})
|
||||
$dlg[0].showModal()
|
||||
}
|
||||
function setup_dialog_frame_events() {
|
||||
var dom_ready = false, parent_responded = false
|
||||
function notify_dialog_loaded() {
|
||||
console.log('ready', window.innerWidth, window.innerHeight, $('#page-wrapper > .container-fluid').outerHeight() + 20)
|
||||
window.parent.postMessage({
|
||||
type: 'pretix:dialog-loaded',
|
||||
contentHeight: $('#page-wrapper > .container-fluid').outerHeight() + 20,
|
||||
title: document.title,
|
||||
}, location.origin)
|
||||
}
|
||||
window.addEventListener('message', function(e) {
|
||||
if (e.source === window) return // don't handle messages from ourselves
|
||||
if (e.origin !== location.origin) return // only handle messages from same origin
|
||||
|
||||
if (e.data.type === 'pretix:dialog-handshake') {
|
||||
// we are inside an iframe in a dialog
|
||||
if (!parent_responded) {
|
||||
window.document.documentElement.classList.add('in-iframe')
|
||||
if (dom_ready) setTimeout(notify_dialog_loaded, 1)
|
||||
parent_responded = true
|
||||
}
|
||||
} else if (e.data.type === 'pretix:dialog-loading') {
|
||||
// we are parent to a dialog
|
||||
e.source.postMessage({ type: 'pretix:dialog-handshake' })
|
||||
}
|
||||
})
|
||||
$(function () {
|
||||
if (parent_responded) setTimeout(notify_dialog_loaded, 1)
|
||||
dom_ready = true
|
||||
})
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'pretix:dialog-loading',
|
||||
}, location.origin)
|
||||
} catch {}
|
||||
}
|
||||
setup_dialog_frame_events()
|
||||
|
||||
@@ -417,8 +417,6 @@ div.scrolling-multiple-choice, div.scrolling-choice {
|
||||
}
|
||||
}
|
||||
}
|
||||
.clear-input-button { width: 2em; margin-left: -3em; margin-right: 1em; height: 2em; background: transparent; border: 0; color: #aaa; }
|
||||
.clear-input-button:hover { color: #777; }
|
||||
.team-permission-groups {
|
||||
border: 1px solid $input-border;
|
||||
border-radius: $input-border-radius;
|
||||
|
||||
@@ -898,10 +898,6 @@ tbody th {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
html.in-iframe body { background: white }
|
||||
.in-iframe #wrapper > nav.navbar, .in-iframe #page-wrapper > div > footer { display: none; }
|
||||
.in-iframe #page-wrapper { padding: 0 15px; min-height: 568px; border-left: 0; margin: 0; }
|
||||
|
||||
@import "../../pretixbase/scss/_rtl.scss";
|
||||
@import "../../bootstrap/scss/_rtl.scss";
|
||||
@import "_rtl.scss";
|
||||
|
||||
@@ -157,6 +157,12 @@
|
||||
.profile-scope:last-child .profile-save-container {
|
||||
margin-bottom: -15px;
|
||||
}
|
||||
|
||||
/* the rule above removes blank space after any profile-save-container at the end of every fieldset, which causes an overlay if addon-questions are used. So re-add margin for non-addon-fieldsets followed by fieldsets (currently only happens it addon-questions are available) */
|
||||
fieldset:not(:has(.profile-add-on)):has( + fieldset) .profile-scope:last-child .profile-save-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.profile-save-container .help-block {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -199,6 +199,84 @@ def test_sendmail_rule_send_order_vs_pos(send_to, amount_mails, recipients, orde
|
||||
assert djmail.outbox[0].subject == 'Dummy: Foo City @ 11:30'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('send_to,amount_mails,recipients,ticket_mail,addon_mail, products', [
|
||||
(Rule.ATTENDEES, 1, ['addon-attendee@dummy.test'], 'attendee@dummy.test', 'addon-attendee@dummy.test', 'addon'),
|
||||
(Rule.ATTENDEES, 2, ['attendee@dummy.test', 'addon-attendee@dummy.test'], 'attendee@dummy.test',
|
||||
'addon-attendee@dummy.test', 'both'),
|
||||
(Rule.ATTENDEES, 1, ['attendee@dummy.test'], 'attendee@dummy.test', 'attendee@dummy.test', 'both'),
|
||||
(Rule.ATTENDEES, 1, ['attendee@dummy.test'], 'attendee@dummy.test', None, 'addon'),
|
||||
(Rule.ATTENDEES, 1, ['attendee@dummy.test'], 'attendee@dummy.test', None, 'both'),
|
||||
(Rule.ATTENDEES, 1, ['dummy@dummy.test'], None, None, 'addon'),
|
||||
(Rule.ATTENDEES, 1, ['dummy@dummy.test'], None, None, 'both'),
|
||||
(Rule.ATTENDEES, 2, ['dummy@dummy.test', 'addon-attendee@dummy.test'], None, 'addon-attendee@dummy.test', 'both'),
|
||||
(Rule.ATTENDEES, 1, ['dummy@dummy.test'], None, None, 'all'),
|
||||
(Rule.ATTENDEES, 2, ['dummy@dummy.test', 'addon-attendee@dummy.test'], None, 'addon-attendee@dummy.test', 'all'),
|
||||
])
|
||||
@scopes_disabled()
|
||||
def test_sendmail_rule_send_addons(send_to, amount_mails, recipients, ticket_mail, addon_mail, products, order,
|
||||
event, pos, item, item2):
|
||||
djmail.outbox = []
|
||||
|
||||
order.status = order.STATUS_PAID
|
||||
order.save()
|
||||
|
||||
p = pos
|
||||
p.attendee_email = ticket_mail
|
||||
p.save()
|
||||
order.all_positions.create(item=item2, price=0, attendee_email=addon_mail, addon_to=p)
|
||||
rule = order.event.sendmail_rules.create(date_is_absolute=True, send_date=dt_now - datetime.timedelta(hours=1),
|
||||
send_to=send_to, subject='meow', template='meow meow meow',
|
||||
all_products=products == 'all')
|
||||
if products == 'addon':
|
||||
rule.limit_products.set([item2])
|
||||
if products == 'both':
|
||||
rule.limit_products.set([item, item2])
|
||||
|
||||
sendmail_run_rules(None)
|
||||
|
||||
assert len(djmail.outbox) == amount_mails
|
||||
|
||||
_recipients = [mail.to[0] for mail in djmail.outbox]
|
||||
assert set(recipients) == set(_recipients)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('send_to,amount_mails,recipients,ticket_mail,addon_mail, products', [
|
||||
(Rule.ATTENDEES, 2, ['attendee@dummy.test', 'addon-attendee@dummy.test'], 'attendee@dummy.test',
|
||||
'addon-attendee@dummy.test', 'addon'),
|
||||
(Rule.ATTENDEES, 2, ['attendee@dummy.test', 'addon-attendee@dummy.test'], 'attendee@dummy.test',
|
||||
'addon-attendee@dummy.test', 'both'),
|
||||
])
|
||||
@scopes_disabled()
|
||||
def test_sendmail_rule_send_addons_one_unp(send_to, amount_mails, recipients, ticket_mail, addon_mail, products, order,
|
||||
event, pos, item, item2):
|
||||
djmail.outbox = []
|
||||
|
||||
order.status = order.STATUS_PAID
|
||||
order.save()
|
||||
|
||||
p = pos
|
||||
p.attendee_email = ticket_mail
|
||||
p.save()
|
||||
order.all_positions.create(item=item2, price=0, attendee_email=addon_mail, addon_to=p)
|
||||
order.all_positions.create(item=item2, price=0, addon_to=p)
|
||||
rule = order.event.sendmail_rules.create(date_is_absolute=True, send_date=dt_now - datetime.timedelta(hours=1),
|
||||
send_to=send_to, subject='meow', template='meow meow meow',
|
||||
all_products=False)
|
||||
if products == 'addon':
|
||||
rule.limit_products.set([item2])
|
||||
if products == 'both':
|
||||
rule.limit_products.set([item, item2])
|
||||
|
||||
sendmail_run_rules(None)
|
||||
|
||||
assert len(djmail.outbox) == amount_mails
|
||||
|
||||
_recipients = [mail.to[0] for mail in djmail.outbox]
|
||||
assert set(recipients) == set(_recipients)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_sendmail_rule_send_attendees_unset_mail(order, event, item):
|
||||
|
||||
@@ -406,6 +406,312 @@ def test_sendmail_attendee_product_filter(logged_in_client, sendmail_url, event,
|
||||
assert '/order/' not in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_addon_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
p = pos
|
||||
p.attendee_email = 'attendee1@dummy.test'
|
||||
p.save()
|
||||
order.positions.create(
|
||||
item=addon, price=0, attendee_email='add-on-attendee@dummy.test', addon_to=p
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': addon.pk,
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == ['add-on-attendee@dummy.test']
|
||||
assert '/ticket/' in djmail.outbox[0].body
|
||||
assert '/order/' not in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_ticket_and_addon_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
p = pos
|
||||
p.attendee_email = 'attendee1@dummy.test'
|
||||
p.save()
|
||||
order.positions.create(
|
||||
item=addon, price=0, attendee_email='add-on-attendee@dummy.test', addon_to=p
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': {addon.pk, p.item_id},
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 2
|
||||
for msg in djmail.outbox:
|
||||
assert msg.to in [['attendee1@dummy.test'], ['add-on-attendee@dummy.test']]
|
||||
assert '/ticket/' in msg.body
|
||||
assert '/order/' not in msg.body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_ticket_and_same_addon_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
p = pos
|
||||
p.attendee_email = 'attendee1@dummy.test'
|
||||
p.save()
|
||||
order.positions.create(
|
||||
item=addon, price=0, attendee_email='attendee1@dummy.test', addon_to=p
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': {addon.pk, p.item_id},
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == ['attendee1@dummy.test']
|
||||
assert '/ticket/' in djmail.outbox[0].body
|
||||
assert '/order/' not in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_addon_unpersonalized_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
p = pos
|
||||
p.attendee_email = 'attendee1@dummy.test'
|
||||
p.save()
|
||||
order.positions.create(
|
||||
item=addon, price=0, addon_to=p
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': addon.pk,
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == ['attendee1@dummy.test']
|
||||
assert '/ticket/' in djmail.outbox[0].body
|
||||
assert '/order/' not in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_ticket_and_addon_unp_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
p = pos
|
||||
p.attendee_email = 'attendee1@dummy.test'
|
||||
p.save()
|
||||
order.positions.create(
|
||||
item=addon, price=0, addon_to=p
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': {addon.pk, p.item_id},
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == ['attendee1@dummy.test']
|
||||
assert '/ticket/' in djmail.outbox[0].body
|
||||
assert '/order/' not in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_ticket_unp_and_addon_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
order.positions.create(
|
||||
item=addon, price=0, attendee_email='add-on-attendee@dummy.test', addon_to=pos
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': {addon.pk, pos.item_id},
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 2
|
||||
for msg in djmail.outbox:
|
||||
assert msg.to in [[order.email], ['add-on-attendee@dummy.test']]
|
||||
if msg.to == [order.email]:
|
||||
assert '/ticket/' not in msg.body
|
||||
assert '/order/' in msg.body
|
||||
else:
|
||||
assert msg.to == ['add-on-attendee@dummy.test']
|
||||
assert '/ticket/' in msg.body
|
||||
assert '/order/' not in msg.body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_addon_unp_unp_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
order.positions.create(
|
||||
item=addon, price=0, addon_to=pos
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': addon.pk,
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [order.email]
|
||||
assert '/ticket/' not in djmail.outbox[0].body
|
||||
assert '/order/' in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_and_addon_unp_unp_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
order.positions.create(
|
||||
item=addon, price=0, addon_to=pos
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': {addon.pk, pos.item_id},
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [order.email]
|
||||
assert '/ticket/' not in djmail.outbox[0].body
|
||||
assert '/order/' in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_two_addons_one_unp_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
p = pos
|
||||
p.attendee_email = 'attendee1@dummy.test'
|
||||
p.save()
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
order.positions.create(
|
||||
item=addon, price=0, attendee_email='add-on-attendee@dummy.test', addon_to=p
|
||||
)
|
||||
order.positions.create(
|
||||
item=addon, price=0, addon_to=p
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': addon.pk,
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 2
|
||||
for msg in djmail.outbox:
|
||||
assert msg.to in [['attendee1@dummy.test'], ['add-on-attendee@dummy.test']]
|
||||
assert '/ticket/' in msg.body
|
||||
assert '/order/' not in msg.body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_and_two_addons_one_unp_filter(logged_in_client, sendmail_url, event, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
with scopes_disabled():
|
||||
p = pos
|
||||
p.attendee_email = 'attendee1@dummy.test'
|
||||
p.save()
|
||||
addon = Item.objects.create(name='Test addon', event=event, default_price=12)
|
||||
order.positions.create(
|
||||
item=addon, price=0, attendee_email='add-on-attendee@dummy.test', addon_to=p
|
||||
)
|
||||
order.positions.create(
|
||||
item=addon, price=0, addon_to=p
|
||||
)
|
||||
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url + 'orders/',
|
||||
{'sendto': 'na',
|
||||
'action': 'send',
|
||||
'recipients': 'attendees',
|
||||
'items': {addon.pk, p.item_id},
|
||||
'subject_0': 'Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
},
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'alert-success' in response.rendered_content
|
||||
assert len(djmail.outbox) == 2
|
||||
for msg in djmail.outbox:
|
||||
assert msg.to in [['attendee1@dummy.test'], ['add-on-attendee@dummy.test']]
|
||||
assert '/ticket/' in msg.body
|
||||
assert '/order/' not in msg.body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_attendee_subevent_filter(logged_in_client, sendmail_url, event, item, order, pos):
|
||||
event.settings.attendee_emails_asked = True
|
||||
|
||||
Reference in New Issue
Block a user