mirror of
https://github.com/pretix/pretix.git
synced 2026-10-02 19:24:42 +00:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc52dc81f3 | ||
|
|
f3dbbce39c | ||
|
|
a2cef22ea8 | ||
|
|
3843448812 | ||
|
|
49893ca9df | ||
|
|
4eade5070e | ||
|
|
32b1997208 | ||
|
|
eaf4a310f6 | ||
|
|
8dc0f7c1b2 | ||
|
|
dd3e6c4692 | ||
|
|
c7437336b4 | ||
|
|
4c0c775baa | ||
|
|
394652a5ff | ||
|
|
3f50d065ec | ||
|
|
4121061267 | ||
|
|
aed2220139 | ||
|
|
4b2c54d38e | ||
|
|
0113a3dc1f | ||
|
|
c12a8935f1 | ||
|
|
a86a6cc2c7 | ||
|
|
fec2b9a2fc | ||
|
|
d847a7e8f8 | ||
|
|
c58a968196 | ||
|
|
81cbaca162 | ||
|
|
218df7a49f | ||
|
|
f64343d977 | ||
|
|
b36c7cbef3 | ||
|
|
18b39ba7cd |
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# 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/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2026.3.0.dev0"
|
__version__ = "2026.4.0.dev0"
|
||||||
|
|||||||
@@ -196,8 +196,7 @@ class RegistrationForm(forms.Form):
|
|||||||
def clean_password(self):
|
def clean_password(self):
|
||||||
password1 = self.cleaned_data.get('password', '')
|
password1 = self.cleaned_data.get('password', '')
|
||||||
user = User(email=self.cleaned_data.get('email'))
|
user = User(email=self.cleaned_data.get('email'))
|
||||||
if validate_password(password1, user=user) is not None:
|
validate_password(password1, user=user)
|
||||||
raise forms.ValidationError(_(password_validators_help_texts()), code='pw_invalid')
|
|
||||||
return password1
|
return password1
|
||||||
|
|
||||||
def clean_email(self):
|
def clean_email(self):
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ def mail_send_task(self, **kwargs) -> bool:
|
|||||||
try:
|
try:
|
||||||
outgoing_mail = OutgoingMail.objects.select_for_update(of=OF_SELF).get(pk=outgoing_mail)
|
outgoing_mail = OutgoingMail.objects.select_for_update(of=OF_SELF).get(pk=outgoing_mail)
|
||||||
except OutgoingMail.DoesNotExist:
|
except OutgoingMail.DoesNotExist:
|
||||||
logger.info(f"Ignoring job for non existing email {outgoing_mail.guid}")
|
logger.info(f"Ignoring job for non existing email {outgoing_mail}")
|
||||||
return False
|
return False
|
||||||
if outgoing_mail.status == OutgoingMail.STATUS_INFLIGHT:
|
if outgoing_mail.status == OutgoingMail.STATUS_INFLIGHT:
|
||||||
logger.info(f"Ignoring job for inflight email {outgoing_mail.guid}")
|
logger.info(f"Ignoring job for inflight email {outgoing_mail.guid}")
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ from pretix.base.email import get_email_context
|
|||||||
from pretix.base.i18n import get_language_without_region, language
|
from pretix.base.i18n import get_language_without_region, language
|
||||||
from pretix.base.media import MEDIA_TYPES
|
from pretix.base.media import MEDIA_TYPES
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Device, Event, GiftCard, Item, ItemVariation, Membership,
|
CartPosition, Device, Event, GiftCard, Item, ItemVariation, LogEntry,
|
||||||
Order, OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User,
|
Membership, Order, OrderPayment, OrderPosition, Quota, Seat,
|
||||||
Voucher,
|
SeatCategoryMapping, User, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.models.event import SubEvent
|
from pretix.base.models.event import SubEvent
|
||||||
from pretix.base.models.orders import (
|
from pretix.base.models.orders import (
|
||||||
@@ -1618,7 +1618,7 @@ class OrderChangeManager:
|
|||||||
MembershipOperation = namedtuple('MembershipOperation', ('position', 'membership'))
|
MembershipOperation = namedtuple('MembershipOperation', ('position', 'membership'))
|
||||||
CancelOperation = namedtuple('CancelOperation', ('position', 'price_diff'))
|
CancelOperation = namedtuple('CancelOperation', ('position', 'price_diff'))
|
||||||
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent', 'seat', 'membership',
|
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent', 'seat', 'membership',
|
||||||
'valid_from', 'valid_until', 'is_bundled', 'result'))
|
'valid_from', 'valid_until', 'is_bundled', 'result', 'count'))
|
||||||
SplitOperation = namedtuple('SplitOperation', ('position',))
|
SplitOperation = namedtuple('SplitOperation', ('position',))
|
||||||
FeeValueOperation = namedtuple('FeeValueOperation', ('fee', 'value', 'price_diff'))
|
FeeValueOperation = namedtuple('FeeValueOperation', ('fee', 'value', 'price_diff'))
|
||||||
AddFeeOperation = namedtuple('AddFeeOperation', ('fee', 'price_diff'))
|
AddFeeOperation = namedtuple('AddFeeOperation', ('fee', 'price_diff'))
|
||||||
@@ -1632,16 +1632,24 @@ class OrderChangeManager:
|
|||||||
ForceRecomputeOperation = namedtuple('ForceRecomputeOperation', tuple())
|
ForceRecomputeOperation = namedtuple('ForceRecomputeOperation', tuple())
|
||||||
|
|
||||||
class AddPositionResult:
|
class AddPositionResult:
|
||||||
_position: Optional[OrderPosition]
|
_positions: Optional[List[OrderPosition]]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._position = None
|
self._positions = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def position(self) -> OrderPosition:
|
def position(self) -> OrderPosition:
|
||||||
if self._position is None:
|
if self._positions is None:
|
||||||
raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.")
|
raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.")
|
||||||
return self._position
|
if len(self._positions) != 1:
|
||||||
|
raise RuntimeError("More than one position created.")
|
||||||
|
return self._positions[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def positions(self) -> List[OrderPosition]:
|
||||||
|
if self._positions is None:
|
||||||
|
raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.")
|
||||||
|
return self._positions
|
||||||
|
|
||||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False):
|
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False):
|
||||||
self.order = order
|
self.order = order
|
||||||
@@ -1848,8 +1856,12 @@ class OrderChangeManager:
|
|||||||
|
|
||||||
def add_position(self, item: Item, variation: ItemVariation, price: Decimal, addon_to: OrderPosition = None,
|
def add_position(self, item: Item, variation: ItemVariation, price: Decimal, addon_to: OrderPosition = None,
|
||||||
subevent: SubEvent = None, seat: Seat = None, membership: Membership = None,
|
subevent: SubEvent = None, seat: Seat = None, membership: Membership = None,
|
||||||
valid_from: datetime = None, valid_until: datetime = None) -> 'OrderChangeManager.AddPositionResult':
|
valid_from: datetime = None, valid_until: datetime = None, count: int = 1) -> 'OrderChangeManager.AddPositionResult':
|
||||||
|
if count < 1:
|
||||||
|
raise ValueError("Count must be positive")
|
||||||
if isinstance(seat, str):
|
if isinstance(seat, str):
|
||||||
|
if count > 1:
|
||||||
|
raise ValueError("Cannot combine count > 1 with seat")
|
||||||
if not seat:
|
if not seat:
|
||||||
seat = None
|
seat = None
|
||||||
else:
|
else:
|
||||||
@@ -1903,14 +1915,14 @@ class OrderChangeManager:
|
|||||||
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00'):
|
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00'):
|
||||||
self._invoice_dirty = True
|
self._invoice_dirty = True
|
||||||
|
|
||||||
self._totaldiff_guesstimate += price.gross
|
self._totaldiff_guesstimate += price.gross * count
|
||||||
self._quotadiff.update(new_quotas)
|
self._quotadiff.update({q: count for q in new_quotas})
|
||||||
if seat:
|
if seat:
|
||||||
self._seatdiff.update([seat])
|
self._seatdiff.update([seat])
|
||||||
|
|
||||||
result = self.AddPositionResult()
|
result = self.AddPositionResult()
|
||||||
self._operations.append(self.AddOperation(item, variation, price, addon_to, subevent, seat, membership,
|
self._operations.append(self.AddOperation(item, variation, price, addon_to, subevent, seat, membership,
|
||||||
valid_from, valid_until, is_bundled, result))
|
valid_from, valid_until, is_bundled, result, count))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def split(self, position: OrderPosition):
|
def split(self, position: OrderPosition):
|
||||||
@@ -2530,29 +2542,35 @@ class OrderChangeManager:
|
|||||||
secret_dirty.remove(position)
|
secret_dirty.remove(position)
|
||||||
position.save(update_fields=['canceled', 'secret'])
|
position.save(update_fields=['canceled', 'secret'])
|
||||||
elif isinstance(op, self.AddOperation):
|
elif isinstance(op, self.AddOperation):
|
||||||
pos = OrderPosition.objects.create(
|
new_pos = []
|
||||||
item=op.item, variation=op.variation, addon_to=op.addon_to,
|
new_logs = []
|
||||||
price=op.price.gross, order=self.order, tax_rate=op.price.rate, tax_code=op.price.code,
|
for i in range(op.count):
|
||||||
tax_value=op.price.tax, tax_rule=op.item.tax_rule,
|
pos = OrderPosition.objects.create(
|
||||||
positionid=nextposid, subevent=op.subevent, seat=op.seat,
|
item=op.item, variation=op.variation, addon_to=op.addon_to,
|
||||||
used_membership=op.membership, valid_from=op.valid_from, valid_until=op.valid_until,
|
price=op.price.gross, order=self.order, tax_rate=op.price.rate, tax_code=op.price.code,
|
||||||
is_bundled=op.is_bundled,
|
tax_value=op.price.tax, tax_rule=op.item.tax_rule,
|
||||||
)
|
positionid=nextposid, subevent=op.subevent, seat=op.seat,
|
||||||
nextposid += 1
|
used_membership=op.membership, valid_from=op.valid_from, valid_until=op.valid_until,
|
||||||
self.order.log_action('pretix.event.order.changed.add', user=self.user, auth=self.auth, data={
|
is_bundled=op.is_bundled,
|
||||||
'position': pos.pk,
|
)
|
||||||
'item': op.item.pk,
|
nextposid += 1
|
||||||
'variation': op.variation.pk if op.variation else None,
|
new_pos.append(pos)
|
||||||
'addon_to': op.addon_to.pk if op.addon_to else None,
|
new_logs.append(self.order.log_action('pretix.event.order.changed.add', user=self.user, auth=self.auth, data={
|
||||||
'price': op.price.gross,
|
'position': pos.pk,
|
||||||
'positionid': pos.positionid,
|
'item': op.item.pk,
|
||||||
'membership': pos.used_membership_id,
|
'variation': op.variation.pk if op.variation else None,
|
||||||
'subevent': op.subevent.pk if op.subevent else None,
|
'addon_to': op.addon_to.pk if op.addon_to else None,
|
||||||
'seat': op.seat.pk if op.seat else None,
|
'price': op.price.gross,
|
||||||
'valid_from': op.valid_from.isoformat() if op.valid_from else None,
|
'positionid': pos.positionid,
|
||||||
'valid_until': op.valid_until.isoformat() if op.valid_until else None,
|
'membership': pos.used_membership_id,
|
||||||
})
|
'subevent': op.subevent.pk if op.subevent else None,
|
||||||
op.result._position = pos
|
'seat': op.seat.pk if op.seat else None,
|
||||||
|
'valid_from': op.valid_from.isoformat() if op.valid_from else None,
|
||||||
|
'valid_until': op.valid_until.isoformat() if op.valid_until else None,
|
||||||
|
}, save=False))
|
||||||
|
|
||||||
|
op.result._positions = new_pos
|
||||||
|
LogEntry.bulk_create_and_postprocess(new_logs)
|
||||||
elif isinstance(op, self.SplitOperation):
|
elif isinstance(op, self.SplitOperation):
|
||||||
position = position_cache.setdefault(op.position.pk, op.position)
|
position = position_cache.setdefault(op.position.pk, op.position)
|
||||||
split_positions.append(position)
|
split_positions.append(position)
|
||||||
@@ -2877,7 +2895,7 @@ class OrderChangeManager:
|
|||||||
return total
|
return total
|
||||||
|
|
||||||
def _check_order_size(self):
|
def _check_order_size(self):
|
||||||
if (len(self.order.positions.all()) + len([op for op in self._operations if isinstance(op, self.AddOperation)])) > settings.PRETIX_MAX_ORDER_SIZE:
|
if (len(self.order.positions.all()) + sum([op.count for op in self._operations if isinstance(op, self.AddOperation)])) > settings.PRETIX_MAX_ORDER_SIZE:
|
||||||
raise OrderError(
|
raise OrderError(
|
||||||
self.error_messages['max_order_size'] % {
|
self.error_messages['max_order_size'] % {
|
||||||
'max': settings.PRETIX_MAX_ORDER_SIZE,
|
'max': settings.PRETIX_MAX_ORDER_SIZE,
|
||||||
@@ -2938,7 +2956,7 @@ class OrderChangeManager:
|
|||||||
]) + len([
|
]) + len([
|
||||||
o for o in self._operations if isinstance(o, self.SplitOperation)
|
o for o in self._operations if isinstance(o, self.SplitOperation)
|
||||||
])
|
])
|
||||||
adds = len([o for o in self._operations if isinstance(o, self.AddOperation)])
|
adds = sum([o.count for o in self._operations if isinstance(o, self.AddOperation)])
|
||||||
if current > 0 and current - cancels + adds < 1:
|
if current > 0 and current - cancels + adds < 1:
|
||||||
raise OrderError(self.error_messages['complete_cancel'])
|
raise OrderError(self.error_messages['complete_cancel'])
|
||||||
|
|
||||||
@@ -2985,17 +3003,18 @@ class OrderChangeManager:
|
|||||||
elif isinstance(op, self.CancelOperation) and op.position in positions_to_fake_cart:
|
elif isinstance(op, self.CancelOperation) and op.position in positions_to_fake_cart:
|
||||||
fake_cart.remove(positions_to_fake_cart[op.position])
|
fake_cart.remove(positions_to_fake_cart[op.position])
|
||||||
elif isinstance(op, self.AddOperation):
|
elif isinstance(op, self.AddOperation):
|
||||||
cp = CartPosition(
|
for i in range(op.count):
|
||||||
event=self.event,
|
cp = CartPosition(
|
||||||
item=op.item,
|
event=self.event,
|
||||||
variation=op.variation,
|
item=op.item,
|
||||||
used_membership=op.membership,
|
variation=op.variation,
|
||||||
subevent=op.subevent,
|
used_membership=op.membership,
|
||||||
seat=op.seat,
|
subevent=op.subevent,
|
||||||
)
|
seat=op.seat,
|
||||||
cp.override_valid_from = op.valid_from
|
)
|
||||||
cp.override_valid_until = op.valid_until
|
cp.override_valid_from = op.valid_from
|
||||||
fake_cart.append(cp)
|
cp.override_valid_until = op.valid_until
|
||||||
|
fake_cart.append(cp)
|
||||||
try:
|
try:
|
||||||
validate_memberships_in_order(self.order.customer, fake_cart, self.event, lock=True, ignored_order=self.order, testmode=self.order.testmode)
|
validate_memberships_in_order(self.order.customer, fake_cart, self.event, lock=True, ignored_order=self.order, testmode=self.order.testmode)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
|
|||||||
@@ -331,6 +331,10 @@ class OtherOperationsForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class OrderPositionAddForm(forms.Form):
|
class OrderPositionAddForm(forms.Form):
|
||||||
|
count = forms.IntegerField(
|
||||||
|
label=_('Number of products to add'),
|
||||||
|
initial=1,
|
||||||
|
)
|
||||||
itemvar = forms.ChoiceField(
|
itemvar = forms.ChoiceField(
|
||||||
label=_('Product')
|
label=_('Product')
|
||||||
)
|
)
|
||||||
@@ -432,6 +436,10 @@ class OrderPositionAddForm(forms.Form):
|
|||||||
d['used_membership'] = [m for m in self.memberships if str(m.pk) == d['used_membership']][0]
|
d['used_membership'] = [m for m in self.memberships if str(m.pk) == d['used_membership']][0]
|
||||||
else:
|
else:
|
||||||
d['used_membership'] = None
|
d['used_membership'] = None
|
||||||
|
if d.get("count", 1) and d.get("seat"):
|
||||||
|
raise ValidationError({
|
||||||
|
"seat": _("You can not choose a seat when adding multiple products at once.")
|
||||||
|
})
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -329,6 +329,7 @@
|
|||||||
{{ add_form.custom_error }}
|
{{ add_form.custom_error }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% bootstrap_field add_form.count layout="control" %}
|
||||||
{% bootstrap_field add_form.itemvar layout="control" %}
|
{% bootstrap_field add_form.itemvar layout="control" %}
|
||||||
{% bootstrap_field add_form.price addon_after=request.event.currency layout="control" %}
|
{% bootstrap_field add_form.price addon_after=request.event.currency layout="control" %}
|
||||||
{% if add_form.addon_to %}
|
{% if add_form.addon_to %}
|
||||||
@@ -364,6 +365,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-horizontal">
|
<div class="form-horizontal">
|
||||||
|
{% bootstrap_field add_position_formset.empty_form.count layout="control" %}
|
||||||
{% bootstrap_field add_position_formset.empty_form.itemvar layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.itemvar layout="control" %}
|
||||||
{% bootstrap_field add_position_formset.empty_form.price addon_after=request.event.currency layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.price addon_after=request.event.currency layout="control" %}
|
||||||
{% if add_position_formset.empty_form.addon_to %}
|
{% if add_position_formset.empty_form.addon_to %}
|
||||||
|
|||||||
@@ -8,7 +8,45 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form_errors form %}
|
{% bootstrap_form_errors form %}
|
||||||
{% bootstrap_field form.name layout='horizontal' %}
|
{% bootstrap_field form.name layout='horizontal' %}
|
||||||
{% bootstrap_field form.devicetype layout='horizontal' %}
|
|
||||||
|
<div class="form-group{% if form.devicetype.errors %} has-error{% endif %}">
|
||||||
|
<label class="col-md-3 control-label">{% trans "Device type" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div>
|
||||||
|
<div class="big-radio radio">
|
||||||
|
<label>
|
||||||
|
<input type="radio" required value="totp" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "totp" %}checked{% endif %}>
|
||||||
|
<strong>{% trans "Smartphone with Authenticator app" %}</strong><br>
|
||||||
|
<div class="help-block">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Use your smartphone with any Time-based One-Time-Password app like freeOTP, Google Authenticator or Proton Authenticator.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="big-radio radio">
|
||||||
|
<label>
|
||||||
|
<input type="radio" required value="webauthn" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "webauthn" %}checked{% endif %}>
|
||||||
|
<strong>{% trans "WebAuthn-compatible hardware token" %}</strong><br>
|
||||||
|
<div class="help-block">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Use a hardware token like the Yubikey, or other biometric authentication like fingerprint or face recognition.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if form.devicetype.errors %}
|
||||||
|
<div class="help-block">
|
||||||
|
{% for error in form.devicetype.errors %}
|
||||||
|
<p>{{ error|escape }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group submit-group">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
{% trans "Continue" %}
|
{% trans "Continue" %}
|
||||||
|
|||||||
@@ -28,11 +28,6 @@
|
|||||||
{% trans "iOS (iTunes)" %}
|
{% trans "iOS (iTunes)" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a href="https://m.google.com/authenticator">
|
|
||||||
{% trans "Blackberry (Link via Google)" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -2059,12 +2059,13 @@ class OrderChange(OrderView):
|
|||||||
else:
|
else:
|
||||||
variation = None
|
variation = None
|
||||||
try:
|
try:
|
||||||
ocm.add_position(item, variation,
|
for i in range(f.cleaned_data.get("count", 1)):
|
||||||
f.cleaned_data['price'],
|
ocm.add_position(item, variation,
|
||||||
f.cleaned_data.get('addon_to'),
|
f.cleaned_data['price'],
|
||||||
f.cleaned_data.get('subevent'),
|
f.cleaned_data.get('addon_to'),
|
||||||
f.cleaned_data.get('seat'),
|
f.cleaned_data.get('subevent'),
|
||||||
f.cleaned_data.get('used_membership'))
|
f.cleaned_data.get('seat'),
|
||||||
|
f.cleaned_data.get('used_membership'))
|
||||||
except OrderError as e:
|
except OrderError as e:
|
||||||
f.custom_error = str(e)
|
f.custom_error = str(e)
|
||||||
return False
|
return False
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@ ausgecheckt
|
|||||||
ausgeklappt
|
ausgeklappt
|
||||||
auswahl
|
auswahl
|
||||||
Authentication
|
Authentication
|
||||||
|
Authenticator
|
||||||
Authenticator-App
|
Authenticator-App
|
||||||
Autorisierungscode
|
Autorisierungscode
|
||||||
Autorisierungs-Endpunktes
|
Autorisierungs-Endpunktes
|
||||||
@@ -130,6 +131,7 @@ Eingangsscan
|
|||||||
Einlassbuchung
|
Einlassbuchung
|
||||||
Einlassdatum
|
Einlassdatum
|
||||||
Einlasskontrolle
|
Einlasskontrolle
|
||||||
|
Einmalpasswörter
|
||||||
einzuchecken
|
einzuchecken
|
||||||
email
|
email
|
||||||
E-Mail-Renderer
|
E-Mail-Renderer
|
||||||
@@ -163,6 +165,7 @@ Explorer
|
|||||||
FA
|
FA
|
||||||
Favicon
|
Favicon
|
||||||
F-Droid
|
F-Droid
|
||||||
|
freeOTP
|
||||||
Footer
|
Footer
|
||||||
Footer-Link
|
Footer-Link
|
||||||
Footer-Text
|
Footer-Text
|
||||||
@@ -557,6 +560,7 @@ Zahlungs-ID
|
|||||||
Zahlungspflichtig
|
Zahlungspflichtig
|
||||||
Zehnerkarten
|
Zehnerkarten
|
||||||
Zeitbasiert
|
Zeitbasiert
|
||||||
|
zeitbasierte
|
||||||
Zeitslotbuchung
|
Zeitslotbuchung
|
||||||
Zimpler
|
Zimpler
|
||||||
ZIP-Datei
|
ZIP-Datei
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@ ausgecheckt
|
|||||||
ausgeklappt
|
ausgeklappt
|
||||||
auswahl
|
auswahl
|
||||||
Authentication
|
Authentication
|
||||||
|
Authenticator
|
||||||
Authenticator-App
|
Authenticator-App
|
||||||
Autorisierungscode
|
Autorisierungscode
|
||||||
Autorisierungs-Endpunktes
|
Autorisierungs-Endpunktes
|
||||||
@@ -130,6 +131,7 @@ Eingangsscan
|
|||||||
Einlassbuchung
|
Einlassbuchung
|
||||||
Einlassdatum
|
Einlassdatum
|
||||||
Einlasskontrolle
|
Einlasskontrolle
|
||||||
|
Einmalpasswörter
|
||||||
einzuchecken
|
einzuchecken
|
||||||
email
|
email
|
||||||
E-Mail-Renderer
|
E-Mail-Renderer
|
||||||
@@ -163,6 +165,7 @@ Explorer
|
|||||||
FA
|
FA
|
||||||
Favicon
|
Favicon
|
||||||
F-Droid
|
F-Droid
|
||||||
|
freeOTP
|
||||||
Footer
|
Footer
|
||||||
Footer-Link
|
Footer-Link
|
||||||
Footer-Text
|
Footer-Text
|
||||||
@@ -557,6 +560,7 @@ Zahlungs-ID
|
|||||||
Zahlungspflichtig
|
Zahlungspflichtig
|
||||||
Zehnerkarten
|
Zehnerkarten
|
||||||
Zeitbasiert
|
Zeitbasiert
|
||||||
|
zeitbasierte
|
||||||
Zeitslotbuchung
|
Zeitslotbuchung
|
||||||
Zimpler
|
Zimpler
|
||||||
ZIP-Datei
|
ZIP-Datei
|
||||||
|
|||||||
+310
-288
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
"POT-Creation-Date: 2026-03-30 11:25+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||||
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
|
"PO-Revision-Date: 2026-03-30 03:00+0000\n"
|
||||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
"js/es/>\n"
|
"js/es/>\n"
|
||||||
@@ -329,7 +329,7 @@ msgstr "Pedido no aprobado"
|
|||||||
|
|
||||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
|
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
|
||||||
msgid "Checked-in Tickets"
|
msgid "Checked-in Tickets"
|
||||||
msgstr "Registro de código QR"
|
msgstr "Billetes registrados"
|
||||||
|
|
||||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:69
|
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:69
|
||||||
msgid "Valid Tickets"
|
msgid "Valid Tickets"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||||
"PO-Revision-Date: 2026-02-10 16:49+0000\n"
|
"PO-Revision-Date: 2026-03-25 14:14+0000\n"
|
||||||
"Last-Translator: Raffaele Doretto <ced@comune.portogruaro.ve.it>\n"
|
"Last-Translator: Pietro Isotti <isottipietro@gmail.com>\n"
|
||||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
"js/it/>\n"
|
"js/it/>\n"
|
||||||
"Language: it\n"
|
"Language: it\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 5.15.2\n"
|
"X-Generator: Weblate 5.16.2\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||||
@@ -310,9 +310,8 @@ msgid "Ticket code revoked/changed"
|
|||||||
msgstr "Codice biglietto annullato/modificato"
|
msgstr "Codice biglietto annullato/modificato"
|
||||||
|
|
||||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||||
#, fuzzy
|
|
||||||
msgid "Ticket blocked"
|
msgid "Ticket blocked"
|
||||||
msgstr "Biglietto non pagato"
|
msgstr "Biglietto bloccato"
|
||||||
|
|
||||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||||
msgid "Ticket not valid at this time"
|
msgid "Ticket not valid at this time"
|
||||||
@@ -429,7 +428,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||||
msgid "If this takes longer than a few minutes, please contact us."
|
msgid "If this takes longer than a few minutes, please contact us."
|
||||||
msgstr ""
|
msgstr "Se questa operazione richiede alcuni minuti, si prega di contattarci."
|
||||||
|
|
||||||
#: pretix/static/pretixbase/js/asynctask.js:331
|
#: pretix/static/pretixbase/js/asynctask.js:331
|
||||||
msgid "Close message"
|
msgid "Close message"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||||
"PO-Revision-Date: 2026-02-23 10:00+0000\n"
|
"PO-Revision-Date: 2026-03-23 21:00+0000\n"
|
||||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
"js/ja/>\n"
|
"js/ja/>\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Generator: Weblate 5.16\n"
|
"X-Generator: Weblate 5.16.2\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||||
@@ -60,7 +60,7 @@ msgstr "PayPal後払い"
|
|||||||
|
|
||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||||
msgid "iDEAL | Wero"
|
msgid "iDEAL | Wero"
|
||||||
msgstr ""
|
msgstr "iDEAL | Wero"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||||
msgid "SEPA Direct Debit"
|
msgid "SEPA Direct Debit"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||||
"PO-Revision-Date: 2026-01-26 22:00+0000\n"
|
"PO-Revision-Date: 2026-03-25 08:00+0000\n"
|
||||||
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
|
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
|
||||||
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix-js/pt_BR/>\n"
|
"pretix/pretix-js/pt_BR/>\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Weblate 5.15.2\n"
|
"X-Generator: Weblate 5.16.2\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||||
@@ -60,7 +60,7 @@ msgstr "PayPal Pay Later"
|
|||||||
|
|
||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||||
msgid "iDEAL | Wero"
|
msgid "iDEAL | Wero"
|
||||||
msgstr ""
|
msgstr "iDEAL | Wero"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||||
msgid "SEPA Direct Debit"
|
msgid "SEPA Direct Debit"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||||
"PO-Revision-Date: 2025-10-10 17:00+0000\n"
|
"PO-Revision-Date: 2026-03-26 14:29+0000\n"
|
||||||
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
|
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
|
||||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
"js/sv/>\n"
|
"js/sv/>\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 5.13.3\n"
|
"X-Generator: Weblate 5.16.2\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||||
@@ -1023,7 +1023,6 @@ msgid "Waiting list"
|
|||||||
msgstr "Väntelista"
|
msgstr "Väntelista"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||||
#, fuzzy
|
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
msgid ""
|
msgid ""
|
||||||
"You currently have an active cart for this event. If you select more "
|
"You currently have an active cart for this event. If you select more "
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ anonymized
|
|||||||
Auth
|
Auth
|
||||||
authentification
|
authentification
|
||||||
authenticator
|
authenticator
|
||||||
|
Authenticator
|
||||||
automatical
|
automatical
|
||||||
availabilities
|
availabilities
|
||||||
backend
|
backend
|
||||||
@@ -22,6 +23,7 @@ barcodes
|
|||||||
Bcc
|
Bcc
|
||||||
BCC
|
BCC
|
||||||
BezahlCode
|
BezahlCode
|
||||||
|
biometric
|
||||||
BLIK
|
BLIK
|
||||||
blocklist
|
blocklist
|
||||||
BN
|
BN
|
||||||
@@ -56,6 +58,7 @@ EPS
|
|||||||
eps
|
eps
|
||||||
favicon
|
favicon
|
||||||
filetype
|
filetype
|
||||||
|
freeOTP
|
||||||
frontend
|
frontend
|
||||||
frontpage
|
frontpage
|
||||||
Galician
|
Galician
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ from django import forms
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.hashers import check_password
|
from django.contrib.auth.hashers import check_password
|
||||||
from django.contrib.auth.password_validation import (
|
from django.contrib.auth.password_validation import (
|
||||||
get_password_validators, password_validators_help_texts, validate_password,
|
MinimumLengthValidator, get_password_validators, validate_password,
|
||||||
)
|
)
|
||||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
@@ -300,13 +300,12 @@ class SetPasswordForm(forms.Form):
|
|||||||
)
|
)
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
label=_('Password'),
|
label=_('Password'),
|
||||||
widget=forms.PasswordInput(attrs={'minlength': '8', 'autocomplete': 'new-password'}),
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
max_length=4096,
|
max_length=4096,
|
||||||
required=True
|
|
||||||
)
|
)
|
||||||
password_repeat = forms.CharField(
|
password_repeat = forms.CharField(
|
||||||
label=_('Repeat password'),
|
label=_('Repeat password'),
|
||||||
widget=forms.PasswordInput(attrs={'minlength': '8', 'autocomplete': 'new-password'}),
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
max_length=4096,
|
max_length=4096,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -316,6 +315,14 @@ class SetPasswordForm(forms.Form):
|
|||||||
kwargs['initial']['email'] = self.customer.email
|
kwargs['initial']['email'] = self.customer.email
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
pw_min_len_validators = [v for v in get_customer_password_validators() if isinstance(v, MinimumLengthValidator)]
|
||||||
|
if pw_min_len_validators:
|
||||||
|
self.fields['password'].widget.attrs['minlength'] = max(v.min_length for v in pw_min_len_validators)
|
||||||
|
self.fields['password_repeat'].widget.attrs['minlength'] = max(v.min_length for v in pw_min_len_validators)
|
||||||
|
|
||||||
|
if 'password' not in self.data:
|
||||||
|
self.fields['password'].help_text = ' '.join(v.get_help_text() for v in pw_min_len_validators)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
password1 = self.cleaned_data.get('password', '')
|
password1 = self.cleaned_data.get('password', '')
|
||||||
password2 = self.cleaned_data.get('password_repeat')
|
password2 = self.cleaned_data.get('password_repeat')
|
||||||
@@ -329,8 +336,7 @@ class SetPasswordForm(forms.Form):
|
|||||||
|
|
||||||
def clean_password(self):
|
def clean_password(self):
|
||||||
password1 = self.cleaned_data.get('password', '')
|
password1 = self.cleaned_data.get('password', '')
|
||||||
if validate_password(password1, user=self.customer, password_validators=get_customer_password_validators()) is not None:
|
validate_password(password1, user=self.customer, password_validators=get_customer_password_validators())
|
||||||
raise forms.ValidationError(_(password_validators_help_texts()), code='pw_invalid')
|
|
||||||
return password1
|
return password1
|
||||||
|
|
||||||
|
|
||||||
@@ -395,13 +401,13 @@ class ChangePasswordForm(forms.Form):
|
|||||||
)
|
)
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
label=_('New password'),
|
label=_('New password'),
|
||||||
widget=forms.PasswordInput(attrs={'minlength': '8', 'autocomplete': 'new-password'}),
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
max_length=4096,
|
max_length=4096,
|
||||||
required=True
|
required=True
|
||||||
)
|
)
|
||||||
password_repeat = forms.CharField(
|
password_repeat = forms.CharField(
|
||||||
label=_('Repeat password'),
|
label=_('Repeat password'),
|
||||||
widget=forms.PasswordInput(attrs={'minlength': '8', 'autocomplete': 'new-password'}),
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
max_length=4096,
|
max_length=4096,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -411,6 +417,14 @@ class ChangePasswordForm(forms.Form):
|
|||||||
kwargs['initial']['email'] = self.customer.email
|
kwargs['initial']['email'] = self.customer.email
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
pw_min_len_validators = [v for v in get_customer_password_validators() if isinstance(v, MinimumLengthValidator)]
|
||||||
|
if pw_min_len_validators:
|
||||||
|
self.fields['password'].widget.attrs['minlength'] = max(v.min_length for v in pw_min_len_validators)
|
||||||
|
self.fields['password_repeat'].widget.attrs['minlength'] = max(v.min_length for v in pw_min_len_validators)
|
||||||
|
|
||||||
|
if 'password' not in self.data:
|
||||||
|
self.fields['password'].help_text = ' '.join(v.get_help_text() for v in pw_min_len_validators)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
password1 = self.cleaned_data.get('password', '')
|
password1 = self.cleaned_data.get('password', '')
|
||||||
password2 = self.cleaned_data.get('password_repeat')
|
password2 = self.cleaned_data.get('password_repeat')
|
||||||
@@ -424,8 +438,7 @@ class ChangePasswordForm(forms.Form):
|
|||||||
|
|
||||||
def clean_password(self):
|
def clean_password(self):
|
||||||
password1 = self.cleaned_data.get('password', '')
|
password1 = self.cleaned_data.get('password', '')
|
||||||
if validate_password(password1, user=self.customer, password_validators=get_customer_password_validators()) is not None:
|
validate_password(password1, user=self.customer, password_validators=get_customer_password_validators())
|
||||||
raise forms.ValidationError(_(password_validators_help_texts()), code='pw_invalid')
|
|
||||||
return password1
|
return password1
|
||||||
|
|
||||||
def clean_password_current(self):
|
def clean_password_current(self):
|
||||||
|
|||||||
@@ -2406,6 +2406,15 @@ class OrderChangeManagerTests(TestCase):
|
|||||||
self.ocm.commit()
|
self.ocm.commit()
|
||||||
assert self.order.positions.count() == 2
|
assert self.order.positions.count() == 2
|
||||||
|
|
||||||
|
@classscope(attr='o')
|
||||||
|
def test_add_item_quota_partial(self):
|
||||||
|
q1 = self.event.quotas.create(name='Test', size=1)
|
||||||
|
q1.items.add(self.shirt)
|
||||||
|
self.ocm.add_position(self.shirt, None, None, None, count=2)
|
||||||
|
with self.assertRaises(OrderError):
|
||||||
|
self.ocm.commit()
|
||||||
|
assert self.order.positions.count() == 2
|
||||||
|
|
||||||
@classscope(attr='o')
|
@classscope(attr='o')
|
||||||
def test_add_item_addon(self):
|
def test_add_item_addon(self):
|
||||||
self.shirt.category = self.event.categories.create(name='Add-ons', is_addon=True)
|
self.shirt.category = self.event.categories.create(name='Add-ons', is_addon=True)
|
||||||
|
|||||||
@@ -1584,10 +1584,11 @@ class OrderChangeTests(SoupTest):
|
|||||||
'add_position-MAX_NUM_FORMS': '100',
|
'add_position-MAX_NUM_FORMS': '100',
|
||||||
'add_position-0-itemvar': str(self.shirt.pk),
|
'add_position-0-itemvar': str(self.shirt.pk),
|
||||||
'add_position-0-do': 'on',
|
'add_position-0-do': 'on',
|
||||||
|
'add_position-0-count': '2',
|
||||||
'add_position-0-price': '14.00',
|
'add_position-0-price': '14.00',
|
||||||
})
|
})
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
assert self.order.positions.count() == 3
|
assert self.order.positions.count() == 4
|
||||||
assert self.order.positions.last().item == self.shirt
|
assert self.order.positions.last().item == self.shirt
|
||||||
assert self.order.positions.last().price == 14
|
assert self.order.positions.last().price == 14
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user