We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Internal Server Error: KeyError: 'payment_stripe_secret_key'
when entering "*****" as secret key into the stripe payment plugin I get this internal server error:

```
ERROR 2022-01-29 19:52:01,164 django.request log Internal Server Error: /control/event/test/test/settings/payment/stripe_settings
Traceback (most recent call last):
  File "/var/pretix/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/var/pretix/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/var/pretix/venv/lib/python3.8/site-packages/pretix/control/permissions.py", line 69, in wrapper
    return function(request, *args, **kw)
  File "/var/pretix/venv/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/var/pretix/venv/lib/python3.8/site-packages/pretix/control/views/event.py", line 431, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/var/pretix/venv/lib/python3.8/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/usr/lib/python3.8/contextlib.py", line 75, in inner
    return func(*args, **kwds)
  File "/var/pretix/venv/lib/python3.8/site-packages/pretix/control/views/event.py", line 453, in post
    self.form.save()
  File "/var/pretix/venv/lib/python3.8/site-packages/pretix/base/forms/__init__.py", line 118, in save
    self.cleaned_data[k] = self.initial[k]
KeyError: 'payment_stripe_secret_key'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pretix/base/forms/__init__.py`
Content:
```
1 #
2 # This file is part of pretix (Community Edition).
3 #
4 # Copyright (C) 2014-2020 Raphael Michel and contributors
5 # Copyright (C) 2020-2021 rami.io GmbH and contributors
6 #
7 # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
8 # Public License as published by the Free Software Foundation in version 3 of the License.
9 #
10 # ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
11 # applicable granting you additional permissions and placing additional restrictions on your usage of this software.
12 # Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
13 # this file, see <https://pretix.eu/about/en/license>.
14 #
15 # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
16 # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17 # details.
18 #
19 # You should have received a copy of the GNU Affero General Public License along with this program.  If not, see
20 # <https://www.gnu.org/licenses/>.
21 #
22 
23 # This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
24 # the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
25 #
26 # This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
27 # full history of changes and contributors is available at <https://github.com/pretix/pretix>.
28 #
29 # This file contains Apache-licensed contributions copyrighted by: Alexey Kislitsin, Tobias Kunze
30 #
31 # Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
32 # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
33 # License for the specific language governing permissions and limitations under the License.
34 
35 import logging
36 
37 import i18nfield.forms
38 from django import forms
39 from django.forms.models import ModelFormMetaclass
40 from django.utils.crypto import get_random_string
41 from formtools.wizard.views import SessionWizardView
42 from hierarkey.forms import HierarkeyForm
43 
44 from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
45 
46 from .validators import PlaceholderValidator  # NOQA
47 
48 logger = logging.getLogger(__name__)
49 
50 
51 class BaseI18nModelForm(i18nfield.forms.BaseI18nModelForm):
52     # compatibility shim for django-i18nfield library
53 
54     def __init__(self, *args, **kwargs):
55         self.event = kwargs.pop('event', None)
56         if self.event:
57             kwargs['locales'] = self.event.settings.get('locales')
58         super().__init__(*args, **kwargs)
59 
60 
61 class I18nModelForm(BaseI18nModelForm, metaclass=ModelFormMetaclass):
62     pass
63 
64 
65 class I18nFormSet(i18nfield.forms.I18nModelFormSet):
66     # compatibility shim for django-i18nfield library
67 
68     def __init__(self, *args, **kwargs):
69         self.event = kwargs.pop('event', None)
70         if self.event:
71             kwargs['locales'] = self.event.settings.get('locales')
72         super().__init__(*args, **kwargs)
73 
74 
75 class I18nInlineFormSet(i18nfield.forms.I18nInlineFormSet):
76     # compatibility shim for django-i18nfield library
77 
78     def __init__(self, *args, **kwargs):
79         event = kwargs.pop('event', None)
80         if event:
81             kwargs['locales'] = event.settings.get('locales')
82         super().__init__(*args, **kwargs)
83 
84 
85 SECRET_REDACTED = '*****'
86 
87 
88 class SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):
89     auto_fields = []
90 
91     def __init__(self, *args, **kwargs):
92         from pretix.base.settings import DEFAULTS
93 
94         self.obj = kwargs.get('obj', None)
95         self.locales = self.obj.settings.get('locales') if self.obj else kwargs.pop('locales', None)
96         kwargs['attribute_name'] = 'settings'
97         kwargs['locales'] = self.locales
98         kwargs['initial'] = self.obj.settings.freeze()
99         super().__init__(*args, **kwargs)
100         for fname in self.auto_fields:
101             kwargs = DEFAULTS[fname].get('form_kwargs', {})
102             if callable(kwargs):
103                 kwargs = kwargs()
104             kwargs.setdefault('required', False)
105             field = DEFAULTS[fname]['form_class'](
106                 **kwargs
107             )
108             if isinstance(field, i18nfield.forms.I18nFormField):
109                 field.widget.enabled_locales = self.locales
110             self.fields[fname] = field
111         for k, f in self.fields.items():
112             if isinstance(f, (RelativeDateTimeField, RelativeDateField)):
113                 f.set_event(self.obj)
114 
115     def save(self):
116         for k, v in self.cleaned_data.items():
117             if isinstance(self.fields.get(k), SecretKeySettingsField) and self.cleaned_data.get(k) == SECRET_REDACTED:
118                 self.cleaned_data[k] = self.initial[k]
119         return super().save()
120 
121     def clean(self):
122         d = super().clean()
123 
124         # There is logic in HierarkeyForm.save() to only persist fields that changed. HierarkeyForm determines if
125         # something changed by comparing `self._s.get(name)` to `value`. This leaves an edge case open for multi-lingual
126         # text fields. On the very first load, the initial value in `self._s.get(name)` will be a LazyGettextProxy-based
127         # string. However, only some of the languages are usually visible, so even if the user does not change anything
128         # at all, it will be considered a changed value and stored. We do not want that, as it makes it very hard to add
129         # languages to an organizer/event later on. So we trick it and make sure nothing gets changed in that situation.
130         for name, field in self.fields.items():
131             if isinstance(field, i18nfield.forms.I18nFormField):
132                 value = d.get(name)
133                 if not value:
134                     continue
135 
136                 current = self._s.get(name, as_type=type(value))
137                 if name not in self.changed_data:
138                     d[name] = current
139 
140         return d
141 
142     def get_new_filename(self, name: str) -> str:
143         from pretix.base.models import Event
144 
145         nonce = get_random_string(length=8)
146         if isinstance(self.obj, Event):
147             fname = '%s/%s/%s.%s.%s' % (
148                 self.obj.organizer.slug, self.obj.slug, name, nonce, name.split('.')[-1]
149             )
150         else:
151             fname = '%s/%s.%s.%s' % (self.obj.slug, name, nonce, name.split('.')[-1])
152         # TODO: make sure pub is always correct
153         return 'pub/' + fname
154 
155 
156 class PrefixForm(forms.Form):
157     prefix = forms.CharField(widget=forms.HiddenInput)
158 
159 
160 class SafeSessionWizardView(SessionWizardView):
161     def get_prefix(self, request, *args, **kwargs):
162         if hasattr(request, '_session_wizard_prefix'):
163             return request._session_wizard_prefix
164         prefix_form = PrefixForm(self.request.POST, prefix=super().get_prefix(request, *args, **kwargs))
165         if not prefix_form.is_valid():
166             request._session_wizard_prefix = get_random_string(length=24)
167         else:
168             request._session_wizard_prefix = prefix_form.cleaned_data['prefix']
169         return request._session_wizard_prefix
170 
171     def get_context_data(self, form, **kwargs):
172         context = super().get_context_data(form=form, **kwargs)
173         context['wizard']['prefix_form'] = PrefixForm(
174             prefix=super().get_prefix(self.request),
175             initial={
176                 'prefix': self.get_prefix(self.request)
177             }
178         )
179         return context
180 
181 
182 class SecretKeySettingsWidget(forms.TextInput):
183     def __init__(self, attrs=None):
184         if attrs is None:
185             attrs = {}
186         attrs.update({
187             'autocomplete': 'new-password'  # see https://bugs.chromium.org/p/chromium/issues/detail?id=370363#c7
188         })
189         super().__init__(attrs)
190 
191     def get_context(self, name, value, attrs):
192         if value:
193             value = SECRET_REDACTED
194         return super().get_context(name, value, attrs)
195 
196 
197 class SecretKeySettingsField(forms.CharField):
198     widget = SecretKeySettingsWidget
199 
200     def has_changed(self, initial, data):
201         if data == SECRET_REDACTED:
202             return False
203         return super().has_changed(initial, data)
204 
205     def run_validators(self, value):
206         if value == SECRET_REDACTED:
207             return
208         return super().run_validators(value)
209
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pretix/base/forms/__init__.py b/src/pretix/base/forms/__init__.py
--- a/src/pretix/base/forms/__init__.py
+++ b/src/pretix/base/forms/__init__.py
@@ -38,6 +38,7 @@
 from django import forms
 from django.forms.models import ModelFormMetaclass
 from django.utils.crypto import get_random_string
+from django.utils.translation import gettext_lazy as _
 from formtools.wizard.views import SessionWizardView
 from hierarkey.forms import HierarkeyForm
 
@@ -128,6 +129,12 @@
         # at all, it will be considered a changed value and stored. We do not want that, as it makes it very hard to add
         # languages to an organizer/event later on. So we trick it and make sure nothing gets changed in that situation.
         for name, field in self.fields.items():
+            if isinstance(field, SecretKeySettingsField) and d.get(name) == SECRET_REDACTED and not self.initial.get(name):
+                self.add_error(
+                    name,
+                    _('Due to technical reasons you cannot set inputs, that need to be masked (e.g. passwords), to %(value)s.') % {'value': SECRET_REDACTED}
+                )
+
             if isinstance(field, i18nfield.forms.I18nFormField):
                 value = d.get(name)
                 if not value:

{"golden_diff": "diff --git a/src/pretix/base/forms/__init__.py b/src/pretix/base/forms/__init__.py\n--- a/src/pretix/base/forms/__init__.py\n+++ b/src/pretix/base/forms/__init__.py\n@@ -38,6 +38,7 @@\n from django import forms\n from django.forms.models import ModelFormMetaclass\n from django.utils.crypto import get_random_string\n+from django.utils.translation import gettext_lazy as _\n from formtools.wizard.views import SessionWizardView\n from hierarkey.forms import HierarkeyForm\n \n@@ -128,6 +129,12 @@\n         # at all, it will be considered a changed value and stored. We do not want that, as it makes it very hard to add\n         # languages to an organizer/event later on. So we trick it and make sure nothing gets changed in that situation.\n         for name, field in self.fields.items():\n+            if isinstance(field, SecretKeySettingsField) and d.get(name) == SECRET_REDACTED and not self.initial.get(name):\n+                self.add_error(\n+                    name,\n+                    _('Due to technical reasons you cannot set inputs, that need to be masked (e.g. passwords), to %(value)s.') % {'value': SECRET_REDACTED}\n+                )\n+\n             if isinstance(field, i18nfield.forms.I18nFormField):\n                 value = d.get(name)\n                 if not value:\n", "issue": "Internal Server Error: KeyError: 'payment_stripe_secret_key'\nwhen entering \"*****\" as secret key into the stripe payment plugin I get this internal server error:\r\n\r\n```\r\nERROR 2022-01-29 19:52:01,164 django.request log Internal Server Error: /control/event/test/test/settings/payment/stripe_settings\r\nTraceback (most recent call last):\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/django/core/handlers/exception.py\", line 47, in inner\r\n    response = get_response(request)\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/django/core/handlers/base.py\", line 181, in _get_response\r\n    response = wrapped_callback(request, *callback_args, **callback_kwargs)\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/pretix/control/permissions.py\", line 69, in wrapper\r\n    return function(request, *args, **kw)\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/django/views/generic/base.py\", line 70, in view\r\n    return self.dispatch(request, *args, **kwargs)\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/pretix/control/views/event.py\", line 431, in dispatch\r\n    return super().dispatch(request, *args, **kwargs)\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/django/views/generic/base.py\", line 98, in dispatch\r\n    return handler(request, *args, **kwargs)\r\n  File \"/usr/lib/python3.8/contextlib.py\", line 75, in inner\r\n    return func(*args, **kwds)\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/pretix/control/views/event.py\", line 453, in post\r\n    self.form.save()\r\n  File \"/var/pretix/venv/lib/python3.8/site-packages/pretix/base/forms/__init__.py\", line 118, in save\r\n    self.cleaned_data[k] = self.initial[k]\r\nKeyError: 'payment_stripe_secret_key'\r\n```\n", "before_files": [{"content": "#\n# This file is part of pretix (Community Edition).\n#\n# Copyright (C) 2014-2020 Raphael Michel and contributors\n# Copyright (C) 2020-2021 rami.io GmbH and contributors\n#\n# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General\n# Public License as published by the Free Software Foundation in version 3 of the License.\n#\n# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are\n# applicable granting you additional permissions and placing additional restrictions on your usage of this software.\n# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive\n# this file, see <https://pretix.eu/about/en/license>.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along with this program.  If not, see\n# <https://www.gnu.org/licenses/>.\n#\n\n# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of\n# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.\n#\n# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A\n# full history of changes and contributors is available at <https://github.com/pretix/pretix>.\n#\n# This file contains Apache-licensed contributions copyrighted by: Alexey Kislitsin, Tobias Kunze\n#\n# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations under the License.\n\nimport logging\n\nimport i18nfield.forms\nfrom django import forms\nfrom django.forms.models import ModelFormMetaclass\nfrom django.utils.crypto import get_random_string\nfrom formtools.wizard.views import SessionWizardView\nfrom hierarkey.forms import HierarkeyForm\n\nfrom pretix.base.reldate import RelativeDateField, RelativeDateTimeField\n\nfrom .validators import PlaceholderValidator  # NOQA\n\nlogger = logging.getLogger(__name__)\n\n\nclass BaseI18nModelForm(i18nfield.forms.BaseI18nModelForm):\n    # compatibility shim for django-i18nfield library\n\n    def __init__(self, *args, **kwargs):\n        self.event = kwargs.pop('event', None)\n        if self.event:\n            kwargs['locales'] = self.event.settings.get('locales')\n        super().__init__(*args, **kwargs)\n\n\nclass I18nModelForm(BaseI18nModelForm, metaclass=ModelFormMetaclass):\n    pass\n\n\nclass I18nFormSet(i18nfield.forms.I18nModelFormSet):\n    # compatibility shim for django-i18nfield library\n\n    def __init__(self, *args, **kwargs):\n        self.event = kwargs.pop('event', None)\n        if self.event:\n            kwargs['locales'] = self.event.settings.get('locales')\n        super().__init__(*args, **kwargs)\n\n\nclass I18nInlineFormSet(i18nfield.forms.I18nInlineFormSet):\n    # compatibility shim for django-i18nfield library\n\n    def __init__(self, *args, **kwargs):\n        event = kwargs.pop('event', None)\n        if event:\n            kwargs['locales'] = event.settings.get('locales')\n        super().__init__(*args, **kwargs)\n\n\nSECRET_REDACTED = '*****'\n\n\nclass SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):\n    auto_fields = []\n\n    def __init__(self, *args, **kwargs):\n        from pretix.base.settings import DEFAULTS\n\n        self.obj = kwargs.get('obj', None)\n        self.locales = self.obj.settings.get('locales') if self.obj else kwargs.pop('locales', None)\n        kwargs['attribute_name'] = 'settings'\n        kwargs['locales'] = self.locales\n        kwargs['initial'] = self.obj.settings.freeze()\n        super().__init__(*args, **kwargs)\n        for fname in self.auto_fields:\n            kwargs = DEFAULTS[fname].get('form_kwargs', {})\n            if callable(kwargs):\n                kwargs = kwargs()\n            kwargs.setdefault('required', False)\n            field = DEFAULTS[fname]['form_class'](\n                **kwargs\n            )\n            if isinstance(field, i18nfield.forms.I18nFormField):\n                field.widget.enabled_locales = self.locales\n            self.fields[fname] = field\n        for k, f in self.fields.items():\n            if isinstance(f, (RelativeDateTimeField, RelativeDateField)):\n                f.set_event(self.obj)\n\n    def save(self):\n        for k, v in self.cleaned_data.items():\n            if isinstance(self.fields.get(k), SecretKeySettingsField) and self.cleaned_data.get(k) == SECRET_REDACTED:\n                self.cleaned_data[k] = self.initial[k]\n        return super().save()\n\n    def clean(self):\n        d = super().clean()\n\n        # There is logic in HierarkeyForm.save() to only persist fields that changed. HierarkeyForm determines if\n        # something changed by comparing `self._s.get(name)` to `value`. This leaves an edge case open for multi-lingual\n        # text fields. On the very first load, the initial value in `self._s.get(name)` will be a LazyGettextProxy-based\n        # string. However, only some of the languages are usually visible, so even if the user does not change anything\n        # at all, it will be considered a changed value and stored. We do not want that, as it makes it very hard to add\n        # languages to an organizer/event later on. So we trick it and make sure nothing gets changed in that situation.\n        for name, field in self.fields.items():\n            if isinstance(field, i18nfield.forms.I18nFormField):\n                value = d.get(name)\n                if not value:\n                    continue\n\n                current = self._s.get(name, as_type=type(value))\n                if name not in self.changed_data:\n                    d[name] = current\n\n        return d\n\n    def get_new_filename(self, name: str) -> str:\n        from pretix.base.models import Event\n\n        nonce = get_random_string(length=8)\n        if isinstance(self.obj, Event):\n            fname = '%s/%s/%s.%s.%s' % (\n                self.obj.organizer.slug, self.obj.slug, name, nonce, name.split('.')[-1]\n            )\n        else:\n            fname = '%s/%s.%s.%s' % (self.obj.slug, name, nonce, name.split('.')[-1])\n        # TODO: make sure pub is always correct\n        return 'pub/' + fname\n\n\nclass PrefixForm(forms.Form):\n    prefix = forms.CharField(widget=forms.HiddenInput)\n\n\nclass SafeSessionWizardView(SessionWizardView):\n    def get_prefix(self, request, *args, **kwargs):\n        if hasattr(request, '_session_wizard_prefix'):\n            return request._session_wizard_prefix\n        prefix_form = PrefixForm(self.request.POST, prefix=super().get_prefix(request, *args, **kwargs))\n        if not prefix_form.is_valid():\n            request._session_wizard_prefix = get_random_string(length=24)\n        else:\n            request._session_wizard_prefix = prefix_form.cleaned_data['prefix']\n        return request._session_wizard_prefix\n\n    def get_context_data(self, form, **kwargs):\n        context = super().get_context_data(form=form, **kwargs)\n        context['wizard']['prefix_form'] = PrefixForm(\n            prefix=super().get_prefix(self.request),\n            initial={\n                'prefix': self.get_prefix(self.request)\n            }\n        )\n        return context\n\n\nclass SecretKeySettingsWidget(forms.TextInput):\n    def __init__(self, attrs=None):\n        if attrs is None:\n            attrs = {}\n        attrs.update({\n            'autocomplete': 'new-password'  # see https://bugs.chromium.org/p/chromium/issues/detail?id=370363#c7\n        })\n        super().__init__(attrs)\n\n    def get_context(self, name, value, attrs):\n        if value:\n            value = SECRET_REDACTED\n        return super().get_context(name, value, attrs)\n\n\nclass SecretKeySettingsField(forms.CharField):\n    widget = SecretKeySettingsWidget\n\n    def has_changed(self, initial, data):\n        if data == SECRET_REDACTED:\n            return False\n        return super().has_changed(initial, data)\n\n    def run_validators(self, value):\n        if value == SECRET_REDACTED:\n            return\n        return super().run_validators(value)\n", "path": "src/pretix/base/forms/__init__.py"}], "after_files": [{"content": "#\n# This file is part of pretix (Community Edition).\n#\n# Copyright (C) 2014-2020 Raphael Michel and contributors\n# Copyright (C) 2020-2021 rami.io GmbH and contributors\n#\n# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General\n# Public License as published by the Free Software Foundation in version 3 of the License.\n#\n# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are\n# applicable granting you additional permissions and placing additional restrictions on your usage of this software.\n# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive\n# this file, see <https://pretix.eu/about/en/license>.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along with this program.  If not, see\n# <https://www.gnu.org/licenses/>.\n#\n\n# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of\n# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.\n#\n# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A\n# full history of changes and contributors is available at <https://github.com/pretix/pretix>.\n#\n# This file contains Apache-licensed contributions copyrighted by: Alexey Kislitsin, Tobias Kunze\n#\n# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations under the License.\n\nimport logging\n\nimport i18nfield.forms\nfrom django import forms\nfrom django.forms.models import ModelFormMetaclass\nfrom django.utils.crypto import get_random_string\nfrom django.utils.translation import gettext_lazy as _\nfrom formtools.wizard.views import SessionWizardView\nfrom hierarkey.forms import HierarkeyForm\n\nfrom pretix.base.reldate import RelativeDateField, RelativeDateTimeField\n\nfrom .validators import PlaceholderValidator  # NOQA\n\nlogger = logging.getLogger(__name__)\n\n\nclass BaseI18nModelForm(i18nfield.forms.BaseI18nModelForm):\n    # compatibility shim for django-i18nfield library\n\n    def __init__(self, *args, **kwargs):\n        self.event = kwargs.pop('event', None)\n        if self.event:\n            kwargs['locales'] = self.event.settings.get('locales')\n        super().__init__(*args, **kwargs)\n\n\nclass I18nModelForm(BaseI18nModelForm, metaclass=ModelFormMetaclass):\n    pass\n\n\nclass I18nFormSet(i18nfield.forms.I18nModelFormSet):\n    # compatibility shim for django-i18nfield library\n\n    def __init__(self, *args, **kwargs):\n        self.event = kwargs.pop('event', None)\n        if self.event:\n            kwargs['locales'] = self.event.settings.get('locales')\n        super().__init__(*args, **kwargs)\n\n\nclass I18nInlineFormSet(i18nfield.forms.I18nInlineFormSet):\n    # compatibility shim for django-i18nfield library\n\n    def __init__(self, *args, **kwargs):\n        event = kwargs.pop('event', None)\n        if event:\n            kwargs['locales'] = event.settings.get('locales')\n        super().__init__(*args, **kwargs)\n\n\nSECRET_REDACTED = '*****'\n\n\nclass SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):\n    auto_fields = []\n\n    def __init__(self, *args, **kwargs):\n        from pretix.base.settings import DEFAULTS\n\n        self.obj = kwargs.get('obj', None)\n        self.locales = self.obj.settings.get('locales') if self.obj else kwargs.pop('locales', None)\n        kwargs['attribute_name'] = 'settings'\n        kwargs['locales'] = self.locales\n        kwargs['initial'] = self.obj.settings.freeze()\n        super().__init__(*args, **kwargs)\n        for fname in self.auto_fields:\n            kwargs = DEFAULTS[fname].get('form_kwargs', {})\n            if callable(kwargs):\n                kwargs = kwargs()\n            kwargs.setdefault('required', False)\n            field = DEFAULTS[fname]['form_class'](\n                **kwargs\n            )\n            if isinstance(field, i18nfield.forms.I18nFormField):\n                field.widget.enabled_locales = self.locales\n            self.fields[fname] = field\n        for k, f in self.fields.items():\n            if isinstance(f, (RelativeDateTimeField, RelativeDateField)):\n                f.set_event(self.obj)\n\n    def save(self):\n        for k, v in self.cleaned_data.items():\n            if isinstance(self.fields.get(k), SecretKeySettingsField) and self.cleaned_data.get(k) == SECRET_REDACTED:\n                self.cleaned_data[k] = self.initial[k]\n        return super().save()\n\n    def clean(self):\n        d = super().clean()\n\n        # There is logic in HierarkeyForm.save() to only persist fields that changed. HierarkeyForm determines if\n        # something changed by comparing `self._s.get(name)` to `value`. This leaves an edge case open for multi-lingual\n        # text fields. On the very first load, the initial value in `self._s.get(name)` will be a LazyGettextProxy-based\n        # string. However, only some of the languages are usually visible, so even if the user does not change anything\n        # at all, it will be considered a changed value and stored. We do not want that, as it makes it very hard to add\n        # languages to an organizer/event later on. So we trick it and make sure nothing gets changed in that situation.\n        for name, field in self.fields.items():\n            if isinstance(field, SecretKeySettingsField) and d.get(name) == SECRET_REDACTED and not self.initial.get(name):\n                self.add_error(\n                    name,\n                    _('Due to technical reasons you cannot set inputs, that need to be masked (e.g. passwords), to %(value)s.') % {'value': SECRET_REDACTED}\n                )\n\n            if isinstance(field, i18nfield.forms.I18nFormField):\n                value = d.get(name)\n                if not value:\n                    continue\n\n                current = self._s.get(name, as_type=type(value))\n                if name not in self.changed_data:\n                    d[name] = current\n\n        return d\n\n    def get_new_filename(self, name: str) -> str:\n        from pretix.base.models import Event\n\n        nonce = get_random_string(length=8)\n        if isinstance(self.obj, Event):\n            fname = '%s/%s/%s.%s.%s' % (\n                self.obj.organizer.slug, self.obj.slug, name, nonce, name.split('.')[-1]\n            )\n        else:\n            fname = '%s/%s.%s.%s' % (self.obj.slug, name, nonce, name.split('.')[-1])\n        # TODO: make sure pub is always correct\n        return 'pub/' + fname\n\n\nclass PrefixForm(forms.Form):\n    prefix = forms.CharField(widget=forms.HiddenInput)\n\n\nclass SafeSessionWizardView(SessionWizardView):\n    def get_prefix(self, request, *args, **kwargs):\n        if hasattr(request, '_session_wizard_prefix'):\n            return request._session_wizard_prefix\n        prefix_form = PrefixForm(self.request.POST, prefix=super().get_prefix(request, *args, **kwargs))\n        if not prefix_form.is_valid():\n            request._session_wizard_prefix = get_random_string(length=24)\n        else:\n            request._session_wizard_prefix = prefix_form.cleaned_data['prefix']\n        return request._session_wizard_prefix\n\n    def get_context_data(self, form, **kwargs):\n        context = super().get_context_data(form=form, **kwargs)\n        context['wizard']['prefix_form'] = PrefixForm(\n            prefix=super().get_prefix(self.request),\n            initial={\n                'prefix': self.get_prefix(self.request)\n            }\n        )\n        return context\n\n\nclass SecretKeySettingsWidget(forms.TextInput):\n    def __init__(self, attrs=None):\n        if attrs is None:\n            attrs = {}\n        attrs.update({\n            'autocomplete': 'new-password'  # see https://bugs.chromium.org/p/chromium/issues/detail?id=370363#c7\n        })\n        super().__init__(attrs)\n\n    def get_context(self, name, value, attrs):\n        if value:\n            value = SECRET_REDACTED\n        return super().get_context(name, value, attrs)\n\n\nclass SecretKeySettingsField(forms.CharField):\n    widget = SecretKeySettingsWidget\n\n    def has_changed(self, initial, data):\n        if data == SECRET_REDACTED:\n            return False\n        return super().has_changed(initial, data)\n\n    def run_validators(self, value):\n        if value == SECRET_REDACTED:\n            return\n        return super().run_validators(value)\n", "path": "src/pretix/base/forms/__init__.py"}]}