From ca021d0628ca3f3123e4a7d23dee9c84b1451207 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 18 Mar 2026 13:10:42 +0100 Subject: [PATCH] feat: add custom plugins and dockerfile --- .drone.yml | 39 --- .env.sample | 8 - .gitignore | 1 - Dockerfile | 19 ++ README.md | 153 ++++++++- abra.sh | 2 - compose.yml | 39 --- plugins.txt | 26 ++ plugins/attendance_confirm_plugin/MANIFEST.in | 4 + plugins/attendance_confirm_plugin/README.md | 19 ++ .../PKG-INFO | 28 ++ .../SOURCES.txt | 15 + .../dependency_links.txt | 1 + .../entry_points.txt | 2 + .../requires.txt | 1 + .../top_level.txt | 1 + .../pretix_attendance_confirm/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 266 bytes .../__pycache__/apps.cpython-313.pyc | Bin 0 -> 1453 bytes .../__pycache__/forms.cpython-313.pyc | Bin 0 -> 1902 bytes .../__pycache__/signals.cpython-313.pyc | Bin 0 -> 2281 bytes .../__pycache__/urls.cpython-313.pyc | Bin 0 -> 543 bytes .../__pycache__/views.cpython-313.pyc | Bin 0 -> 12255 bytes .../pretix_attendance_confirm/apps.py | 22 ++ .../pretix_attendance_confirm/forms.py | 33 ++ .../pretix_attendance_confirm/signals.py | 41 +++ .../attendance_confirm/presets.js | 166 ++++++++++ .../attendance_confirm/control_preset.html | 13 + .../attendance_confirm/send.html | 35 +++ .../pretix_attendance_confirm/urls.py | 13 + .../pretix_attendance_confirm/views.py | 186 +++++++++++ .../attendance_confirm_plugin/pyproject.toml | 3 + plugins/attendance_confirm_plugin/setup.cfg | 38 +++ plugins/selective_export_plugin/MANIFEST.in | 4 + plugins/selective_export_plugin/README.md | 48 +++ .../pretix_selective_export.egg-info/PKG-INFO | 63 ++++ .../SOURCES.txt | 13 + .../dependency_links.txt | 1 + .../entry_points.txt | 2 + .../requires.txt | 1 + .../top_level.txt | 1 + .../pretix_selective_export/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 234 bytes .../__pycache__/apps.cpython-313.pyc | Bin 0 -> 1379 bytes .../__pycache__/exporter.cpython-313.pyc | Bin 0 -> 17966 bytes .../__pycache__/signals.cpython-313.pyc | Bin 0 -> 4018 bytes .../pretix_selective_export/apps.py | 21 ++ .../pretix_selective_export/exporter.py | 293 ++++++++++++++++++ .../pretix_selective_export/signals.py | 80 +++++ .../pretixplugins/selective_export/presets.js | 189 +++++++++++ .../selective_export/control_preset.html | 13 + .../selective_export_plugin/pyproject.toml | 3 + plugins/selective_export_plugin/setup.cfg | 38 +++ release/.git-keep-me | 0 54 files changed, 1574 insertions(+), 106 deletions(-) delete mode 100644 .drone.yml delete mode 100644 .env.sample delete mode 100644 .gitignore create mode 100644 Dockerfile delete mode 100755 abra.sh delete mode 100644 compose.yml create mode 100644 plugins.txt create mode 100644 plugins/attendance_confirm_plugin/MANIFEST.in create mode 100644 plugins/attendance_confirm_plugin/README.md create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/PKG-INFO create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/SOURCES.txt create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/dependency_links.txt create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/entry_points.txt create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/requires.txt create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/top_level.txt create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/__init__.py create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/__init__.cpython-313.pyc create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/apps.cpython-313.pyc create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/forms.cpython-313.pyc create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/signals.cpython-313.pyc create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/urls.cpython-313.pyc create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/views.cpython-313.pyc create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/apps.py create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/forms.py create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/signals.py create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/static/pretixplugins/attendance_confirm/presets.js create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/control_preset.html create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/send.html create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/urls.py create mode 100644 plugins/attendance_confirm_plugin/pretix_attendance_confirm/views.py create mode 100644 plugins/attendance_confirm_plugin/pyproject.toml create mode 100644 plugins/attendance_confirm_plugin/setup.cfg create mode 100644 plugins/selective_export_plugin/MANIFEST.in create mode 100644 plugins/selective_export_plugin/README.md create mode 100644 plugins/selective_export_plugin/pretix_selective_export.egg-info/PKG-INFO create mode 100644 plugins/selective_export_plugin/pretix_selective_export.egg-info/SOURCES.txt create mode 100644 plugins/selective_export_plugin/pretix_selective_export.egg-info/dependency_links.txt create mode 100644 plugins/selective_export_plugin/pretix_selective_export.egg-info/entry_points.txt create mode 100644 plugins/selective_export_plugin/pretix_selective_export.egg-info/requires.txt create mode 100644 plugins/selective_export_plugin/pretix_selective_export.egg-info/top_level.txt create mode 100644 plugins/selective_export_plugin/pretix_selective_export/__init__.py create mode 100644 plugins/selective_export_plugin/pretix_selective_export/__pycache__/__init__.cpython-313.pyc create mode 100644 plugins/selective_export_plugin/pretix_selective_export/__pycache__/apps.cpython-313.pyc create mode 100644 plugins/selective_export_plugin/pretix_selective_export/__pycache__/exporter.cpython-313.pyc create mode 100644 plugins/selective_export_plugin/pretix_selective_export/__pycache__/signals.cpython-313.pyc create mode 100644 plugins/selective_export_plugin/pretix_selective_export/apps.py create mode 100644 plugins/selective_export_plugin/pretix_selective_export/exporter.py create mode 100644 plugins/selective_export_plugin/pretix_selective_export/signals.py create mode 100644 plugins/selective_export_plugin/pretix_selective_export/static/pretixplugins/selective_export/presets.js create mode 100644 plugins/selective_export_plugin/pretix_selective_export/templates/pretixplugins/selective_export/control_preset.html create mode 100644 plugins/selective_export_plugin/pyproject.toml create mode 100644 plugins/selective_export_plugin/setup.cfg delete mode 100644 release/.git-keep-me diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 8139990..0000000 --- a/.drone.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -kind: pipeline -name: deploy to swarm-test.autonomic.zone -steps: - - name: deployment - image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest - settings: - host: swarm-test.autonomic.zone - stack: example_com # UPDATE ME - generate_secrets: true - purge: true - deploy_key: - from_secret: drone_ssh_swarm_test - networks: - - proxy - environment: - DOMAIN: example.swarm-test.autonomic.zone # UPDATE ME - STACK_NAME: example_com # UPDATE ME - LETS_ENCRYPT_ENV: staging - # Also set any config versions from abra.sh -trigger: - branch: - - main ---- -kind: pipeline -name: generate recipe catalogue -steps: - - name: release a new version - image: plugins/downstream - settings: - server: https://build.coopcloud.tech - token: - from_secret: drone_abra-bot_token - fork: true - repositories: - - toolshed/auto-recipes-catalogue-json - -trigger: - event: tag diff --git a/.env.sample b/.env.sample deleted file mode 100644 index c079a31..0000000 --- a/.env.sample +++ /dev/null @@ -1,8 +0,0 @@ -TYPE={{ .Name }} - -DOMAIN={{ .Name }}.example.com - -## Domain aliases -#EXTRA_DOMAINS=', `www.{{ .Name }}.example.com`' - -LETS_ENCRYPT_ENV=production diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7a6353d..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.envrc diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eeba00f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM pretix/standalone:stable + +USER root + +# Copy the entire repo so local plugin paths in plugins.txt resolve correctly +COPY . /tmp/bundle/ + +# Install all plugins listed in plugins.txt. +# Lines starting with # and empty lines are ignored. +# Local paths (./plugins/foo/) are resolved relative to /tmp/bundle/. +RUN cd /tmp/bundle && \ + grep -v '^\s*#' plugins.txt | grep -v '^\s*$' | \ + xargs pip3 install && \ + rm -rf /tmp/bundle + +USER pretixuser + +# Rebuild static files and asset pipeline after plugin installation +RUN cd /pretix/src && make production \ No newline at end of file diff --git a/README.md b/README.md index aa78cfe..e5228a3 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,143 @@ -# {{ .Name }} +# Pretix Plugins -{{ .Description }} +A community-maintained mono-repository of custom pretix plugins and tooling to +build a bundled Docker image for use with the +[Co-op Cloud pretix recipe](https://git.coopcloud.tech/coop-cloud/pretix). - +--- -* **Category**: {{ .Category }} -* **Status**: {{ .Status }} -* **Image**: {{ .Image }} -* **Healthcheck**: {{ .Healthcheck }} -* **Backups**: {{ .Backups }} -* **Email**: {{ .Email }} -* **Tests**: {{ .Tests }} -* **SSO**: {{ .SSO }} +## How it works - +``` +plugins/ ← plugin source code lives here (Python packages) + my-plugin/ + pretix_my_plugin/ + setup.py +plugins.txt ← select which plugins go into the Docker image +Dockerfile ← builds the image from plugins.txt +.gitea/workflows/ ← CI: automatically builds & pushes image on every tag +``` -## Quick start +Each plugin is a standard Python package inside `plugins/`. The `plugins.txt` +file controls which of those plugins (and optionally any PyPI or external +plugins) are installed into the final Docker image. -* `abra app new {{ .Name }} --secrets` -* `abra app config ` -* `abra app deploy ` +The resulting image is a drop-in replacement for `pretix/standalone` and is +used via the optional `compose.plugin.yml` override in the pretix recipe. -For more, see [`docs.coopcloud.tech`](https://docs.coopcloud.tech). +--- + +## Using the image in your pretix deployment + +In your app `.env` file (managed by abra): + +```bash +COMPOSE_FILE=compose.yml:compose.plugin.yml +PRETIX_PLUGIN_IMAGE=git.coopcloud.tech//pretix-plugins:1.0.0 +``` + +If `PRETIX_PLUGIN_IMAGE` is not set, the standard `pretix/standalone` image +is used — operators who do not need plugins are not affected at all. + +--- + +## Selecting plugins + +Edit `plugins.txt` to choose which plugins are included in your image. +Each non-empty, non-comment line is passed directly to `pip install`. + +``` +# Local plugins from this repo (recommended for custom plugins): +./plugins/attendance_confirm_plugin/ +./plugins/selective_export_plugin/ + +# Community plugins from PyPI: +pretix-pages + +# External plugins via Git URL: +git+https://git.coopcloud.tech/other-org/pretix-some-plugin.git@1.0.0 +``` + +Comment out or remove a line to exclude a plugin from the image. + +--- + +## Building and publishing the image + +### Automatic (recommended) + +Push a Git tag to trigger the Gitea Actions workflow: + +```bash +git tag 1.0.0 +git push origin 1.0.0 +``` + +The CI pipeline will build and push: +- `git.coopcloud.tech//pretix-plugins:1.0.0` +- `git.coopcloud.tech//pretix-plugins:latest` + +### Manual + +```bash +docker build -t git.coopcloud.tech//pretix-plugins:1.0.0 . +docker push git.coopcloud.tech//pretix-plugins:1.0.0 +``` + +--- + +## Contributing a plugin + +1. Create a new directory under `plugins/` named after your plugin: + ``` + plugins/my-new-plugin/ + ``` + +2. Add a valid pretix plugin Python package inside it (see structure below). + +3. Add it to `plugins.txt` if you want it included in the default image build. + +4. Open a pull request. + +### Required plugin structure + +``` +plugins/my-new-plugin/ +├── pretix_my_new_plugin/ +│ ├── __init__.py +│ └── apps.py ← AppConfig + PretixPluginMeta required +└── setup.py ← entry_points for pretix.plugin required +``` + +See `plugins/attendance_confirm_plugin/` for a fully working reference implementation. + +### Plugin checklist before opening a PR + +- [ ] `setup.py` contains a valid `pretix.plugin` entry point +- [ ] `apps.py` contains `AppConfig` with a `PretixPluginMeta` inner class +- [ ] Plugin installs cleanly with `pip install ./plugins/my-new-plugin/` +- [ ] No hard-coded credentials or environment-specific configuration + +--- + +## Repository structure + +``` +pretix-plugin-catalogue/ +├── README.md +├── Dockerfile ← builds the bundle image +├── plugins.txt ← controls which plugins are included +├── .gitea/ +│ └── workflows/ +│ └── docker.yml ← CI: build & push on tag +└── plugins/ + └── example-plugin/ ← reference plugin implementation + ├── pretix_example_plugin/ + │ ├── __init__.py + │ └── apps.py + └── setup.py +``` + +## Credits + +The plugins attendance_confirm_plugin and selective_export_plugin were developed by make IT social (https://makeitsocial.net/). Thanks! \ No newline at end of file diff --git a/abra.sh b/abra.sh deleted file mode 100755 index 13b5452..0000000 --- a/abra.sh +++ /dev/null @@ -1,2 +0,0 @@ -# Set any config versions here -# Docs: https://docs.coopcloud.tech/maintainers/handbook/#manage-configs diff --git a/compose.yml b/compose.yml deleted file mode 100644 index 5e77224..0000000 --- a/compose.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -services: - app: - image: nginx:1.27.5 - networks: - - proxy - deploy: - restart_policy: - condition: on-failure - labels: - - "traefik.enable=true" - - "traefik.http.services.${STACK_NAME}.loadbalancer.server.port=80" - - "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})" - - "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure" - - "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}" - ## Edit the following line if you are using one, but not both, "Redirect" sections below - #- "traefik.http.routers.${STACK_NAME}.middlewares=${STACK_NAME}-redirectscheme,${STACK_NAME}-redirecthostname" - ## Redirect from EXTRA_DOMAINS to DOMAIN - # - "traefik.http.middlewares.${STACK_NAME}-redirecthostname.redirectregex.regex=^http[s]?://([^/]*)/(.*)" - # - "traefik.http.middlewares.${STACK_NAME}-redirecthostname.redirectregex.replacement=https://${DOMAIN}/$${2}" - # - "traefik.http.middlewares.${STACK_NAME}-redirecthostname.redirectregex.permanent=true" - ## Redirect HTTP to HTTPS - # - "traefik.http.middlewares.${STACK_NAME}-redirectscheme.redirectscheme.scheme=https" - # - "traefik.http.middlewares.${STACK_NAME}-redirectscheme.redirectscheme.permanent=true" - ## When you're ready for release, run "abra recipe sync " to set this - - "coop-cloud.${STACK_NAME}.version=" - ## Enable backups: https://docs.coopcloud.tech/maintainers/handbook/#how-do-i-configure-backuprestore - # - "backupbot.backup=true" - # - "backupbot.backup.path=/some/path" - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost"] - interval: 30s - timeout: 10s - retries: 10 - start_period: 1m - -networks: - proxy: - external: true diff --git a/plugins.txt b/plugins.txt new file mode 100644 index 0000000..daa5278 --- /dev/null +++ b/plugins.txt @@ -0,0 +1,26 @@ +# plugins.txt +# ============ +# One plugin per line. pip install syntax is fully supported. +# Comment out or remove a line to exclude a plugin from the image. +# +# Supported formats: +# Local plugin from this repo: +# ./plugins/my-plugin/ +# +# Plugin from PyPI: +# pretix-pages +# +# Plugin from a Git repository at a specific tag: +# git+https://git.coopcloud.tech/my-org/pretix-my-plugin.git@1.0.0 +# +# After editing this file, build a new image: +# git tag 1.x.0 && git push origin 1.x.0 +# ============ + +# --- Local plugins (source code in this repo) --- +./plugins/attendance_confirm_plugin +./selective_export_plugin + +# --- Community plugins from PyPI (uncomment to enable) --- +#pretix-pages +#pretix-fontpack-free \ No newline at end of file diff --git a/plugins/attendance_confirm_plugin/MANIFEST.in b/plugins/attendance_confirm_plugin/MANIFEST.in new file mode 100644 index 0000000..863544d --- /dev/null +++ b/plugins/attendance_confirm_plugin/MANIFEST.in @@ -0,0 +1,4 @@ +recursive-include +pretix_attendance_confirm/templates * +recursive-include +pretix_attendance_confirm/static * \ No newline at end of file diff --git a/plugins/attendance_confirm_plugin/README.md b/plugins/attendance_confirm_plugin/README.md new file mode 100644 index 0000000..f11afc2 --- /dev/null +++ b/plugins/attendance_confirm_plugin/README.md @@ -0,0 +1,19 @@ +# pretix-attendance-confirm (draft) + +pretix plugin to send attendance confirmation emails to attendees. +Emails are sent per attendee (attendee email first, order email as fallback) and can +be customized per event. + +Features: +- Recipient list with checkboxes: checked-in attendees are preselected, you can + deselect or manually include non-checked-in attendees. +- Presets: save/load subject and body directly on the form (no page reload). + Presets are stored in the browser (localStorage) per URL/event. +- Placeholders: {attendee_name} and {event_name}. +- Sending is disabled until the event has ended. + +## Install + +``` +pip install -e /path/to/attendance_confirm_plugin +``` diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/PKG-INFO b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/PKG-INFO new file mode 100644 index 0000000..842f146 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/PKG-INFO @@ -0,0 +1,28 @@ +Metadata-Version: 2.4 +Name: pretix-attendance-confirm +Version: 0.1.0 +Summary: Attendance confirmation email plugin for pretix +Author: Ez for mITs +License: AGPL-3.0-or-later +Classifier: Development Status :: 3 - Alpha +Classifier: Environment :: Plugins +Classifier: Framework :: Django +Classifier: License :: OSI Approved :: GNU Affero General Public License v3 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Office/Business +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +Requires-Dist: pretix>=2024.3 + +# pretix-attendance-confirm (draft) + +pretix plugin to send attendance confirmation emails to checked-in attendees. +Emails are sent per attendee (attendee email first, order email as fallback) and can +be customized per event. + +## Install + +``` +pip install -e /path/to/attendance_confirm_plugin +``` diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/SOURCES.txt b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/SOURCES.txt new file mode 100644 index 0000000..82fddab --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/SOURCES.txt @@ -0,0 +1,15 @@ +README.md +pyproject.toml +setup.cfg +pretix_attendance_confirm/__init__.py +pretix_attendance_confirm/apps.py +pretix_attendance_confirm/forms.py +pretix_attendance_confirm/signals.py +pretix_attendance_confirm/urls.py +pretix_attendance_confirm/views.py +pretix_attendance_confirm.egg-info/PKG-INFO +pretix_attendance_confirm.egg-info/SOURCES.txt +pretix_attendance_confirm.egg-info/dependency_links.txt +pretix_attendance_confirm.egg-info/entry_points.txt +pretix_attendance_confirm.egg-info/requires.txt +pretix_attendance_confirm.egg-info/top_level.txt \ No newline at end of file diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/dependency_links.txt b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/entry_points.txt b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/entry_points.txt new file mode 100644 index 0000000..b8d0423 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[pretix.plugin] +attendance_confirm = pretix_attendance_confirm diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/requires.txt b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/requires.txt new file mode 100644 index 0000000..213e748 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/requires.txt @@ -0,0 +1 @@ +pretix>=2024.3 diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/top_level.txt b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/top_level.txt new file mode 100644 index 0000000..49828ba --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm.egg-info/top_level.txt @@ -0,0 +1 @@ +pretix_attendance_confirm diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__init__.py b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__init__.py new file mode 100644 index 0000000..ed80f0a --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__init__.py @@ -0,0 +1 @@ +default_app_config = "pretix_attendance_confirm.apps.PretixAttendanceConfirmApp" diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/__init__.cpython-313.pyc b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f52e75b958ebacb5796fdb78c4a381e585f0a3f GIT binary patch literal 266 zcmey&%ge<81dOVlnSnt1F^B^Lj8MjB4j^MHLoh=TLpq}-QoI{HSV_PI?irps2OXmLl0% zDu{IQDG<<0FS+;Hf6$*{z!!Oe^iuTTTMIQP&{Jkf#e(}FppP@Vv%~pjztN;r$`gE? zPjWv9*@6 zl}*os(ba3lrF3eFnA?e>Ps8BY>!MTavN&eXW9mD@0s7^`gD0QwHt9j*Y2$z*G>_ag zNs8*!nLI}0C20{*YyufufvlYY)y{&}(q#o(N*W19Qjvxg>08K8K9IAGRRVK%4)Wc( zT3)Ok0*k%pJ6KB4aRQem^+69Sy)^KESc#GV>Rd%oS5knUDF1uy@9Hd*IgN8-FAVrK zF4OLi$q|0yI+vD;95PoNw8YO|`MRxwVQY{}*7d9PIop7Is{lo0R!W%N|9@syx^?LH zyI$}Gi=E4&>`eh-SZEB*;}Fc=?hzkq8`g)`hAp7m! z%{VmO6Xt%)I_q9A4Q7m6!y9QUw;ye8Y<_Hg^b={;ZipPEfzxA@iae#gu+#UYU8MB8 zzT;1K<|)O7^Vs(S7KFI1P@&2_p*#KfBm`kRW5BV^BIi0W>xM88g%0B`c#-VBD7j%T z!i#8oz84Qbs#|D2d&YdRL@AGP%avCQUb`PNPAM#4$OL~xd|5vUd#uie;M7wF>znmi z58ldwMoBxTA^)M5x)Vj*iUwlgpY4Yden(ybe}M3UT+A<=E#5z$|6rtEES7(GbiR0d zq>sxhBV$~7ca*zWe&?+E^nCg2QDIzte^eST-1%OEH9QI?f;-henuJK*OSMfTADm+a zXBX!NvOd+ z8F)*A%#`MoMS+~v1f;=b*b6c)+&lejTr^J`7nPf5OZD^0#_7Rds-fNegCM*vE81F` zLLO??a;TYaoS++8@}@wxO;o2V{TQWf#oz?oPX-#~|4~|JB~^vhb~v+A74Sr2X;s%N s6}gA0%C})1GsyPyRfONOilV$Cw_lM5zmd&9^?7CO$CcLvUrC8?0B8Y(D*ylh literal 0 HcmV?d00001 diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/forms.cpython-313.pyc b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/forms.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eac239285555ea19884d721d0840ad8f30e53399 GIT binary patch literal 1902 zcmaJ>&2Q936d&8WUT3o(X%dnYQe>jg#7R zZo;A>PN;A}J)owia^OJ!2X49aNWy_-6i$^Y<+doT%B_84@4~8$I+o|XnR)Ncd-Hzp z?PjxS1Y>4n)&4z!&~JRv8Ob)YI}gkQq$6EeK<5Q2sGJuU#PbqOsAvR@Azj*!^u)5T z6j|#Si0Mfwrc}0)n!Ljon!u73kJfx(G8PE}vKC;+XxuPk(Rfa^7rRe@`35{#MpV!d z6?K71x(J_C5R2%9peOdD6=g-FiRI){rrSi!kD=uh_?SE?#A{vFQ_~_<{#RP*NP|@W zUN-d}J$+Qrd#8oSdZr_zeItFRxBCAq`BrDNzSw;K&zcoUCU6uR{G$Z(gI^hPT&4SwvE z4L$h*+)xxt;QtkMDc8QRi^LKZ;-4ljhJA2$wpOofJaR2UkJzqD-1)E$yO%v`guX>q zHy%};K=p06tP&ff%qlUj1w_3>Y{wXZ%tG|G^}hqv4~sHk}T;MCE_rJlIx*ONtvZODU(<2GJu)&REbl^9MdfQCb7Pz zu%sW>31te#Jf#>@UKwoQLg)l`-66A4>gR3Zl$n6p&@7Lkz#8QVWC?>>K9jGlMzjZG(=mJ>W1lBo<>NNHI92s(WQsJXF;%A|u-7J! zO&L&%ZrN~6g3TDPxbB3O?Y2eme+8zZLN3&AP+leMy}f{5=tRu#_g7(heH(2n*}JJN zWn_K)siHl6e@oGty`!7G6WbG$&nM1oO`O>t8h$!*3wIaRGn<*itwGdxXfyv&b8_a#%nzCM<*mLWEeZ8a zwuVtg+l8x9qyI}1P*SO=JmY64c{%R<4F{S?dx5ZY%~-<@ajl@*$X|jyb_ucs^;Z2I zUIo01!Fy1@VO?~LpiwoPfY8(G&G?ZB!LCsw->DOeKq3=C7aCplBB8$LYHA3ey-B|1 z)&FmW%Y+&LS1=s9mR8WD$U(|$hVt_N1!-B1C$5JvOJiK~%Av#g493?(!)c%7F^2Zz z2aXN3;{o(|vSR%8%vKE=p^gs1CQJEYX$1hs6dt-kiNR7e;`@e0*gn8e06om)K%jOh z3<$uAW!nfdiQ{0b3G@IM@_E1df{yZwc;2IlcRarg)9X8^dGy4uiN8=T`FSgvRFogQ z@7?oW)uK0EX*H`WB%Tla1~y!?3S;a8jak6-i;04TB( z0E&V1t5hm4-#JGQcCKC2QcNzd7_Q~TL65|XLKxVNUkLasLkgicQ{Exthe+@q9lJix xHzR929Hx94Kl2Pd21_2N&m(S0f*`y^BQMdZKjcwCUO)Ns>7P#jiGYoi{{f8a@jCzj literal 0 HcmV?d00001 diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/signals.cpython-313.pyc b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/signals.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de9a9559be0b7a8260458ca7833106da3708ea10 GIT binary patch literal 2281 zcmb_d&2Jk;6rb5If2$RC( zr){K&R6?RX6%G~j)C&jr56X#F9N++wtw_WgwSWW?;+DugapKK-H)#@A0*R44`#kSA zGjHDfes8+*coab!d3e=)TS4e|{wHX%Z*)Hf;||i0h8IvCQ#{UXVL`}?RP=0dLCVWi z&MQ>${!0s?e3(W&TV9CfRjTG=G&YVd;0bggrYTxzM$lqfxaU>1sP9#G zcn2!sO#Cm7&0rR$q(samLcxwzh^xD#*02nhWCRwX#Hm|Lgd2cxh>vl}HCMNcqh z2{Y1^KU{_wa%d8rj|VtS3}QH+*cy}iuhwth;&Z{Q?e8;Y_d#lMR?w6g;iU2ci()ak9eZGD;n}kYX%Q`&m2-F!Wz+&2EE%?5sv0(srcTSiEFc4L?87E;+-3@< zzE4ccHmWsp2}ZvA(lskh0Ncxl8&4tKJ|PnaP%MiC9c*Qi~}KgK~;1M#%;BFv*0a zGf~Qw<kSs&3ks>r84o5Xex~aC(~18-&(O$1&?Rlnlfq2ad-TR3jja z63IwR;m0DB4sh@w@AgCIaE^~dW5ryxUL!fOOpRQF64$(T{A6x#cJ$sEeTcpnkaNt6 zZCFmWah;8BiL?)pJ`Y<*cnm*5w~GFV4u1N=dh$p+d1Ng*x%y^DP2QfnHMg!FYpch; zI=6m&zI}ZDLFoJV!+7hxOD)T3smFd+-A-cgX5@*24y0PC6KnfldbICg>(E>$J$diq z-HYq#>2`X0Jw4M-&vZt{@1^gi*GG=FM~-gF;#+uN6Ny8KCt;KtYmH6+iz)MbMk=up zM#G0bU-@jMHFc()I@1{%+f2nG(G77Rrfi@AS?R)o5S!SW9DP@j+fk}SnBlnxg%v+xBk;<1yA7fXBXyC?2o#gP%&@XBd zSoLT(A4AbkR&K1chNss;)Az-1W8cJnP}W{4aA@LCYqYqQ&{ofO)P1*4-#Y!p$u)JX zC5_PuSUZCYnFLFeuRx)yXM?wj^8027{LvuNtW`I-Ouf^oz7x*u^b+BzdpH<&P0*Q3 z4cmc-mRAD)I?+*Y-Cnx0ep0i(Dk-npl;5c*KQ9qqBQHuXGb}t;tCyP=nWrZJ#9u?F z0NsX&F@B6DAEVr_XzDR~`A_ufrh(zkdM96AxPb_uCa>m+OxLcMsE4a9XvAPBKN5kZ6Jb&0>nS(T%*6X#{2nE zqg4;YXvF_NK((*c=vIS@p^h&DWBiJ;A1L#d&fbx48(C95a@Zqo gm*k#Fu1gBfr0_zDJva01n3nxz*wd-|G|VXW5917$>Hq)$ literal 0 HcmV?d00001 diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/views.cpython-313.pyc b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50aa19024ffefd3b83f5daa165590632847813ef GIT binary patch literal 12255 zcmcgSYiwKBd57<(_z)?P6ea75mSxkHD95i@itWU*WxXuf;T0V_vdhxsmBfg;RL&*+ zP!hm@R?vGO_Szse(9JI9t5VL{kQ=eD6F3_j+?NF!iEX+xCL7%Y@D!;+pvworV0Ca6K{K#<88QYJcxs|tZAZs zyaRVo*fFtTyc2hhGnj#IC+nK%8V}(Rz;3*eZN}ZFn?s28?1p)fgg4P|Z}q#!kD$J# z4Y94KJ5GCd>nnJh=X_!RugOPPBU@*)crlSnq*55Z%mSCorTIC5z>Az9B<45*>n0Ny zNYSBeBEtzuj=-2pr7@Sx0c^tDISvb?SjT72!;gIq2yqv3@l0a*BEdca-JD2s=fhgr zbZDMSE~I(6=@?FN_;^-G=h9g|tdrfz442^J0>?vnoKGxrvSl%m&d?IsQ3d0@1V#Q?OhYa!dmHSlb}NOr~?pFE0J@(FAn+i%Z{B zrwGddbU6l?vqCS&t!vA?!NG)zE~+CEs%}J~$g>$101D6L|q>ew|s)Lx-?RJd;XS7>sNb?o%lAGO$&(`XS&uSoKO} zgn5nQd8NZOTg33$t)i=o^U~BY5*q|a_Mtv4$}#zxeUjQ;Cny3cY{k3 z@u4PQVwxVl0Y6^>A$uBO4U4dr)nFZ~#d=nU4X2IR#Om7+HnRrEEvyl8D{F$>#+o6w z$IvbfYiUDom~j(@trT{!HmG&7cF0|96L!bYehqH!Mq!8C!ooJzA7O#Wm|t&!aV&3L z!dxzWVJVZJ10L*u@<{iS{fNxx7Be7tK3b-K zqO^Ez7FCy_t~gqexphT|434D4QS>~NR6V9Nrg_v-_fY|7Y--s|lM1mKRVUPyY1Ojb zm2tDW0S&7+K%?&G8J9!trS`jDqs}qKAjCWmHCK&_3c*Gjresqpk>ldItZbpcEY2M^2UaY99e_Z2SX8l2eM13Y;(T5@A5v$)*;$DB-=LlD|>wJ@4LEB@@x`4nJX0;?U+MXUT@t7CO6@c4i>>a3Ni(C!Mr zYi6ok_=QKVd5*Cgpns3rYwedcvL^K`0?(VLrokezWk4v$6-JS*LOz)Uqii;xk@dNY zOPp+5su)l4bc(R-EU0|2dyGp9@i-V*b}Gx%<_7vDw^j<%{JCrz3>;$m0nxA(lLeDa zS!(A9vT-&|Ob6Trr8q>Ytkb8C566e6rlY51+qpyr4qBXyPd29@PxHwfCelE5z{vqq zDULZ94wnjR@n%{~bsLw0k-=~Ufyf<(!9-Tbe!}T>_3FF?Ir8+A(FlJRP!ryT>=IfD zFgH4{cS?bNG0Zoq+P1GEeVg^M8QEGdZ@aR1 zySuQhXzl;L`FrNSvaVW?#rMdHTpg0LTXc3y&TXO-PFq(w&~c;vdb+8XSp= z%$02wc1vbcoNQJ1HJu8ZD6`-f36Z@dt1=qiM`)UrhG#G09{5xQvBE5F!Yp8u##L&9 zy!q)|vIcR8keY@J*u&YrW=Gd-r;cu?)@A)St6$yBvc0)%_m%Cf4{b)9^|2kKuAjncOk}Wa*EJwV! z+_HUvIyj1LFWc%|gJm}l9u)N+RBvHf$Mi8_o9w9ikQ{N9>G=dhh`_S9T36qv2~HAJ zVA;7&8_1fttX*1=_4L>p6H80PO3?{940?h}b4&H`;r*}{vMD)F)QKRQ)e6}H;eqs0 z8r)sEX-)SqAu=ck4xHsOvJo7{Twah3^iv?*$5fMY;(cfH=}Za~N`)S%3Z=8Y#^?Si z=#xNN_yuHOK{a~|rhoQ#6-LW`M)LQI{(i|na%b=N4}9-{v~xt%Pwg%hToBGD!_aAT@OX~O0@O+&<}tb9MI1HqOR|#)l70 z%g)2YM-Il0PFD+RD3il1dvIj9hEgebLRHy_s!H<^;7VlVuExw-hjIpnBTPYd34Q7d zzW?^swz_4{8=={)F!UeNXC9C(kR+fA zUxVy7U;w#WulhvymXdpG!MNh}U-K%aaw+ga$-ARqSqZdXo0d8T#g4&J$3CfJRO}f2 z&|927S?m}scAP2&UN1PxJzJ%oA+cxZ#{;FFGX-PO84}GKe>yNy>KXmJ4R?0j5^nGK zv-3aLAoh%^m1Sp3q3J&#=+Ndd4V}tQwzLcx^lNNm!f?c1eHeO@J%Jt0z_EqUwqZMp zX%1`lXm~BKNR>&UT_si7>={fOgD4f*;VHaC9`?Zxwc#uBC!y_CLfkstie_V zxzq>XXyPo8lU;;mA=pi$_7G!9P#%>Hv^nKm942`Bv)OD069FIAQ-;L?Db^)(7jOyjsa(CiGxd00t8Y_J44#iq~Z*I1M0p4oIzHOrbbqLh=kFq8dk-GR#l#> zh51y$2)u-%RXtg2^E6uhB#cqjrZxOmH7^?Btf6QFTbZ0c8Zc}ywrF#Wh6+W&XIP)2 zzV#(AFTYwA(?r`;xc19x$=O*m)8F<8+ST5kG6H&fYa^Jc&Z04XLuFT0N;N5v0&Zi# zS5!|rRe&TYxh|>v6YW}80iQ7)YZ%Z_A9LfAwQ5V1GgH3}?yGuTwH5jQ;y%{I8{ppv zzSyRx&(?!Rw4O;G z*9XvH&`iII0Qf2iwFrKr+v`3mAZunV4SaFRH8C@Itd^ObYAe_QvkKRK8=lKuwR#m& zIgqtRw7hkOglcGN%-_n1j9F($G>4`rv2fNU);6HyZT}}NdbYk0Ex0%O!gFjSd#bUD z+CW*mI`g{yqPlseN+G(hhOa_=)6B~%{3S>Aib|*UTQ?Ta#K1ehaeOOwPc zQK=*oN`UnPt7xpOqOlWX>f~P{G>i7@I?=2q+&ZCUK;6A?oEdae+rkBl1^a>sm z!0UwwP6A<^f=sqnJuG-|f(dKqiMc#1Fz561UcM3$TU?sGcMI-mG2FHbbKLoRx8^gs z2qr6v32Wd9$l%K1H2la`#ndGsFSsq#FOnFv@}h)ZAev=uE=$Ki%-YmNSx;|+jRFL_ z;N~Ml$dMJnuR}HsL6hdK=Nq~OWV$#P!BuGSW)-X|)iuYbAiRW=9>{dK; zJ;BBo35s30;v$z>DM4}7e;>;6ACQc23nuqD3KNdck0@%8~d*B`;NP~?X^<)?gk1{G6_W zzU*!;7|Z@Hy(kl&3i$l}W&?#}~RO!I$4|Uq0qcE{*M*glV z3*}&_?C*q|*p;^Kn+wHFuaw#juDL~(0ueC~DF?cwz*aG^wcOr)W8(V6XKo86;x#n2 zuLL$rfxeFeeRpO))Qf@1E5@>`^?mDA>&@vaR?)Q;#&}J0)$(=yX9qPX(Dxss8f0&- zY)VW!T%(ff znCLoIJn?!Fa@WiwgAUNm$mPGL2R0D>y(NF7@`hr$`!5K9eKGtg>UlRZWH$@3D+y(%mvl{rJi6t(2#e7i9$Zw;U zMvcMmMor2@ag4>h6Rm8 z#8TLR!5*u>1twDmr3de~)`!*cACanOL_F(+_um4lKrBmgg?RPxDh8($EEX!P zX{1-t&@@s^mFica9=}1dC6c{OvOgsmU5yq948uKII!T@#;q=*j4qj(Kf0eLt5{!v> z9M(DqE$C(XD@yQ~gjO)gwvYHIt;!CjNV}V+-@|qe(o|FJP%w4HHPhvk7)GlT zZA-lY@9nY?(2ld1*cGy)RzhZ=*d?w?Vd`vzm~YUUoDI2ceQ{(q4-Zg4sSETym#nrE z=Ce4L%;y9}x2wH^S{Eq8$t@L1bZC)WdXZbs@*K5{WdGVa^0*cD4m!ak>V}D(Bzr43 z%F`}*<;vl75|gN#+^oD6i=0geoH}~qZYs}Y0`;{5htHAg(+It}C1wqFk&GHHZ52#n zNZ@QHQezqc6Ja8^R1oI54Aco!<0o>|f}*yRY#?kUP!^+>8=fb`=SY?&8RY`P*UGLD zUXacE6sA6a@4+XLKtcvG5Mmn5eH8i?V(z1j_etJ=A8opiI%&S=J_`RE+Wm7B6;bp) zI`nh&(y!3wPd)ub { + const option = document.createElement("option"); + option.value = name; + option.textContent = name; + select.appendChild(option); + }); + + if (selectedName) { + select.value = selectedName; + } + }; + + let presets = readPresets(); + refreshOptions(presets); + + loadBtn.addEventListener("click", function () { + const name = select.value; + if (!name || !presets[name]) { + return; + } + const preset = presets[name]; + subjectInput.value = preset.subject || ""; + messageInput.value = preset.message || ""; + }); + + saveBtn.addEventListener("click", function () { + const name = nameInput.value.trim(); + if (!name) { + window.alert(i18n.nameRequired || "Bitte einen Namen eingeben."); + return; + } + if (presets[name] && !window.confirm(i18n.confirmOverwrite || "Bestehende Voreinstellung überschreiben?")) { + return; + } + presets[name] = { + subject: subjectInput.value, + message: messageInput.value, + }; + writePresets(presets); + refreshOptions(presets, name); + nameInput.value = ""; + }); +})(); diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/control_preset.html b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/control_preset.html new file mode 100644 index 0000000..b8874a7 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/control_preset.html @@ -0,0 +1,13 @@ +{% load i18n static %} + + diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/send.html b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/send.html new file mode 100644 index 0000000..8a715b9 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/templates/pretixplugins/attendance_confirm/send.html @@ -0,0 +1,35 @@ +{% extends "pretixcontrol/event/base.html" %} +{% load i18n %} + +{% block title %}{% trans "Teilnahmebestätigungen" %}{% endblock %} + +{% block content %} +

