pretix-map 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pretix_map-0.1.5.dist-info/METADATA +88 -0
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.5.dist-info}/RECORD +32 -30
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.5.dist-info}/WHEEL +1 -1
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.5.dist-info}/licenses/LICENSE +15 -15
- pretix_mapplugin/__init__.py +1 -1
- pretix_mapplugin/apps.py +28 -28
- pretix_mapplugin/geocoding.py +162 -102
- pretix_mapplugin/locale/de/LC_MESSAGES/django.po +12 -12
- pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.po +12 -12
- pretix_mapplugin/management/commands/geocode_existing_orders.py +271 -271
- pretix_mapplugin/migrations/0001_initial.py +27 -27
- pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py +32 -32
- pretix_mapplugin/migrations/0003_mapmilestone.py +27 -0
- pretix_mapplugin/models.py +71 -47
- pretix_mapplugin/signals.py +77 -92
- pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css +51 -51
- pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js +342 -442
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css +59 -59
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css +14 -14
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js +10 -10
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.esm.js +14419 -14419
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.js +14512 -14512
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.css +661 -661
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.js +5 -5
- pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js +2 -2
- pretix_mapplugin/tasks.py +144 -113
- pretix_mapplugin/templates/pretix_mapplugin/map_page.html +154 -68
- pretix_mapplugin/templates/pretix_mapplugin/milestones.html +53 -0
- pretix_mapplugin/urls.py +38 -21
- pretix_mapplugin/views.py +272 -163
- pretix_map-0.1.3.dist-info/METADATA +0 -195
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.5.dist-info}/entry_points.txt +0 -0
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pretix-map
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: An overview map of the catchment area of previous orders. Measured by postcode
|
|
5
|
+
Author-email: MarkenJaden <jjsch1410@gmail.com>
|
|
6
|
+
Maintainer-email: MarkenJaden <jjsch1410@gmail.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Project-URL: homepage, https://github.com/MarkenJaden/pretix-map
|
|
9
|
+
Project-URL: repository, https://github.com/MarkenJaden/pretix-map
|
|
10
|
+
Keywords: pretix
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: geopy
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Pretix Sales Map Plugin
|
|
17
|
+
|
|
18
|
+
A powerful geographic analytics and visualization plugin for [pretix](https://github.com/pretix/pretix). This plugin allows you to visualize where your attendees are coming from, track sales growth over time, and analyze regional market penetration.
|
|
19
|
+
|
|
20
|
+
## ✨ Features
|
|
21
|
+
|
|
22
|
+
- **📍 Automatic Geocoding:** Automatically converts attendee addresses to map coordinates upon payment.
|
|
23
|
+
- **🗺️ Interactive Map:** Switch between **Pin View** (with clustering), **Heatmap**, and **Density Grid**.
|
|
24
|
+
- **📈 Sales Analytics:** Track total revenue, average travel distance, and top regions (cities/items).
|
|
25
|
+
- **⏱️ Timeline Animation:** Play back the history of your sales to see geographic growth over time.
|
|
26
|
+
- **★ Marketing Milestones:** Define important dates (e.g., newsletters) in the UI to see their impact on the timeline.
|
|
27
|
+
- **🔄 Event Comparison:** Overlay data from previous events to compare reach and performance.
|
|
28
|
+
- **⚠️ Quality Control:** Dedicated view for orders with failed geocoding to manage data issues.
|
|
29
|
+
- **🌙 Nightly Sync:** Automated background task to retry failed geocoding attempts.
|
|
30
|
+
- **🔘 Manual Trigger:** Admin button to re-run geocoding for all orders of an event.
|
|
31
|
+
|
|
32
|
+
## 🚀 Installation & Setup
|
|
33
|
+
|
|
34
|
+
To use this plugin with your local pretix instance (`C:\Users\SCJA03\Desktop\Programmieren\pretix`):
|
|
35
|
+
|
|
36
|
+
### 1. Register the Plugin
|
|
37
|
+
Open your terminal, navigate to this directory, and ensure your pretix virtual environment is active.
|
|
38
|
+
```bash
|
|
39
|
+
python setup.py develop
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Run Migrations
|
|
43
|
+
Apply the database changes for geodata and milestones:
|
|
44
|
+
```bash
|
|
45
|
+
# From your pretix/src directory
|
|
46
|
+
python manage.py migrate
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Configuration
|
|
50
|
+
Add a User-Agent for the geocoding service in your `pretix.cfg`:
|
|
51
|
+
```ini
|
|
52
|
+
[pretix_mapplugin]
|
|
53
|
+
nominatim_user_agent = YourProjectName/1.0 (contact@yourdomain.com)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 4. Enable the Plugin
|
|
57
|
+
1. Log in to your Pretix Control Panel.
|
|
58
|
+
2. Go to **Organizer Settings > Plugins** and enable **Map-Plugin**.
|
|
59
|
+
3. In your specific **Event > Settings > Plugins**, also enable **Map-Plugin**.
|
|
60
|
+
|
|
61
|
+
## 🛠️ Usage
|
|
62
|
+
|
|
63
|
+
- **Map View:** Navigate to **Sales Map > Map View** in the event sidebar.
|
|
64
|
+
- **Milestones:** Go to **Sales Map > Milestones** to add marketing dates.
|
|
65
|
+
- **Revenue Weighting:** Use the toggle button on the map to see "where the money comes from" instead of just "where the people are".
|
|
66
|
+
|
|
67
|
+
## 🚀 Starting the Application (Development)
|
|
68
|
+
|
|
69
|
+
1. **Pretix Server:**
|
|
70
|
+
```bash
|
|
71
|
+
cd C:\Users\SCJA03\Desktop\Programmieren\pretix\src
|
|
72
|
+
python manage.py runserver
|
|
73
|
+
```
|
|
74
|
+
2. **Celery Worker:**
|
|
75
|
+
```bash
|
|
76
|
+
cd C:\Users\SCJA03\Desktop\Programmieren\pretix\src
|
|
77
|
+
celery -A pretix.celery_app worker -l info
|
|
78
|
+
```
|
|
79
|
+
*Note: If you don't have a broker like Redis or RabbitMQ running, the plugin will automatically fall back to synchronous geocoding (eager mode) if configured in `pretix.cfg`.*
|
|
80
|
+
|
|
81
|
+
## 🛡️ Requirements
|
|
82
|
+
|
|
83
|
+
- pretix >= 2.7
|
|
84
|
+
- geopy
|
|
85
|
+
- A running Celery worker (essential for background geocoding)
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
Developed by MarkenJaden. Released under the Apache License 2.0.
|
|
@@ -1,37 +1,38 @@
|
|
|
1
|
-
pretix_map-0.1.
|
|
2
|
-
pretix_mapplugin/__init__.py,sha256=
|
|
3
|
-
pretix_mapplugin/apps.py,sha256=
|
|
4
|
-
pretix_mapplugin/geocoding.py,sha256=
|
|
5
|
-
pretix_mapplugin/models.py,sha256=
|
|
6
|
-
pretix_mapplugin/signals.py,sha256=
|
|
7
|
-
pretix_mapplugin/tasks.py,sha256=
|
|
8
|
-
pretix_mapplugin/urls.py,sha256=
|
|
9
|
-
pretix_mapplugin/views.py,sha256=
|
|
1
|
+
pretix_map-0.1.5.dist-info/licenses/LICENSE,sha256=MNMHjIJIjeQzQboYGsgFSRTBUHctF603DDa8MgCNyAg,554
|
|
2
|
+
pretix_mapplugin/__init__.py,sha256=rPSfWgIeq2YWVPyESOAwCBt8vftsTpIkuLAGDEzyRQc,22
|
|
3
|
+
pretix_mapplugin/apps.py,sha256=QOo53Z6zX0yB-Rz6I92tAu051sP38hM2iJlP9sk2JEg,802
|
|
4
|
+
pretix_mapplugin/geocoding.py,sha256=N_JyjYU_JBouwE3oaL3G1wI5jAyezZzS5IBMgI_GdWQ,6053
|
|
5
|
+
pretix_mapplugin/models.py,sha256=Ofx8S1UlpQVFt-pC9-_LKecF-Hv2uI2rDq5zJ5S_UJQ,2494
|
|
6
|
+
pretix_mapplugin/signals.py,sha256=_SRT-3TllYzD9kUf0JG3PnuWa6cPHkFFSWInCDGm_j0,3155
|
|
7
|
+
pretix_mapplugin/tasks.py,sha256=HKDFC-U1TtYDNrN26QVx133hn0K0BdBUm4qCCYHcgOY,6367
|
|
8
|
+
pretix_mapplugin/urls.py,sha256=gZC74OUSZI2ZUWQXm1pbz8taIV8jA9D2h7FvojRX9Ws,1458
|
|
9
|
+
pretix_mapplugin/views.py,sha256=MByLWX2_U7gqs1AV59ZICIZDcEi2nIPPOfFxZgUhCvI,11591
|
|
10
10
|
pretix_mapplugin/locale/de/LC_MESSAGES/django.mo,sha256=6VVRAqa0ixL-lDA1QwoVvG0wd5ZBwYjaR4P8T73hxhU,269
|
|
11
|
-
pretix_mapplugin/locale/de/LC_MESSAGES/django.po,sha256
|
|
11
|
+
pretix_mapplugin/locale/de/LC_MESSAGES/django.po,sha256=-HtJt_qb8k7C30pVXRuYeh5CIB_ISVt2HEAXGn2rVnw,311
|
|
12
12
|
pretix_mapplugin/locale/de_Informal/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.mo,sha256=6VVRAqa0ixL-lDA1QwoVvG0wd5ZBwYjaR4P8T73hxhU,269
|
|
14
|
-
pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.po,sha256
|
|
14
|
+
pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.po,sha256=-HtJt_qb8k7C30pVXRuYeh5CIB_ISVt2HEAXGn2rVnw,311
|
|
15
15
|
pretix_mapplugin/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
pretix_mapplugin/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
pretix_mapplugin/management/commands/geocode_existing_orders.py,sha256=
|
|
18
|
-
pretix_mapplugin/migrations/0001_initial.py,sha256=
|
|
19
|
-
pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py,sha256=
|
|
17
|
+
pretix_mapplugin/management/commands/geocode_existing_orders.py,sha256=ACDCH17WdNY6cQLVNj8oW0vlqfzlM3-oqA-sKDPtnFg,13828
|
|
18
|
+
pretix_mapplugin/migrations/0001_initial.py,sha256=i33QeU7ioVpk5HpSShdF-ievDUHsjTzKf2OWO9oSkl8,993
|
|
19
|
+
pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py,sha256=U0xDF4KcLqb1QTuDJvilr9NEECVorM7W8ll86RDWi-4,857
|
|
20
|
+
pretix_mapplugin/migrations/0003_mapmilestone.py,sha256=2fcuMOSWcyF-wReVuGs53_a8pLpYLfNoCIIAekdRSUQ,991
|
|
20
21
|
pretix_mapplugin/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
22
|
pretix_mapplugin/static/pretix_mapplugin/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css,sha256=
|
|
23
|
-
pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js,sha256=
|
|
24
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css,sha256=
|
|
25
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css,sha256
|
|
26
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js,sha256=
|
|
27
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.esm.js,sha256=
|
|
23
|
+
pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css,sha256=l-PfqoW8pGYAI30l6KYkmdxRDQdtz84vN_6Qbvo3kNU,913
|
|
24
|
+
pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js,sha256=1zJKkHL1WnB8u3MkUJOR_33dGkaiz5ylpJbNg4Tr-Iw,19897
|
|
25
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css,sha256=YSWCMtmNZNwqex4CEw1nQhvFub2lmU7vcCKP-XVwwXA,1287
|
|
26
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css,sha256=YU3qCpj_P06tdPBJGPax0bm6Q1wltfwjsho5TR4-TYc,872
|
|
27
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js,sha256=65UqrlgGoRAnKfKRuriH3eeDrOhZgZo1SCenduc-SGo,5158
|
|
28
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.esm.js,sha256=Ffst8WrY9mwnR_UnN1iW4n9cHbbYDtWt9_3psXENFCY,415639
|
|
28
29
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.esm.js.map,sha256=lxY34ye1PfsolFQ8pTsEtlgBJ4tW7panCBRsm35HNbs,866200
|
|
29
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.js,sha256=
|
|
30
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.js,sha256=IV8Omcuoc8Y1mhT0Sv-yu2T-CxccDjkT2w8Qdb7SJ6Y,441279
|
|
30
31
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.js.map,sha256=l7cBxd_w6YZOXEWKixXC7DN4ejJsXed7kI-eoTvi1Wo,866292
|
|
31
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.css,sha256=
|
|
32
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.js,sha256=
|
|
32
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.css,sha256=M3v8pcq9A7OYFbJwD-vis7ft9VkhxZzUn4jssyghIwM,14145
|
|
33
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.js,sha256=20nQCchB9co0qIjJZRGuk2_Z9VM-kNiyxNV1lvTlZBo,147552
|
|
33
34
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.js.map,sha256=YAoQ3FzREN4GmVENMir8vgHHypC0xfSK3CAxTHCqx1M,225544
|
|
34
|
-
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js,sha256=
|
|
35
|
+
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js,sha256=WL6HHfYfbFEkZOFdsJQeY7lJG_E5airjvqbznghUzRw,33724
|
|
35
36
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js.map,sha256=R8juz3AGnE2aOl3MIx1ram0tNMMiec7EPYwDbqb2ycc,27636
|
|
36
37
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/layers-2x.png,sha256=Bm2sqFDY_77wB68AsG6sABVyje4nnFHzy2xxbffELt8,1259
|
|
37
38
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/layers.png,sha256=Hbvp0CjikvNvy6j4s6KNXokydU_CIVuaxp5M3s9RB8Y,696
|
|
@@ -39,9 +40,10 @@ pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-ic
|
|
|
39
40
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-icon.png,sha256=V0w6XMqF9BFAhbaEFZbWLwDXyJLHsD8oy_owHesdxDc,1466
|
|
40
41
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-shadow.png,sha256=Jk9cZAM58ELdcpBiz8BMF_jqDymIK1OOOEjtjxDttNo,618
|
|
41
42
|
pretix_mapplugin/templates/pretix_mapplugin/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
-
pretix_mapplugin/templates/pretix_mapplugin/map_page.html,sha256=
|
|
43
|
-
|
|
44
|
-
pretix_map-0.1.
|
|
45
|
-
pretix_map-0.1.
|
|
46
|
-
pretix_map-0.1.
|
|
47
|
-
pretix_map-0.1.
|
|
43
|
+
pretix_mapplugin/templates/pretix_mapplugin/map_page.html,sha256=_lUjaaRNf9bBc4aepek4Sp2w7QE1coRLPSF9mdfIFiw,9824
|
|
44
|
+
pretix_mapplugin/templates/pretix_mapplugin/milestones.html,sha256=2cdcmuA9XxDC3BFZuVL9tO-Nsz7whKasqLM9en3x5cw,2032
|
|
45
|
+
pretix_map-0.1.5.dist-info/METADATA,sha256=ls_bUm29Qxk256wtUPHplJOrWxywvFN8J7LPecu2HN0,3575
|
|
46
|
+
pretix_map-0.1.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
47
|
+
pretix_map-0.1.5.dist-info/entry_points.txt,sha256=C3NAjeZHoCekafkLMCJynPcABRTK8AUprtQv7sUNDZs,137
|
|
48
|
+
pretix_map-0.1.5.dist-info/top_level.txt,sha256=CAtEnkgA73zE9Gadm5mjt1SpXHBPOS-QWP0dQVoNToE,17
|
|
49
|
+
pretix_map-0.1.5.dist-info/RECORD,,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
Copyright 2025 MarkenJaden
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
|
|
1
|
+
|
|
2
|
+
Copyright 2025 MarkenJaden
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
pretix_mapplugin/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.5"
|
pretix_mapplugin/apps.py
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
from django.utils.translation import gettext_lazy
|
|
2
|
-
|
|
3
|
-
from . import __version__
|
|
4
|
-
|
|
5
|
-
try:
|
|
6
|
-
from pretix.base.plugins import PluginConfig
|
|
7
|
-
except ImportError:
|
|
8
|
-
raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class PluginApp(PluginConfig):
|
|
12
|
-
default = True
|
|
13
|
-
name = "pretix_mapplugin"
|
|
14
|
-
verbose_name = "Map-Plugin"
|
|
15
|
-
|
|
16
|
-
class PretixPluginMeta:
|
|
17
|
-
name = gettext_lazy("Map-Plugin")
|
|
18
|
-
author = "MarkenJaden"
|
|
19
|
-
description = gettext_lazy("An overview map of the catchment area of previous orders. Measured by postcode")
|
|
20
|
-
visible = True
|
|
21
|
-
version = __version__
|
|
22
|
-
category = "FEATURE"
|
|
23
|
-
compatibility = "pretix>=2.7.0"
|
|
24
|
-
settings_links = []
|
|
25
|
-
navigation_links = []
|
|
26
|
-
|
|
27
|
-
def ready(self):
|
|
28
|
-
from . import signals # NOQA
|
|
1
|
+
from django.utils.translation import gettext_lazy
|
|
2
|
+
|
|
3
|
+
from . import __version__
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from pretix.base.plugins import PluginConfig
|
|
7
|
+
except ImportError:
|
|
8
|
+
raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PluginApp(PluginConfig):
|
|
12
|
+
default = True
|
|
13
|
+
name = "pretix_mapplugin"
|
|
14
|
+
verbose_name = "Map-Plugin"
|
|
15
|
+
|
|
16
|
+
class PretixPluginMeta:
|
|
17
|
+
name = gettext_lazy("Map-Plugin")
|
|
18
|
+
author = "MarkenJaden"
|
|
19
|
+
description = gettext_lazy("An overview map of the catchment area of previous orders. Measured by postcode")
|
|
20
|
+
visible = True
|
|
21
|
+
version = __version__
|
|
22
|
+
category = "FEATURE"
|
|
23
|
+
compatibility = "pretix>=2.7.0"
|
|
24
|
+
settings_links = []
|
|
25
|
+
navigation_links = []
|
|
26
|
+
|
|
27
|
+
def ready(self):
|
|
28
|
+
from . import signals # NOQA
|
pretix_mapplugin/geocoding.py
CHANGED
|
@@ -1,102 +1,162 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from geopy.geocoders import Nominatim
|
|
3
|
-
from geopy.exc import GeocoderTimedOut, GeocoderServiceError
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
geolocator
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if addr.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
1
|
+
import logging
|
|
2
|
+
from geopy.geocoders import Nominatim
|
|
3
|
+
from geopy.exc import GeocoderTimedOut, GeocoderServiceError
|
|
4
|
+
from geopy.distance import distance as geopy_distance
|
|
5
|
+
from time import sleep
|
|
6
|
+
|
|
7
|
+
# DO NOT import settings here, as it won't work reliably in Celery
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# Define a default/fallback User-Agent. Users *should* override this in pretix.cfg.
|
|
12
|
+
DEFAULT_NOMINATIM_USER_AGENT = "pretix-map-plugin/unknown (Please configure nominatim_user_agent in pretix.cfg)"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# --- Geocoding Function (Accepts user_agent) ---
|
|
16
|
+
def geocode_address(address_string: str, nominatim_user_agent: str | None = None) -> tuple[float, float] | None:
|
|
17
|
+
"""
|
|
18
|
+
Tries to geocode a given address string using Nominatim, using the
|
|
19
|
+
provided User-Agent string.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
address_string: A single string representing the address.
|
|
23
|
+
nominatim_user_agent: The User-Agent string to use for Nominatim.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A tuple (latitude, longitude) if successful, otherwise None.
|
|
27
|
+
"""
|
|
28
|
+
# Use the provided User-Agent or the default
|
|
29
|
+
user_agent = nominatim_user_agent or DEFAULT_NOMINATIM_USER_AGENT
|
|
30
|
+
|
|
31
|
+
if user_agent == DEFAULT_NOMINATIM_USER_AGENT:
|
|
32
|
+
# Log warning if default is used - admins should configure this
|
|
33
|
+
logger.warning(
|
|
34
|
+
"Using default Nominatim User-Agent. Please set a specific "
|
|
35
|
+
"'nominatim_user_agent' under [pretix_mapplugin] in your "
|
|
36
|
+
"pretix.cfg according to Nominatim's usage policy."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Initialize the geolocator with the determined user_agent
|
|
40
|
+
geolocator = Nominatim(user_agent=user_agent)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Add a 1-second delay to respect Nominatim's usage policy (1 req/sec)
|
|
44
|
+
sleep(1)
|
|
45
|
+
|
|
46
|
+
# Perform geocoding
|
|
47
|
+
location = geolocator.geocode(address_string, timeout=10) # 10-second timeout
|
|
48
|
+
|
|
49
|
+
if location:
|
|
50
|
+
logger.debug(
|
|
51
|
+
f"Geocoded '{address_string}' to ({location.latitude}, {location.longitude}) using User-Agent: {user_agent}"
|
|
52
|
+
)
|
|
53
|
+
return (location.latitude, location.longitude)
|
|
54
|
+
else:
|
|
55
|
+
logger.warning(f"Could not geocode address: {address_string} (Address not found by Nominatim)")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
except GeocoderTimedOut:
|
|
59
|
+
logger.error(f"Geocoding timed out for address: {address_string}")
|
|
60
|
+
return None
|
|
61
|
+
except GeocoderServiceError as e:
|
|
62
|
+
# Log specific service errors (e.g., API limits, server issues)
|
|
63
|
+
logger.error(f"Geocoding service error for address '{address_string}': {e}")
|
|
64
|
+
return None
|
|
65
|
+
except Exception as e:
|
|
66
|
+
# Catch any other unexpected exceptions during geocoding
|
|
67
|
+
logger.exception(f"An unexpected error occurred during geocoding for address '{address_string}': {e}")
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# --- Helper to Format Address Candidates from Pretix Order ---
|
|
72
|
+
def get_address_candidates_from_order(order) -> list[str]:
|
|
73
|
+
"""
|
|
74
|
+
Creates a list of formatted address strings from a Pretix order's invoice address,
|
|
75
|
+
ordered from most specific to least specific (fallback).
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
order: A Pretix `Order` object.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A list of address strings (e.g., ["Street, City, Zip, Country", "Zip City, Country"]).
|
|
82
|
+
"""
|
|
83
|
+
# Ensure order and invoice_address exist
|
|
84
|
+
if not order or not order.invoice_address:
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
candidates = []
|
|
88
|
+
addr = order.invoice_address # Shortcut
|
|
89
|
+
country_name = str(addr.country.name) if addr.country else ""
|
|
90
|
+
|
|
91
|
+
# Strategy 1: Full Address (Street, Zip City, State, Country)
|
|
92
|
+
parts_full = []
|
|
93
|
+
if addr.street: parts_full.append(addr.street)
|
|
94
|
+
if addr.zipcode and addr.city:
|
|
95
|
+
parts_full.append(f"{addr.zipcode} {addr.city}")
|
|
96
|
+
elif addr.city:
|
|
97
|
+
parts_full.append(addr.city)
|
|
98
|
+
elif addr.zipcode:
|
|
99
|
+
parts_full.append(addr.zipcode)
|
|
100
|
+
|
|
101
|
+
if addr.state: parts_full.append(addr.state)
|
|
102
|
+
if country_name: parts_full.append(country_name)
|
|
103
|
+
|
|
104
|
+
full_address = ", ".join(filter(None, parts_full))
|
|
105
|
+
if full_address:
|
|
106
|
+
candidates.append(full_address)
|
|
107
|
+
|
|
108
|
+
# Strategy 2: Zip + City + Country (No Street) - Good for privacy or if street is weird
|
|
109
|
+
parts_zip_city = []
|
|
110
|
+
if addr.zipcode and addr.city:
|
|
111
|
+
parts_zip_city.append(f"{addr.zipcode} {addr.city}")
|
|
112
|
+
elif addr.city: # Fallback if only city
|
|
113
|
+
parts_zip_city.append(addr.city)
|
|
114
|
+
|
|
115
|
+
if country_name: parts_zip_city.append(country_name)
|
|
116
|
+
|
|
117
|
+
zip_city_address = ", ".join(filter(None, parts_zip_city))
|
|
118
|
+
if zip_city_address and zip_city_address not in candidates:
|
|
119
|
+
candidates.append(zip_city_address)
|
|
120
|
+
|
|
121
|
+
# Strategy 3: Just Zip + Country (Coarse location)
|
|
122
|
+
parts_zip = []
|
|
123
|
+
if addr.zipcode: parts_zip.append(addr.zipcode)
|
|
124
|
+
if country_name: parts_zip.append(country_name)
|
|
125
|
+
|
|
126
|
+
zip_address = ", ".join(filter(None, parts_zip))
|
|
127
|
+
if zip_address and zip_address not in candidates:
|
|
128
|
+
candidates.append(zip_address)
|
|
129
|
+
|
|
130
|
+
return candidates
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_best_address_string(order) -> str | None:
|
|
134
|
+
"""Returns the most specific address string for an order."""
|
|
135
|
+
candidates = get_address_candidates_from_order(order)
|
|
136
|
+
return candidates[0] if candidates else None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def geocode_event_location(event, nominatim_user_agent: str | None = None) -> tuple[float, float] | None:
|
|
140
|
+
"""
|
|
141
|
+
Tries to geocode the event's location field.
|
|
142
|
+
"""
|
|
143
|
+
if not event or not event.location:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
location_str = str(event.location)
|
|
147
|
+
logger.info(f"Geocoding event location: {location_str}")
|
|
148
|
+
return geocode_address(location_str, nominatim_user_agent=nominatim_user_agent)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def calculate_distance(coord1: tuple[float, float], coord2: tuple[float, float]) -> float | None:
|
|
152
|
+
"""
|
|
153
|
+
Calculates the distance in kilometers between two coordinates (lat, lon).
|
|
154
|
+
Returns None if coordinates are invalid.
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
if not coord1 or not coord2:
|
|
158
|
+
return None
|
|
159
|
+
return geopy_distance(coord1, coord2).km
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.warning(f"Error calculating distance: {e}")
|
|
162
|
+
return None
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
msgid ""
|
|
2
|
-
msgstr ""
|
|
3
|
-
"Project-Id-Version: \n"
|
|
4
|
-
"Report-Msgid-Bugs-To: \n"
|
|
5
|
-
"POT-Creation-Date: 2017-03-07 19:01+0100\n"
|
|
6
|
-
"PO-Revision-Date: \n"
|
|
7
|
-
"Last-Translator: MarkenJaden\n"
|
|
8
|
-
"Language-Team: \n"
|
|
9
|
-
"Language: de\n"
|
|
10
|
-
"MIME-Version: 1.0\n"
|
|
11
|
-
"Content-Type: text/plain; charset=UTF-8\n"
|
|
12
|
-
"Content-Transfer-Encoding: 8bit\n"
|
|
1
|
+
msgid ""
|
|
2
|
+
msgstr ""
|
|
3
|
+
"Project-Id-Version: \n"
|
|
4
|
+
"Report-Msgid-Bugs-To: \n"
|
|
5
|
+
"POT-Creation-Date: 2017-03-07 19:01+0100\n"
|
|
6
|
+
"PO-Revision-Date: \n"
|
|
7
|
+
"Last-Translator: MarkenJaden\n"
|
|
8
|
+
"Language-Team: \n"
|
|
9
|
+
"Language: de\n"
|
|
10
|
+
"MIME-Version: 1.0\n"
|
|
11
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
12
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
msgid ""
|
|
2
|
-
msgstr ""
|
|
3
|
-
"Project-Id-Version: \n"
|
|
4
|
-
"Report-Msgid-Bugs-To: \n"
|
|
5
|
-
"POT-Creation-Date: 2017-03-07 19:01+0100\n"
|
|
6
|
-
"PO-Revision-Date: \n"
|
|
7
|
-
"Last-Translator: MarkenJaden\n"
|
|
8
|
-
"Language-Team: \n"
|
|
9
|
-
"Language: de\n"
|
|
10
|
-
"MIME-Version: 1.0\n"
|
|
11
|
-
"Content-Type: text/plain; charset=UTF-8\n"
|
|
12
|
-
"Content-Transfer-Encoding: 8bit\n"
|
|
1
|
+
msgid ""
|
|
2
|
+
msgstr ""
|
|
3
|
+
"Project-Id-Version: \n"
|
|
4
|
+
"Report-Msgid-Bugs-To: \n"
|
|
5
|
+
"POT-Creation-Date: 2017-03-07 19:01+0100\n"
|
|
6
|
+
"PO-Revision-Date: \n"
|
|
7
|
+
"Last-Translator: MarkenJaden\n"
|
|
8
|
+
"Language-Team: \n"
|
|
9
|
+
"Language: de\n"
|
|
10
|
+
"MIME-Version: 1.0\n"
|
|
11
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
12
|
+
"Content-Transfer-Encoding: 8bit\n"
|