mirror of
https://github.com/pretix/pretix.git
synced 2026-05-16 17:03:58 +00:00
Compare commits
1 Commits
fix-clone-
...
pajowu/wid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cd54c9c5c |
@@ -1,16 +1,11 @@
|
||||
Contributing to pretix
|
||||
======================
|
||||
|
||||
Welcome to pretix, we are happy that you would like to contribute.
|
||||
Before you do so, please make sure to read the following documents:
|
||||
Hey there and welcome to pretix!
|
||||
|
||||
- [Contribution workflow](https://docs.pretix.eu/dev/development/contribution/general.html)
|
||||
- [AI-assisted contribution policy](https://docs.pretix.eu/dev/development/contribution/ai.html)
|
||||
- [Coding style and quality](https://docs.pretix.eu/dev/development/contribution/style.html)
|
||||
- [Development setup](https://docs.pretix.eu/dev/development/setup.html)
|
||||
- [Code of Conduct](https://docs.pretix.eu/dev/development/contribution/codeofconduct.html)
|
||||
* We've got a contributors guide in [our documentation](https://docs.pretix.eu/dev/development/contribution/) together with notes on the [development setup](https://docs.pretix.eu/dev/development/setup.html).
|
||||
|
||||
Before we can accept your first PR we'll need you to sign [our **Contributor License Agreement** (CLA)](https://pretix.eu/about/en/cla).
|
||||
You can find more information about the how and why in our [License FAQ](https://docs.pretix.eu/trust/licensing/faq/) and in our [license change blog post](https://pretix.eu/about/en/blog/20210412-license/).
|
||||
* Please note that we have a [Code of Conduct](https://docs.pretix.eu/dev/development/contribution/codeofconduct.html) in place that applies to all project contributions, including issues, pull requests, etc.
|
||||
|
||||
* Before we can accept a PR from you we'll need you to sign [our CLA](https://pretix.eu/about/en/cla). You can find more information about the how and why in our [License FAQ](https://docs.pretix.eu/trust/licensing/faq/) and in our [license change blog post](https://pretix.eu/about/en/blog/20210412-license/).
|
||||
|
||||
**Before contributing new functionality, always open a discussion first.**
|
||||
@@ -1,24 +0,0 @@
|
||||
.. _`aipolicy`:
|
||||
|
||||
AI-assisted contribution policy
|
||||
===============================
|
||||
|
||||
pretix is maintained by humans.
|
||||
Every discussion, issue, and pull request is read and reviewed by humans (and sometimes machines, too).
|
||||
We ask you to respect the time and effort put in by these humans by not sending low-effort, unqualified work, since it puts the burden of validation on the maintainer.
|
||||
|
||||
Therefore, the pretix project has strict rules for AI usage:
|
||||
|
||||
- **All AI usage in any form must be disclosed.** You must state the tool you used (e.g. Claude Code, Cursor, Amp) along with the extent that the work was AI-assisted.
|
||||
|
||||
- **The human-in-the-loop must fully understand all code.** If you can't explain what your changes do and how they interact with the greater system without the aid of AI tools, do not contribute to this project.
|
||||
|
||||
- **Issues and discussions can use AI assistance but must have a full human-in-the-loop.** This means that any content generated with AI must have been reviewed and edited by a human before submission. AI is very good at being overly verbose and including noise that distracts from the main point. Humans must do their research and trim this down.
|
||||
|
||||
- **No AI-generated media is allowed (art, images, videos, audio, etc.).** Text and code are the only acceptable AI-generated content, per the other rules in this policy.
|
||||
|
||||
- **Bad AI drivers will be excluded from the project.** People who produce bad contributions that are clearly AI (slop) will be blocked from our organization without warning.
|
||||
|
||||
This policy was inspired by the `ghostty project`_.
|
||||
|
||||
.. _ghostty project: https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
|
||||
@@ -1,39 +1,23 @@
|
||||
Contribution workflow
|
||||
=====================
|
||||
General remarks
|
||||
===============
|
||||
|
||||
You are interested in contributing to pretix? That is awesome!
|
||||
|
||||
If you’re new to contributing to open source software, don’t be afraid. We’ll happily review your code and give you
|
||||
constructive and friendly feedback on your changes. Every contribution should go through the following steps.
|
||||
constructive and friendly feedback on your changes.
|
||||
|
||||
Discussion & Design
|
||||
-------------------
|
||||
|
||||
pretix is a large and mature project with more of a decade of history and hopefully many more decades to come.
|
||||
Keeping pretix in good shape over long timeframes is first and foremost a fight against complexity.
|
||||
With every additional feature, complexity grows, and both features and complexity are hard to remove.
|
||||
|
||||
Even if you are doing the initial work of the contribution, accepting the contribution is not free for us.
|
||||
Not only will we need to maintain the feature, but every feature adds cost to the maintenance of every other feature it interacts with, and every feature adds effort for users to understand how pretix works.
|
||||
Therefore, we must carefully select what features we add, based on how well they fit the system in general and of how much use they will be to our larger user base.
|
||||
|
||||
We strongly ask you to **create a discussion on GitHub for every new feature idea** outlining the use case and the proposed implementation design.
|
||||
Pull requests without prior discussion will likely just be closed.
|
||||
|
||||
For bug fixes and very minor changes, you can skip this step and open a PR right away.
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
To develop your contribution, you'll need pretix running locally on your machine. Head over to :ref:`devsetup` to learn how to do this.
|
||||
First of all, you'll need pretix running locally on your machine. Head over to :ref:`devsetup` to learn how to do this.
|
||||
If you run into any problems on your way, please do not hesitate to ask us anytime!
|
||||
|
||||
While developing, please have a look at our :ref:`aipolicy` and our guidelines on :ref:`codestyle`.
|
||||
Please note that we bound ourselves to a :ref:`coc` that applies to all communication around the project. You can be
|
||||
assured that we will not tolerate any form of harassment.
|
||||
|
||||
Sending a patch
|
||||
---------------
|
||||
|
||||
Once you have a first draft of your changes, please `create a pull request`_ on our `GitHub repository`_.
|
||||
If you improved pretix in any way, we'd be very happy if you contribute it
|
||||
back to the main code base! The easiest way to do so is to `create a pull request`_
|
||||
on our `GitHub repository`_.
|
||||
|
||||
We recommend that you create a feature branch for every issue you work on so the changes can
|
||||
be reviewed individually.
|
||||
@@ -41,17 +25,14 @@ Please use the test suite to check whether your changes break any existing featu
|
||||
the code style checks to confirm you are consistent with pretix's coding style. You'll
|
||||
find instructions on this in the :ref:`checksandtests` section of the development setup guide.
|
||||
|
||||
We automatically run the tests and the code style check on every pull request through GitHub Actions and we won’t
|
||||
We automatically run the tests and the code style check on every pull request on Travis CI and we won’t
|
||||
accept any pull requests without all tests passing. However, if you don't find out *why* they are not passing,
|
||||
just send the pull request and tell us – we'll be glad to help.
|
||||
|
||||
If you add a new feature, please include appropriate documentation into your patch. If you fix a bug,
|
||||
please include a regression test, i.e. a test that fails without your changes and passes after applying your changes.
|
||||
|
||||
Again: If you get stuck, do not hesitate to contact us through GitHub discussions.
|
||||
|
||||
Please note that we bound ourselves to a :ref:`coc` that applies to all communication around the project. You can be
|
||||
assured that we will not tolerate any form of harassment.
|
||||
Again: If you get stuck, do not hesitate to contact any of us, or Raphael personally at mail@raphaelmichel.de.
|
||||
|
||||
.. _create a pull request: https://help.github.com/articles/creating-a-pull-request/
|
||||
.. _GitHub repository: https://github.com/pretix/pretix
|
||||
|
||||
@@ -6,5 +6,4 @@ Contributing to pretix
|
||||
|
||||
general
|
||||
style
|
||||
ai
|
||||
codeofconduct
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
.. spelling:word-list:: Rebase rebasing
|
||||
|
||||
.. _`codestyle`:
|
||||
|
||||
Coding style and quality
|
||||
========================
|
||||
|
||||
@@ -30,6 +28,8 @@ Code
|
||||
Commits and Pull Requests
|
||||
-------------------------
|
||||
|
||||
|
||||
|
||||
Most commits should start as pull requests, therefore this applies to the titles of pull requests as well since
|
||||
the pull request title will become the commit message on merge. We prefer merging with GitHub's "Squash and merge"
|
||||
feature if the PR contains multiple commits that do not carry value to keep. If there is value in keeping the
|
||||
|
||||
@@ -33,9 +33,9 @@ dependencies = [
|
||||
"bleach==6.3.*",
|
||||
"celery==5.6.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=46.0.7",
|
||||
"cryptography>=44.0.0",
|
||||
"css-inline==0.20.*",
|
||||
"defusedcsv>=3.0.0",
|
||||
"defusedcsv>=1.1.0",
|
||||
"dnspython==2.*",
|
||||
"Django[argon2]==5.2.*",
|
||||
"django-bootstrap3==26.1",
|
||||
@@ -93,11 +93,11 @@ dependencies = [
|
||||
"redis==7.4.*",
|
||||
"reportlab==4.4.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.58.*",
|
||||
"sentry-sdk==2.57.*",
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
"tlds>=2026041800",
|
||||
"tlds>=2020041600",
|
||||
"tqdm==4.*",
|
||||
"ua-parser==1.0.*",
|
||||
"vobject==0.9.*",
|
||||
|
||||
@@ -1103,25 +1103,13 @@ class PaymentListExporter(ListExporter):
|
||||
def iterate_list(self, form_data):
|
||||
provider_names = dict(get_all_payment_providers())
|
||||
|
||||
i_numbers = Invoice.objects.filter(
|
||||
order=OuterRef('order_id'),
|
||||
).values('order').annotate(
|
||||
m=GroupConcat('full_invoice_no', delimiter=', ')
|
||||
).values(
|
||||
'm'
|
||||
).order_by()
|
||||
|
||||
payments = OrderPayment.objects.filter(
|
||||
order__event__in=self.events,
|
||||
state__in=form_data.get('payment_states', [])
|
||||
).annotate(
|
||||
order_invoice_numbers=Subquery(i_numbers, output_field=CharField()),
|
||||
).select_related('order').prefetch_related('order__event').order_by('created')
|
||||
refunds = OrderRefund.objects.filter(
|
||||
order__event__in=self.events,
|
||||
state__in=form_data.get('refund_states', [])
|
||||
).annotate(
|
||||
order_invoice_numbers=Subquery(i_numbers, output_field=CharField()),
|
||||
).select_related('order').prefetch_related('order__event').order_by('created')
|
||||
|
||||
if form_data.get('end_date_range'):
|
||||
@@ -1147,7 +1135,6 @@ class PaymentListExporter(ListExporter):
|
||||
headers = [
|
||||
_('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'),
|
||||
_('Status code'), _('Amount'), _('Payment method'), _('Comment'), _('Matching ID'), _('Payment details'),
|
||||
_('Invoice numbers'),
|
||||
]
|
||||
yield headers
|
||||
|
||||
@@ -1185,7 +1172,6 @@ class PaymentListExporter(ListExporter):
|
||||
obj.comment if isinstance(obj, OrderRefund) else "",
|
||||
matching_id,
|
||||
payment_details,
|
||||
obj.order_invoice_numbers,
|
||||
]
|
||||
yield row
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ from pretix.base.settings import (
|
||||
COUNTRIES_WITH_STATE_IN_ADDRESS, COUNTRY_STATE_LABEL,
|
||||
PERSON_NAME_SALUTATIONS, PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS,
|
||||
)
|
||||
from pretix.base.templatetags.rich_text import URL_RE, rich_text
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.control.forms import (
|
||||
ExtFileField, ExtValidationMixin, SizeValidationMixin, SplitDateTimeField,
|
||||
@@ -227,15 +227,9 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
# bots.
|
||||
r'^[^$€/%§{}<>~]*$',
|
||||
message=_('Please do not use special characters in names.')
|
||||
),
|
||||
RegexValidator(
|
||||
URL_RE,
|
||||
inverse_match=True,
|
||||
message=_('Please do not use special characters in names.')
|
||||
)
|
||||
]
|
||||
}
|
||||
self.max_length = defaults['max_length']
|
||||
self.scheme_name = kwargs.pop('scheme')
|
||||
self.titles = kwargs.pop('titles')
|
||||
self.scheme = PERSON_NAME_SCHEMES.get(self.scheme_name)
|
||||
@@ -293,7 +287,7 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
if self.require_all_fields and not all(v for v in value):
|
||||
raise forms.ValidationError(self.error_messages['incomplete'], code='required')
|
||||
|
||||
if sum(len(v) for v in value.values() if v) > (self.max_length or 250):
|
||||
if sum(len(v) for v in value.values() if v) > 250:
|
||||
raise forms.ValidationError(_('Please enter a shorter name.'), code='max_length')
|
||||
|
||||
if value.get("salutation") == "empty":
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# Generated by Django 5.2.12 on 2026-04-28 11:34
|
||||
import logging
|
||||
|
||||
from django.db import IntegrityError, migrations, transaction
|
||||
from django.db.models import Count, F
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def fix_cross_organizer_eventmetavalues(apps, schema_editor):
|
||||
EventMetaProperty = apps.get_model("pretixbase", "EventMetaProperty")
|
||||
EventMetaValue = apps.get_model("pretixbase", "EventMetaValue")
|
||||
|
||||
cross_org_values = EventMetaValue.objects.filter(event__organizer__pk__ne=F('property__organizer__pk'))
|
||||
for emv in cross_org_values:
|
||||
logger.info(f"Fixup cross-organizer value {emv.event.organizer.slug}/{emv.event.slug}\n before: {emv.property.name}({emv.property.id}@{emv.property.organizer.slug}) = {emv.value}")
|
||||
try:
|
||||
emv.property = emv.event.organizer.meta_properties.filter(name=emv.property.name).first()
|
||||
logger.info(f" found existing EventMetaProperty in {emv.event.organizer.slug}")
|
||||
if EventMetaValue.objects.filter(event=emv.event, property=emv.property).exists():
|
||||
logger.info(f" EventMetaValue with property in correct organizer already exists, deleting the cross-organizer one")
|
||||
emv.delete()
|
||||
continue
|
||||
except EventMetaProperty.DoesNotExist:
|
||||
meta_prop = emv.property
|
||||
meta_prop.pk = None
|
||||
meta_prop.organizer = emv.event.organizer
|
||||
meta_prop.save(force_insert=True)
|
||||
logger.info(f" created new EventMetaProperty")
|
||||
emv.property = meta_prop
|
||||
logger.info(f" after: {emv.property.name}({emv.property.id}@{emv.property.organizer.slug}) = {emv.value}")
|
||||
emv.save(update_fields=["property"])
|
||||
|
||||
|
||||
def make_eventmetaproperties_unique(apps, schema_editor):
|
||||
EventMetaProperty = apps.get_model("pretixbase", "EventMetaProperty")
|
||||
EventMetaValue = apps.get_model("pretixbase", "EventMetaValue")
|
||||
|
||||
duplicates = EventMetaProperty.objects.values('organizer', 'organizer__slug', 'name').annotate(count=Count('id')).filter(count__gt=1)
|
||||
for dup in duplicates:
|
||||
logger.info(f"Fixup duplicate property {dup['organizer__slug']} {dup['name']}")
|
||||
props = list(EventMetaProperty.objects.filter(organizer=dup['organizer'], name=dup['name']))
|
||||
|
||||
# TODO: any better idea than picking the property to keep more or less randomly (first in database order)?
|
||||
target = props[0]
|
||||
invalid = props[1:]
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
affected = EventMetaValue.objects.filter(
|
||||
event__organizer=dup['organizer'], property__in=invalid
|
||||
).update(
|
||||
property=target
|
||||
)
|
||||
logger.info(f" Switching {affected} value(s) over to {target.name}({target.id}@{target.organizer.slug})")
|
||||
|
||||
except IntegrityError as e:
|
||||
logger.info(f" Failed to switch all value(s) over to {target.name}({target.id}@{target.organizer.slug})")
|
||||
logger.info(f" {e}")
|
||||
for prop in invalid:
|
||||
newname = f'{prop.name}_DUPLICATE_{prop.id}'
|
||||
logger.info(f" Renaming {prop.name}({prop.id}@{prop.organizer.slug}) to {newname}({prop.id}@{prop.organizer.slug})")
|
||||
prop.name = newname
|
||||
prop.save()
|
||||
|
||||
else:
|
||||
for prop in invalid:
|
||||
logger.info(f" Deleting {prop.name}({prop.id}@{prop.organizer.slug})")
|
||||
prop.delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0298_pluggable_permissions"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fix_cross_organizer_eventmetavalues, migrations.RunPython.noop),
|
||||
migrations.RunPython(make_eventmetaproperties_unique, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.2.12 on 2026-04-28 11:34
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0299_fixup_eventmetaproperties"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name="eventmetaproperty",
|
||||
unique_together={("organizer", "name")},
|
||||
),
|
||||
]
|
||||
@@ -877,8 +877,6 @@ class Event(EventMixin, LoggedModel):
|
||||
ItemProgramTime, ItemVariationMetaValue, Question, Quota,
|
||||
)
|
||||
|
||||
is_cross_organizer = other.organizer_id != self.organizer_id
|
||||
|
||||
# Note: avoid self.set_active_plugins(), it causes trouble e.g. for the badges plugin.
|
||||
# Plugins can create data in installed() hook based on existing data of the event.
|
||||
# Calling set_active_plugins() results in defaults being created while actually data
|
||||
@@ -907,15 +905,6 @@ class Event(EventMixin, LoggedModel):
|
||||
for emv in EventMetaValue.objects.filter(event=other):
|
||||
emv.pk = None
|
||||
emv.event = self
|
||||
if is_cross_organizer:
|
||||
try:
|
||||
emv.property = self.organizer.meta_properties.get(name=emv.property.name)
|
||||
except EventMetaProperty.DoesNotExist:
|
||||
meta_prop = emv.property
|
||||
meta_prop.pk = None
|
||||
meta_prop.organizer = self.organizer
|
||||
meta_prop.save(force_insert=True)
|
||||
emv.property = meta_prop
|
||||
emv.save(force_insert=True)
|
||||
|
||||
for fl in EventFooterLink.objects.filter(event=other):
|
||||
@@ -969,13 +958,13 @@ class Event(EventMixin, LoggedModel):
|
||||
if i.tax_rule_id:
|
||||
i.tax_rule = tax_map[i.tax_rule_id]
|
||||
|
||||
if i.grant_membership_type and is_cross_organizer:
|
||||
if i.grant_membership_type and other.organizer_id != self.organizer_id:
|
||||
i.grant_membership_type = None
|
||||
|
||||
i.save() # no force_insert since i.picture.save could have already inserted
|
||||
i.log_action('pretix.object.cloned')
|
||||
|
||||
if require_membership_types and not is_cross_organizer:
|
||||
if require_membership_types and other.organizer_id == self.organizer_id:
|
||||
i.require_membership_types.set(require_membership_types)
|
||||
|
||||
if not i.all_sales_channels:
|
||||
@@ -990,7 +979,7 @@ class Event(EventMixin, LoggedModel):
|
||||
v._prefetched_objects_cache = {}
|
||||
v.save(force_insert=True)
|
||||
|
||||
if require_membership_types and not is_cross_organizer:
|
||||
if require_membership_types and other.organizer_id == self.organizer_id:
|
||||
v.require_membership_types.set(require_membership_types)
|
||||
if not v.all_sales_channels:
|
||||
v.limit_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in limit_sales_channels]))
|
||||
@@ -1841,7 +1830,6 @@ class EventMetaProperty(LoggedModel):
|
||||
|
||||
class Meta:
|
||||
ordering = ("position", "name",)
|
||||
unique_together = ('organizer', 'name')
|
||||
|
||||
@property
|
||||
def choice_keys(self):
|
||||
@@ -1875,8 +1863,6 @@ class EventMetaValue(LoggedModel):
|
||||
self.event.cache.clear()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.event and self.event.organizer != self.property.organizer:
|
||||
raise ValidationError(_("Property and event must belong to the same organizer."))
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.cache.clear()
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
# 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.
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
@@ -165,6 +168,46 @@ class QuestionsForm(BaseQuestionsForm):
|
||||
)
|
||||
|
||||
|
||||
class AddOnRadioSelect(forms.RadioSelect):
|
||||
option_template_name = 'pretixpresale/forms/addon_choice_option.html'
|
||||
|
||||
def optgroups(self, name, value, attrs=None):
|
||||
attrs = attrs or {}
|
||||
groups = []
|
||||
has_selected = False
|
||||
for index, (option_value, option_label, option_desc) in enumerate(chain(self.choices)):
|
||||
if option_value is None:
|
||||
option_value = ''
|
||||
if isinstance(option_label, (list, tuple)):
|
||||
raise TypeError('Choice groups are not supported here')
|
||||
group_name = None
|
||||
subgroup = []
|
||||
groups.append((group_name, subgroup, index))
|
||||
|
||||
selected = (
|
||||
force_str(option_value) in value and
|
||||
(has_selected is False or self.allow_multiple_selected)
|
||||
)
|
||||
if selected is True and has_selected is False:
|
||||
has_selected = True
|
||||
attrs['description'] = option_desc
|
||||
subgroup.append(self.create_option(
|
||||
name, option_value, option_label, selected, index,
|
||||
subindex=None, attrs=attrs,
|
||||
))
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
class AddOnVariationField(forms.ChoiceField):
|
||||
def valid_value(self, value):
|
||||
text_value = force_str(value)
|
||||
for k, v, d in self.choices:
|
||||
if value == k or text_value == force_str(k):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class MembershipForm(forms.Form):
|
||||
required_css_class = 'required'
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ class RegistrationForm(forms.Form):
|
||||
)
|
||||
|
||||
self.fields['name_parts'] = NamePartsFormField(
|
||||
max_length=70,
|
||||
max_length=255,
|
||||
required=True,
|
||||
scheme=request.organizer.settings.name_scheme,
|
||||
titles=request.organizer.settings.name_scheme_titles,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{% load rich_text %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% include "django/forms/widgets/input.html" %} {{ widget.label }}</label> {% if widget.attrs.description %}<span class="fa fa-info-circle toggle-variation-description" aria-hidden="true"></span>
|
||||
<div class="variation-description addon-variation-description">{{ widget.attrs.description|rich_text }}</div>{% endif %}
|
||||
@@ -966,6 +966,7 @@ $table-bg-accent: rgba(128, 128, 128, 0.05);
|
||||
width: 80vw;
|
||||
max-width: 1080px;
|
||||
height: 80vh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
.pretix-widget-frame-inner iframe {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -238,9 +238,6 @@ def test_full_clone_cross_organizer_differences():
|
||||
sc1_c = organizer.sales_channels.create(identifier="c")
|
||||
sc2_a = organizer2.sales_channels.get(identifier="web")
|
||||
sc2_c = organizer2.sales_channels.create(identifier="c")
|
||||
o1_meta_prop_a = organizer.meta_properties.create(name="Prop to copy")
|
||||
o1_meta_prop_b = organizer.meta_properties.create(name="Prop to find")
|
||||
o2_meta_prop_b = organizer2.meta_properties.create(name="Prop to find")
|
||||
|
||||
event = Event.objects.create(
|
||||
organizer=organizer, name='Dummy', slug='dummy',
|
||||
@@ -265,9 +262,6 @@ def test_full_clone_cross_organizer_differences():
|
||||
event.settings.payment_giftcard__enabled = True
|
||||
event.settings.payment_giftcard__restrict_to_sales_channels = ['web', 'b', 'c']
|
||||
|
||||
event.meta_values.create(property=o1_meta_prop_a, value='a')
|
||||
event.meta_values.create(property=o1_meta_prop_b, value='b')
|
||||
|
||||
copied_event = Event.objects.create(
|
||||
organizer=organizer2, name='Dummy2', slug='dummy2',
|
||||
date_from=datetime.datetime(2022, 4, 15, 9, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
@@ -290,9 +284,3 @@ def test_full_clone_cross_organizer_differences():
|
||||
|
||||
assert event.settings.get('payment_giftcard__restrict_to_sales_channels', as_type=list) == ['web', 'b', 'c']
|
||||
assert copied_event.settings.get('payment_giftcard__restrict_to_sales_channels', as_type=list) == ['web', 'c']
|
||||
|
||||
assert event.meta_values.get(property__name=o1_meta_prop_a.name).property.organizer == organizer
|
||||
assert copied_event.meta_values.get(property__name=o1_meta_prop_a.name).value == 'a'
|
||||
assert copied_event.meta_values.get(property__name=o1_meta_prop_a.name).property.organizer == organizer2
|
||||
assert copied_event.meta_values.get(property=o2_meta_prop_b).value == 'b'
|
||||
assert copied_event.meta_values.get(property=o2_meta_prop_b).property.organizer == organizer2
|
||||
|
||||
Reference in New Issue
Block a user