{% trans "Teilnahmebestätigungen" %}

+ + {% if not event_ended %} +
+ {% trans "Diese Veranstaltung ist noch nicht vorbei. Der Versand ist erst nach dem Ende möglich." %} +
+ {% endif %} + +

+ {% blocktrans count counter=checked_in_count %} + 1 eingecheckte Person ist vorausgewählt. + {% plural %} + {{ counter }} eingecheckte Personen sind vorausgewählt. + {% endblocktrans %} + {% blocktrans count counter=total_count %} + Insgesamt gibt es 1 Person in der Teilnehmerliste. + {% plural %} + Insgesamt gibt es {{ counter }} Personen in der Teilnehmerliste. + {% endblocktrans %} +

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/urls.py b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/urls.py new file mode 100644 index 0000000..e7b4874 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/urls.py @@ -0,0 +1,13 @@ +from django.urls import re_path + +from . import views + +app_name = "attendance_confirm" + +urlpatterns = [ + re_path( + r'^control/event/(?P[^/]+)/(?P[^/]+)/attendance-confirm/send/$', + views.SendConfirmationsView.as_view(), + name='send' + ), +] diff --git a/plugins/attendance_confirm_plugin/pretix_attendance_confirm/views.py b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/views.py new file mode 100644 index 0000000..3b53af1 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pretix_attendance_confirm/views.py @@ -0,0 +1,186 @@ +from email.utils import formataddr + +from django.conf import settings +from django.contrib import messages +from django.db.models import Max +from django.db.models.functions import Coalesce +from django.shortcuts import redirect +from django.urls import reverse +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ +from django.views.generic import FormView + +from pretix.base.models import Checkin, OrderPosition +from pretix.base.services.mail import clean_sender_name, mail_send, mail_send_task +from pretix.control.permissions import EventPermissionRequiredMixin +from pretix.helpers.format import SafeFormatter, format_map + +from .forms import ConfirmationMailForm + +SETTINGS_SUBJECT = "attendance_confirm_subject" +SETTINGS_MESSAGE = "attendance_confirm_message" + +DEFAULT_SUBJECT = _("Ihre Teilnahmebestätigung für {event_name}") +DEFAULT_MESSAGE = _( + "Hallo {attendee_name},\n\n" + "danke, dass du bei {event_name} dabei warst.\n\n" + "Du kannst folgende Platzhalter verwenden:\n" + "- {attendee_name}: Name der teilnehmenden Person (falls vorhanden)\n" + "- {event_name}: Name der Veranstaltung\n\n" + "Voreinstellungen: Speichere Betreff und Text als Voreinstellung. " + "Mit „Laden“ kannst du eine gespeicherte Voreinstellung jederzeit ohne Seitenwechsel anwenden. " + "Beim Laden werden Betreff und Text im Formular ersetzt.\n\n" + "Versand: Beim Klick auf „Teilnahmebestätigungen senden“ geht eine E-Mail pro eingecheckter " + "Person raus – zuerst an die Teilnehmer-E-Mail, sonst an die Bestell-E-Mail. " + "Wenn eine Veranstaltung noch läuft, ist der Versand deaktiviert.\n\n" + "Viele Grüße" +) + + +class SendConfirmationsView(EventPermissionRequiredMixin, FormView): + template_name = "pretixplugins/attendance_confirm/send.html" + permission = "can_change_orders" + form_class = ConfirmationMailForm + + def get_event_end(self): + if self.request.event.has_subevents: + return self.request.event.subevents.aggregate( + last_end=Coalesce(Max("date_to"), Max("date_from")) + )["last_end"] + return self.request.event.date_to or self.request.event.date_from + + def get_checked_in_positions(self): + checked_in_ids = Checkin.objects.filter( + list__event=self.request.event, + successful=True, + type=Checkin.TYPE_ENTRY, + ).values_list("position_id", flat=True).distinct() + return OrderPosition.objects.filter( + pk__in=checked_in_ids, + order__event=self.request.event, + ).select_related( + "order", + "order__invoice_address", + ) + + def get_all_positions(self): + return OrderPosition.objects.filter( + order__event=self.request.event, + ).select_related( + "order", + "order__invoice_address", + "item", + "variation", + "subevent", + ).order_by("order__code", "positionid") + + def build_recipient_choices(self, positions, checked_in_ids): + choices = [] + for position in positions: + recipient = position.attendee_email or position.order.email + attendee_name = ( + position.attendee_name_cached + or getattr(position.order.invoice_address, "name_cached", "") + or recipient + or _("Unbekannt") + ) + email_label = recipient or _("keine E-Mail") + status = _("eingecheckt") if position.pk in checked_in_ids else _("nicht eingecheckt") + label = f"{attendee_name} - {email_label} - {position.order.code} - {status}" + choices.append((str(position.pk), label)) + return choices + + def get_initial(self): + return { + "subject": self.request.event.settings.get(SETTINGS_SUBJECT, DEFAULT_SUBJECT), + "message": self.request.event.settings.get(SETTINGS_MESSAGE, DEFAULT_MESSAGE), + } + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + positions = list(self.get_all_positions()) + checked_in_ids = set(self.get_checked_in_positions().values_list("pk", flat=True)) + kwargs["recipient_choices"] = self.build_recipient_choices(positions, checked_in_ids) + kwargs["recipient_initial"] = [str(pk) for pk in checked_in_ids] + return kwargs + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx["checked_in_count"] = self.get_checked_in_positions().count() + ctx["total_count"] = self.get_all_positions().count() + ctx["event_end"] = self.get_event_end() + ctx["event_ended"] = bool(ctx["event_end"] and ctx["event_end"] <= now()) + return ctx + + def form_valid(self, form): + event_end = self.get_event_end() + if not event_end or event_end > now(): + messages.error(self.request, _("Diese Veranstaltung ist noch nicht vorbei.")) + return self.get(self.request, *self.args, **self.kwargs) + + self.request.event.settings.set(SETTINGS_SUBJECT, form.cleaned_data["subject"]) + self.request.event.settings.set(SETTINGS_MESSAGE, form.cleaned_data["message"]) + + selected_ids = set(form.cleaned_data.get("recipients") or []) + positions = self.get_all_positions().filter(pk__in=selected_ids) + if not positions.exists(): + messages.error(self.request, _("Es wurden keine Empfänger ausgewählt.")) + return self.get(self.request, *self.args, **self.kwargs) + + sent = 0 + failed = 0 + for position in positions.iterator(): + recipient = position.attendee_email or position.order.email + if not recipient: + continue + attendee_name = ( + position.attendee_name_cached + or getattr(position.order.invoice_address, "name_cached", "") + or recipient + ) + context = { + "attendee_name": attendee_name, + "event_name": str(self.request.event.name), + } + subject = format_map(form.cleaned_data["subject"], context, mode=SafeFormatter.MODE_RICH_TO_PLAIN) + message = format_map(form.cleaned_data["message"], context, mode=SafeFormatter.MODE_RICH_TO_PLAIN) + sender_email = self.request.event.settings.get("mail_from") or settings.MAIL_FROM + sender_name = clean_sender_name( + self.request.event.settings.mail_from_name or str(self.request.event.name) + ) + mail_kwargs = { + "to": [recipient], + "subject": subject, + "body": message, + "html": None, + "sender": formataddr((sender_name, sender_email)), + "event": self.request.event.id, + "order": position.order_id, + "position": position.id, + } + try: + result = mail_send_task.apply(kwargs=mail_kwargs, throw=True) + result.get(propagate=True) + sent += 1 + except Exception: + failed += 1 + + if failed: + messages.error( + self.request, + _("Es wurden {sent} Bestätigungen gesendet, aber {failed} konnten nicht zugestellt werden.").format( + sent=sent, failed=failed + ), + ) + else: + messages.success( + self.request, + _("Es wurden {count} Teilnahmebestätigungen gesendet.").format(count=sent), + ) + return redirect(self.get_success_url()) + + def get_success_url(self): + return reverse("plugins:attendance_confirm:send", kwargs={ + "organizer": self.request.event.organizer.slug, + "event": self.request.event.slug, + }) diff --git a/plugins/attendance_confirm_plugin/pyproject.toml b/plugins/attendance_confirm_plugin/pyproject.toml new file mode 100644 index 0000000..4a85092 --- /dev/null +++ b/plugins/attendance_confirm_plugin/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/plugins/attendance_confirm_plugin/setup.cfg b/plugins/attendance_confirm_plugin/setup.cfg new file mode 100644 index 0000000..157dcbf --- /dev/null +++ b/plugins/attendance_confirm_plugin/setup.cfg @@ -0,0 +1,38 @@ +[metadata] +name = pretix-attendance-confirm +version = 0.1.0 +description = Attendance confirmation email plugin for pretix +long_description = file: README.md +long_description_content_type = text/markdown +author = Ez for mITs +license = AGPL-3.0-or-later +license_files = LICENSE +classifiers = + Development Status :: 3 - Alpha + Environment :: Plugins + Framework :: Django + License :: OSI Approved :: GNU Affero General Public License v3 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Topic :: Office/Business + +[options] +packages = find: +include_package_data = True +install_requires = + pretix>=2024.3 +python_requires = >=3.10 + +[options.packages.find] +exclude = + tests + tests.* + +[options.entry_points] +pretix.plugin = + attendance_confirm = pretix_attendance_confirm + +[options.package_data] +pretix_attendance_confirm = + templates/**/* + static/**/* \ No newline at end of file diff --git a/plugins/selective_export_plugin/MANIFEST.in b/plugins/selective_export_plugin/MANIFEST.in new file mode 100644 index 0000000..fb93042 --- /dev/null +++ b/plugins/selective_export_plugin/MANIFEST.in @@ -0,0 +1,4 @@ +recursive-include +pretix_selective_export/templates * +recursive-include +pretix_selective_export/static * \ No newline at end of file diff --git a/plugins/selective_export_plugin/README.md b/plugins/selective_export_plugin/README.md new file mode 100644 index 0000000..b8d3c05 --- /dev/null +++ b/plugins/selective_export_plugin/README.md @@ -0,0 +1,48 @@ +# pretix-selective-export (draft) + +pretix exporter plugin that lets you pick exactly which fields to export. +It supports event-level exports as well as organizer-level exports that aggregate +all events into one file. + +Notes: +- Activate the plugin at the organizer level to use multi-event exports. +- Activate it for an event to use event-level exports. +- Field selection includes model fields, related labels, invoice fields (when + invoice addresses exist), and question answers (when answers exist). +- Presets: save/load field selections directly on the export form without reloading. + Presets are stored in the browser (localStorage) per URL/event. + +## Setup (pretix + plugin) + +These steps assume a local pretix checkout and Python 3.10+. + +1) Create and activate a virtualenv: +``` +python3 -m venv .venv +source .venv/bin/activate +``` + +2) Install pretix (editable) and its dev requirements: +``` +git clone https://github.com/pretix/pretix.git +cd pretix +pip install -U pip +pip install -e ".[dev]" +``` + +3) Install this plugin (editable): +``` +pip install -e /path/to/selective_export_plugin +``` + +4) Enable the plugin in pretix: +- Add `pretix_selective_export` to the `PLUGINS` list in your pretix config. + +5) Run pretix and use the exporter: +- Start pretix using the standard dev-server command for your checkout. Common options are: +``` +python src/manage.py runserver +``` +- If that doesn't work in your setup, follow the upstream pretix development setup instructions and use their start command. +- For event-level exports: enable the plugin on the event and go to the export page. +- For organizer-level exports: enable the plugin on the organizer and use the organizer export page (single aggregated file). diff --git a/plugins/selective_export_plugin/pretix_selective_export.egg-info/PKG-INFO b/plugins/selective_export_plugin/pretix_selective_export.egg-info/PKG-INFO new file mode 100644 index 0000000..ee1c9dc --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export.egg-info/PKG-INFO @@ -0,0 +1,63 @@ +Metadata-Version: 2.4 +Name: pretix-selective-export +Version: 0.1.0 +Summary: Selective field export plugin for pretix +Author: Ez for mITs +License: AGPL-3.0-or-later +Classifier: Development Status :: 3 - Alpha +Classifier: Environment :: Plugins +Classifier: Framework :: Django +Classifier: License :: OSI Approved :: GNU Affero General Public License v3 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Office/Business +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +Requires-Dist: pretix>=2024.3 + +# pretix-selective-export (draft) + +pretix exporter plugin that lets you pick exactly which fields to export. +It supports event-level exports as well as organizer-level exports that aggregate +all events into one file. + +Notes: +- Activate the plugin at the organizer level to use multi-event exports. +- Activate it for an event to use event-level exports. +- Field selection includes model fields, related labels, invoice fields (when + invoice addresses exist), and question answers (when answers exist). + +## Setup (pretix + plugin) + +These steps assume a local pretix checkout and Python 3.10+. + +1) Create and activate a virtualenv: +``` +python3 -m venv .venv +source .venv/bin/activate +``` + +2) Install pretix (editable) and its dev requirements: +``` +git clone https://github.com/pretix/pretix.git +cd pretix +pip install -U pip +pip install -e ".[dev]" +``` + +3) Install this plugin (editable): +``` +pip install -e /path/to/selective_export_plugin +``` + +4) Enable the plugin in pretix: +- Add `pretix_selective_export` to the `PLUGINS` list in your pretix config. + +5) Run pretix and use the exporter: +- Start pretix using the standard dev-server command for your checkout. Common options are: +``` +python src/manage.py runserver +``` +- If that doesn't work in your setup, follow the upstream pretix development setup instructions and use their start command. +- For event-level exports: enable the plugin on the event and go to the export page. +- For organizer-level exports: enable the plugin on the organizer and use the organizer export page (single aggregated file). diff --git a/plugins/selective_export_plugin/pretix_selective_export.egg-info/SOURCES.txt b/plugins/selective_export_plugin/pretix_selective_export.egg-info/SOURCES.txt new file mode 100644 index 0000000..6d08c8f --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export.egg-info/SOURCES.txt @@ -0,0 +1,13 @@ +README.md +pyproject.toml +setup.cfg +pretix_selective_export/__init__.py +pretix_selective_export/apps.py +pretix_selective_export/exporter.py +pretix_selective_export/signals.py +pretix_selective_export.egg-info/PKG-INFO +pretix_selective_export.egg-info/SOURCES.txt +pretix_selective_export.egg-info/dependency_links.txt +pretix_selective_export.egg-info/entry_points.txt +pretix_selective_export.egg-info/requires.txt +pretix_selective_export.egg-info/top_level.txt \ No newline at end of file diff --git a/plugins/selective_export_plugin/pretix_selective_export.egg-info/dependency_links.txt b/plugins/selective_export_plugin/pretix_selective_export.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/plugins/selective_export_plugin/pretix_selective_export.egg-info/entry_points.txt b/plugins/selective_export_plugin/pretix_selective_export.egg-info/entry_points.txt new file mode 100644 index 0000000..971b755 --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[pretix.plugin] +selective_export = pretix_selective_export diff --git a/plugins/selective_export_plugin/pretix_selective_export.egg-info/requires.txt b/plugins/selective_export_plugin/pretix_selective_export.egg-info/requires.txt new file mode 100644 index 0000000..213e748 --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export.egg-info/requires.txt @@ -0,0 +1 @@ +pretix>=2024.3 diff --git a/plugins/selective_export_plugin/pretix_selective_export.egg-info/top_level.txt b/plugins/selective_export_plugin/pretix_selective_export.egg-info/top_level.txt new file mode 100644 index 0000000..64d650c --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export.egg-info/top_level.txt @@ -0,0 +1 @@ +pretix_selective_export diff --git a/plugins/selective_export_plugin/pretix_selective_export/__init__.py b/plugins/selective_export_plugin/pretix_selective_export/__init__.py new file mode 100644 index 0000000..f351ef2 --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export/__init__.py @@ -0,0 +1 @@ +default_app_config = "pretix_selective_export.apps.PretixSelectiveExportApp" diff --git a/plugins/selective_export_plugin/pretix_selective_export/__pycache__/__init__.cpython-313.pyc b/plugins/selective_export_plugin/pretix_selective_export/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..674c1e18ccd1115f0567a6b0bf378e3fba457da0 GIT binary patch literal 234 zcmey&%ge<81RdKwGJS#cV-N=h7@>^M96-iYhG2#whIB?vrYh5dqSTViiumHxoYdr! z%(B$@)QW=qq7uEtf`Veb0I*~*OwJW7Bq-s=4F<|$LkeT-r}&y%}*)KNwq6t1DXSJ Yb1^rN_`uA_$aq84=OTkp5etwH0K=I^cK`qY literal 0 HcmV?d00001 diff --git a/plugins/selective_export_plugin/pretix_selective_export/__pycache__/apps.cpython-313.pyc b/plugins/selective_export_plugin/pretix_selective_export/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60bcc7d0f0cf1c8dd6921fbc2d2e648b276bdb37 GIT binary patch literal 1379 zcmZ`(&2Jk;6rZ*C%U}6&)0#lTrY)itViS_4;y@#%ttn0%H_8SO$kJ$SkDW#9UGruf zVxMxVEOFsd3GpBB-$csA8L3LgLh!UDqWbM)GgpoA>7F_r5&N<a$+~JlLR%Ale^lE4!VU3=oWI+CFE!|WiuJS z2cxYQjZ3K%6`tDeb{~a7%WDfI+a`>W6NY_v&=>mc{mSQ0%2iw`eNn1l0o6nN^x(;M zwfuGI0Dtz?!-MkUMjSOpb)t+P3gJ1bAy6Cy2}gmXlK{1*2|CG5CH4aS4ZTf~tUO>i z`Vs=uF(B18iz!~%2g1A)hxnwyykk;2>4v}r=}SYn^BdjBjOlATCXvJ6bJG`Ad~=28 zq!eg;D1uz&SyKha#FV2J>HpyDs9;2Q+SJE%bVv!t=~@P|jtRMTs+i`*|6^{)hJC-+ z_JTda+{=v2TsD4?DGjVv2v(u&7xmKLDTla|& znH9FImPh<1wYmQ3(}TV3!@*2kpREV$YwP#yjh|7qIKxeh1Ghsk=4p&OVYBB;Ig9bP zJ=dRDW-)GgK$-6aBnX9B!CaL#uDd;U6aqJn2~aN#c&15c1H3MiirH`(X@}6~xklLO zy3DJ4zQ_6?r!H4S; z;Xkcjb-P_^cl&(q-*ttoh$U-F?+bW_E@tP>=5L?R-WjSF^9w&bIGZ66XhQ`J9 z8)t>e`SrbFW|V(>XpRbZhq=+*#`hZB6i?w@0gcJn3K1bWu5yURa{|OJDq{b{E(VH) z+NhYUUP@=NK?e2Of$P&^f+M&k?-E=UZEHmem0>Ikmc@r_K-^}ZFUMI-$9FpLc4WUM zT&b+(Df-2{8MBjhs}|EBxxl=H1qpcxixT7`o)!J7hm=IUk1~Syr12dIGDI9qasjzp z5lDfq2zZ7@nLDSCFY?#U7T-V5-#xATr5f73KahZz3yQWJzn1>hqA6zL8#ic&w(Jea zLh-`nrpG*=+6>%)`eFpa0OX%WOfet#+OE5l*xhJ9Xf!x6p{pdh`AIyxTkwI1APY{H f1pJ;<6y*h4d4cZ#h93Q?&nU}3_Fp3Li737TLQrNJ literal 0 HcmV?d00001 diff --git a/plugins/selective_export_plugin/pretix_selective_export/__pycache__/exporter.cpython-313.pyc b/plugins/selective_export_plugin/pretix_selective_export/__pycache__/exporter.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3707a2f8704a8ba6ef230aa33581deb0d6bddad6 GIT binary patch literal 17966 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 literal 0 HcmV?d00001 diff --git a/plugins/selective_export_plugin/pretix_selective_export/__pycache__/signals.cpython-313.pyc b/plugins/selective_export_plugin/pretix_selective_export/__pycache__/signals.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6d8fc9052fe1d93cc7fb504774463ca227fa17e GIT binary patch literal 4018 zcmcgvO>7&-6`tMY|5Bnzi9eD|nfj@1S`sPQjtbeb6gY`vP_de7E99UUo3*&6=u)II zyRsa?K$TwVoDv-ZH>VUiMYrTo1U(c;PrdF!K}am@CP3RmZX#4Pg-?BNxJybDQ#3%) z0rdEG-kX^>Gw=J}=%}kJK%k}mxLy2%NXTFD!)b0?Ir=9k9}H+MNs0P_BpeK%ZCgErxRu z8cC5ixe@ZFDEXzpEH8-z)YijXtVDpp=U{D*cXwfcH5oCaEQLO^1+g7S&Sz6?;J|p*j8X zJv1~!!JzX+ZBxU7^UH08 z7Br2A?o`TJ%C1*7)U3KgmFy-}_2R?HOW9T!jkAz9OIw9v*>;fI%~`EjC@Uo`v-yq{ zYaZE&v!n@FE(Cu^J`lU)Z{o1w8a@J@Sfj1^PVQQ6o%(;fVcH8bvMtj-(Cl5(IIc6? z46QZq$=jq!@WXMmAH@KU8fZmlA4c4pRC04r}KM7avFtXDc%a}!N z?FUA8@RVE`UUCC%cvgFyNtPpy*81%xm-$KfEW1{tPOs#3PHM$EMzTT2spMgQB_(zz4)>0C*sux3^SH-x*x1lyzDu zU1ju;X*gsWi3LpLZ&HQjW*++-#Z{jN7m9fxpCSN+UVq*rIXsbS4BhGLH1G4?4y#zc zd}X^_swnw=x8*J=ch!<5Y|)bC*;FVjVR;nBOqQz%bEbn7OO53R>RFeI<$D#&wWR@# z^sOsegX82)m2MO@tyn43QShZ>C_FgIr2%hHguNAL#cy%4<%1RFva+G>yD45Z9YKLx zr4!8E#u~V37(fbJ1I7bQ{GB2qIt#7{T@6bEuuLHQ!TD-r)QpTC1jcq>s)>>J=iZyE ziW8({RG*pQ%O9Jh`)Xry89w zqZ8HWv>BbQg%eMbd&z2e)C`Z--NK8Ur%r^3_s~OviLVlwYGT$*%o_2lhW}dKD@5jY zbN>jESlo!G526=qy~(Gsq%m}*mK^)|&PR8u$tz~^N;NraCTDB?sgIK%C9D18X8$;aW99R{?_jlaXg^bd6q1a<|&>YokM|%X!#n{t(0=(*vEDuyGD6eTxrvhS!;`}3^ z;U!=Rv5N>@fNv&V0!8~Zh+XonxBJ~+)gp<<-oJAx7iu0h>-(q5OTNT&QH6S3kw{wPJR!v#$)+ zag|bbAfj!=n%Ou-+_jZ(y7eMF(RHenHTd5Mt2Mqb8E;s1Dg`M_(<5Arx input.value); + }; + + const setCheckedValues = function (name, values) { + const inputs = form.querySelectorAll(`input[name="${name}"]`); + if (!inputs.length) { + return; + } + const allowed = new Set(values || []); + inputs.forEach((input) => { + input.checked = allowed.has(input.value); + }); + }; + + const buildPresetUI = function () { + const wrapper = document.createElement("div"); + wrapper.className = "form-group"; + + const label = document.createElement("label"); + label.className = "col-md-3 control-label"; + label.textContent = i18n.title || "Presets"; + + const controls = document.createElement("div"); + controls.className = "col-md-9"; + + const loadGroup = document.createElement("div"); + loadGroup.className = "input-group"; + + const select = document.createElement("select"); + select.className = "form-control"; + + const loadBtnWrap = document.createElement("span"); + loadBtnWrap.className = "input-group-btn"; + + const loadBtn = document.createElement("button"); + loadBtn.type = "button"; + loadBtn.className = "btn btn-default"; + loadBtn.textContent = i18n.load || "Load"; + + loadBtnWrap.appendChild(loadBtn); + loadGroup.appendChild(select); + loadGroup.appendChild(loadBtnWrap); + + const saveGroup = document.createElement("div"); + saveGroup.className = "input-group"; + saveGroup.style.marginTop = "6px"; + + const nameInput = document.createElement("input"); + nameInput.type = "text"; + nameInput.className = "form-control"; + nameInput.placeholder = i18n.namePlaceholder || "Preset name"; + + const saveBtnWrap = document.createElement("span"); + saveBtnWrap.className = "input-group-btn"; + + const saveBtn = document.createElement("button"); + saveBtn.type = "button"; + saveBtn.className = "btn btn-default"; + saveBtn.textContent = i18n.save || "Save preset"; + + saveBtnWrap.appendChild(saveBtn); + saveGroup.appendChild(nameInput); + saveGroup.appendChild(saveBtnWrap); + + controls.appendChild(loadGroup); + controls.appendChild(saveGroup); + + wrapper.appendChild(label); + wrapper.appendChild(controls); + + return { wrapper, select, loadBtn, nameInput, saveBtn }; + }; + + const { wrapper, select, loadBtn, nameInput, saveBtn } = buildPresetUI(); + + const legend = fieldset.querySelector("legend"); + if (legend) { + legend.insertAdjacentElement("afterend", wrapper); + } else { + fieldset.prepend(wrapper); + } + + const refreshOptions = function (presets, selectedName) { + select.innerHTML = ""; + + const placeholder = document.createElement("option"); + placeholder.value = ""; + placeholder.textContent = i18n.selectPlaceholder || "Select a preset"; + select.appendChild(placeholder); + + Object.keys(presets) + .sort() + .forEach((name) => { + const option = document.createElement("option"); + option.value = name; + option.textContent = name; + select.appendChild(option); + }); + + if (selectedName) { + select.value = selectedName; + } + }; + + let presets = readPresets(); + refreshOptions(presets); + + const formPrefix = exporterInput.value; + const fieldsName = `${formPrefix}-fields`; + const eventsName = `${formPrefix}-events`; + + loadBtn.addEventListener("click", function () { + const name = select.value; + if (!name || !presets[name]) { + return; + } + const preset = presets[name]; + setCheckedValues(fieldsName, preset.fields || []); + setCheckedValues(eventsName, preset.events || []); + }); + + saveBtn.addEventListener("click", function () { + const name = nameInput.value.trim(); + if (!name) { + window.alert(i18n.nameRequired || "Please enter a preset name."); + return; + } + if (presets[name] && !window.confirm(i18n.confirmOverwrite || "Overwrite existing preset?")) { + return; + } + presets[name] = { + fields: getCheckedValues(fieldsName), + events: getCheckedValues(eventsName), + }; + writePresets(presets); + refreshOptions(presets, name); + nameInput.value = ""; + }); +})(); diff --git a/plugins/selective_export_plugin/pretix_selective_export/templates/pretixplugins/selective_export/control_preset.html b/plugins/selective_export_plugin/pretix_selective_export/templates/pretixplugins/selective_export/control_preset.html new file mode 100644 index 0000000..1ac7df6 --- /dev/null +++ b/plugins/selective_export_plugin/pretix_selective_export/templates/pretixplugins/selective_export/control_preset.html @@ -0,0 +1,13 @@ +{% load i18n static %} + + diff --git a/plugins/selective_export_plugin/pyproject.toml b/plugins/selective_export_plugin/pyproject.toml new file mode 100644 index 0000000..4a85092 --- /dev/null +++ b/plugins/selective_export_plugin/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/plugins/selective_export_plugin/setup.cfg b/plugins/selective_export_plugin/setup.cfg new file mode 100644 index 0000000..5d44e9e --- /dev/null +++ b/plugins/selective_export_plugin/setup.cfg @@ -0,0 +1,38 @@ +[metadata] +name = pretix-selective-export +version = 0.1.0 +description = Selective field export plugin for pretix +long_description = file: README.md +long_description_content_type = text/markdown +author = Ez for mITs +license = AGPL-3.0-or-later +license_files = LICENSE +classifiers = + Development Status :: 3 - Alpha + Environment :: Plugins + Framework :: Django + License :: OSI Approved :: GNU Affero General Public License v3 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Topic :: Office/Business + +[options] +packages = find: +include_package_data = True +install_requires = + pretix>=2024.3 +python_requires = >=3.10 + +[options.packages.find] +exclude = + tests + tests.* + +[options.entry_points] +pretix.plugin = + selective_export = pretix_selective_export + +[options.package_data] +pretix_selective_export = + templates/**/* + static/**/* diff --git a/release/.git-keep-me b/release/.git-keep-me deleted file mode 100644 index e69de29..0000000