pretix-map 0.1.1__py3-none-any.whl → 0.1.3__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.1.dist-info → pretix_map-0.1.3.dist-info}/METADATA +1 -1
- {pretix_map-0.1.1.dist-info → pretix_map-0.1.3.dist-info}/RECORD +15 -14
- pretix_mapplugin/__init__.py +1 -1
- pretix_mapplugin/management/commands/geocode_existing_orders.py +149 -70
- pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py +32 -0
- pretix_mapplugin/models.py +30 -10
- pretix_mapplugin/signals.py +27 -22
- pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css +40 -7
- pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js +321 -257
- pretix_mapplugin/tasks.py +41 -18
- pretix_mapplugin/templates/pretix_mapplugin/map_page.html +40 -17
- {pretix_map-0.1.1.dist-info → pretix_map-0.1.3.dist-info}/WHEEL +0 -0
- {pretix_map-0.1.1.dist-info → pretix_map-0.1.3.dist-info}/entry_points.txt +0 -0
- {pretix_map-0.1.1.dist-info → pretix_map-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {pretix_map-0.1.1.dist-info → pretix_map-0.1.3.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pretix-map
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.3
|
4
4
|
Summary: An overview map of the catchment area of previous orders. Measured by postcode
|
5
5
|
Author-email: MarkenJaden <jjsch1410@gmail.com>
|
6
6
|
Maintainer-email: MarkenJaden <jjsch1410@gmail.com>
|
@@ -1,10 +1,10 @@
|
|
1
|
-
pretix_map-0.1.
|
2
|
-
pretix_mapplugin/__init__.py,sha256=
|
1
|
+
pretix_map-0.1.3.dist-info/licenses/LICENSE,sha256=RhQ89ePNDClBzEROahhwjDrBSEb5Zpx6XewZfGlY4Ss,569
|
2
|
+
pretix_mapplugin/__init__.py,sha256=LPNaKUFe5S0lxcbXP4IcviKz02tUsjRUDAJch2OV7IE,23
|
3
3
|
pretix_mapplugin/apps.py,sha256=AnThwyRw2AAz5f-kmXZ8hm85OmKnlDkRosVoQOBgPzE,830
|
4
4
|
pretix_mapplugin/geocoding.py,sha256=lBmwMvmE_cPyOHxWE8H3Se2P-2Eq0UjDTCv9gUs97Fo,4018
|
5
|
-
pretix_mapplugin/models.py,sha256=
|
6
|
-
pretix_mapplugin/signals.py,sha256=
|
7
|
-
pretix_mapplugin/tasks.py,sha256=
|
5
|
+
pretix_mapplugin/models.py,sha256=klKrgMu1bmiPBwucTdOAZGWtv4WAEKcnoeqPlZgFR1A,2091
|
6
|
+
pretix_mapplugin/signals.py,sha256=pSkucUPU6XgR0KLw4bKYEUW2Bqs5vfQXhO5YILQ2wps,3928
|
7
|
+
pretix_mapplugin/tasks.py,sha256=F_c36RwyTQzUJ9kBBos_-2zth1UXw_kpcQpUcE90BNM,5428
|
8
8
|
pretix_mapplugin/urls.py,sha256=o5407vULF4S-bUihU7AeRxUcMyazg2lPjbvqRflsGxE,838
|
9
9
|
pretix_mapplugin/views.py,sha256=7WgmNZeqwmOesT6PrkAIRC8fNfAcWGm-j9-2YqF5egI,7146
|
10
10
|
pretix_mapplugin/locale/de/LC_MESSAGES/django.mo,sha256=6VVRAqa0ixL-lDA1QwoVvG0wd5ZBwYjaR4P8T73hxhU,269
|
@@ -14,12 +14,13 @@ pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.mo,sha256=6VVRAqa0ixL-lDA
|
|
14
14
|
pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.po,sha256=tIFKw9KOdGTjGq8bHV6tquRZe_MOn8TT4MJjdTRhId8,323
|
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=
|
17
|
+
pretix_mapplugin/management/commands/geocode_existing_orders.py,sha256=Go9XzBb-eI_ohGDJ4x74MWIQkq2f1rMDLFNo0hp29AM,14127
|
18
18
|
pretix_mapplugin/migrations/0001_initial.py,sha256=KAl1Egxptv1bpregGbsh8wUbr4Yh5A_zazVSAQdmoHM,1020
|
19
|
+
pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py,sha256=dXmZRdqrND0pxiPRuitlDdg-Q2JBqYG-sRPJxr6Urpk,889
|
19
20
|
pretix_mapplugin/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
21
|
pretix_mapplugin/static/pretix_mapplugin/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css,sha256=
|
22
|
-
pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js,sha256=
|
22
|
+
pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css,sha256=itRfmhct3UAz9rg0i8KYZDNQbN_sNgo8qoa1UPhvQW0,964
|
23
|
+
pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js,sha256=4SX0yr0VuJQro-A0Fnl6pTlAfk4X9XhjDxkGq95qUsQ,21272
|
23
24
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css,sha256=LWhzWaQGZRsWFrrJxg-6Zn8TT84k0_trtiHBc6qcGpY,1346
|
24
25
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css,sha256=-bdWuWOXMFkX0v9Cvr3OWClPiYefDQz9GGZP_7xZxdc,886
|
25
26
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js,sha256=aPb_2lnWKnXsUc1_-aT9-kbtr4CV3c85jH9xC1e5QDI,5168
|
@@ -38,9 +39,9 @@ pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-ic
|
|
38
39
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-icon.png,sha256=V0w6XMqF9BFAhbaEFZbWLwDXyJLHsD8oy_owHesdxDc,1466
|
39
40
|
pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-shadow.png,sha256=Jk9cZAM58ELdcpBiz8BMF_jqDymIK1OOOEjtjxDttNo,618
|
40
41
|
pretix_mapplugin/templates/pretix_mapplugin/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
|
-
pretix_mapplugin/templates/pretix_mapplugin/map_page.html,sha256=
|
42
|
-
pretix_map-0.1.
|
43
|
-
pretix_map-0.1.
|
44
|
-
pretix_map-0.1.
|
45
|
-
pretix_map-0.1.
|
46
|
-
pretix_map-0.1.
|
42
|
+
pretix_mapplugin/templates/pretix_mapplugin/map_page.html,sha256=OC1CA5wAcDTC8BOejqO98u0vT0-pnfTgB1Frxk_3uWI,3391
|
43
|
+
pretix_map-0.1.3.dist-info/METADATA,sha256=LslAvtHnkh0p5WdCL6vJi5ERPpFNuOXzkvy7ZcRA-jQ,9518
|
44
|
+
pretix_map-0.1.3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
45
|
+
pretix_map-0.1.3.dist-info/entry_points.txt,sha256=C3NAjeZHoCekafkLMCJynPcABRTK8AUprtQv7sUNDZs,137
|
46
|
+
pretix_map-0.1.3.dist-info/top_level.txt,sha256=CAtEnkgA73zE9Gadm5mjt1SpXHBPOS-QWP0dQVoNToE,17
|
47
|
+
pretix_map-0.1.3.dist-info/RECORD,,
|
pretix_mapplugin/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.3"
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import logging
|
2
|
+
import time # Import time for sleep
|
2
3
|
from django.core.management.base import BaseCommand, CommandError
|
3
|
-
from django.core.exceptions import FieldDoesNotExist
|
4
|
+
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
|
5
|
+
from django.db import transaction
|
4
6
|
|
5
7
|
# --- Import Pretix Global Settings accessor ---
|
6
8
|
from django_scopes import scope
|
@@ -21,22 +23,31 @@ except ImportError:
|
|
21
23
|
except ImportError:
|
22
24
|
# Fallback or raise error if neither is found
|
23
25
|
logger.error("Could not determine Pretix settings accessor for management command.")
|
24
|
-
|
26
|
+
|
27
|
+
|
28
|
+
# This will likely cause the command to fail later, but allows it to start
|
29
|
+
class SettingsProxy:
|
30
|
+
def __init__(self): self.settings = {} # Empty dict to avoid errors later
|
25
31
|
|
26
32
|
# --- Import necessary Pretix models ---
|
27
33
|
from pretix.base.models import Order, Event, Organizer
|
28
34
|
|
29
|
-
# --- Import your Geocode model and
|
35
|
+
# --- Import your Geocode model and geocoding functions ---
|
30
36
|
from pretix_mapplugin.models import OrderGeocodeData
|
31
|
-
|
32
|
-
|
33
|
-
|
37
|
+
# --- Import geocoding functions directly, NOT the task ---
|
38
|
+
from pretix_mapplugin.geocoding import (
|
39
|
+
geocode_address,
|
40
|
+
get_formatted_address_from_order,
|
41
|
+
DEFAULT_NOMINATIM_USER_AGENT
|
42
|
+
)
|
34
43
|
|
35
44
|
logger = logging.getLogger(__name__)
|
36
45
|
|
37
46
|
|
38
47
|
class Command(BaseCommand):
|
39
|
-
help = 'Scans paid orders and
|
48
|
+
help = ('Scans paid orders and geocodes addresses for those missing geocode data directly '
|
49
|
+
'within the command, respecting Nominatim rate limits (approx 1 req/sec). '
|
50
|
+
'This can take a long time for many orders.')
|
40
51
|
|
41
52
|
def add_arguments(self, parser):
|
42
53
|
parser.add_argument(
|
@@ -46,11 +57,15 @@ class Command(BaseCommand):
|
|
46
57
|
'--event', type=str, help='Slug of a specific event to process orders for. Requires --organizer.',
|
47
58
|
)
|
48
59
|
parser.add_argument(
|
49
|
-
'--dry-run', action='store_true', help='Simulate
|
60
|
+
'--dry-run', action='store_true', help='Simulate without geocoding or saving.',
|
50
61
|
)
|
51
62
|
parser.add_argument(
|
52
63
|
'--force-recode', action='store_true',
|
53
|
-
help='
|
64
|
+
help='Geocode even for orders that already have geocode data.',
|
65
|
+
)
|
66
|
+
parser.add_argument(
|
67
|
+
'--delay', type=float, default=1.1,
|
68
|
+
help='Delay in seconds between geocoding requests (default: 1.1 to be safe). Set to 0 to disable.'
|
54
69
|
)
|
55
70
|
|
56
71
|
def handle(self, *args, **options):
|
@@ -58,6 +73,14 @@ class Command(BaseCommand):
|
|
58
73
|
event_slug = options['event']
|
59
74
|
dry_run = options['dry_run']
|
60
75
|
force_recode = options['force_recode']
|
76
|
+
delay = options['delay']
|
77
|
+
|
78
|
+
if delay < 1.0 and delay != 0: # Allow disabling delay with 0
|
79
|
+
self.stdout.write(self.style.WARNING(
|
80
|
+
f"Delay is {delay}s, which is less than 1 second. This may violate Nominatim usage policy."))
|
81
|
+
elif delay == 0:
|
82
|
+
self.stdout.write(
|
83
|
+
self.style.WARNING("Delay is disabled (--delay 0). Ensure you comply with geocoding service terms."))
|
61
84
|
|
62
85
|
if event_slug and not organizer_slug:
|
63
86
|
raise CommandError("You must specify --organizer when using --event.")
|
@@ -65,13 +88,10 @@ class Command(BaseCommand):
|
|
65
88
|
# --- Read User-Agent using Pretix Settings accessor ---
|
66
89
|
user_agent = DEFAULT_NOMINATIM_USER_AGENT
|
67
90
|
try:
|
68
|
-
# Get settings holder instance
|
69
91
|
gs = SettingsProxy()
|
70
|
-
# Construct the setting key specific to plugins
|
71
|
-
# Format might be 'plugin:plugin_name:setting_name' or just 'plugin_name_setting_name'
|
72
|
-
# Check Pretix docs or experiment if needed. Assuming the former.
|
73
92
|
setting_key = 'plugin:pretix_mapplugin:nominatim_user_agent'
|
74
|
-
|
93
|
+
# Use .get() which is safer for dictionaries possibly returned by load_config
|
94
|
+
user_agent = getattr(gs, 'settings', {}).get(setting_key, DEFAULT_NOMINATIM_USER_AGENT)
|
75
95
|
|
76
96
|
if user_agent == DEFAULT_NOMINATIM_USER_AGENT:
|
77
97
|
self.stdout.write(self.style.WARNING(
|
@@ -79,9 +99,7 @@ class Command(BaseCommand):
|
|
79
99
|
f"'{setting_key}' in your pretix.cfg."
|
80
100
|
))
|
81
101
|
except Exception as e:
|
82
|
-
# Catch broad exception during settings access
|
83
102
|
self.stderr.write(self.style.ERROR(f"Failed to read plugin settings: {e}. Using default User-Agent."))
|
84
|
-
# Continue with default user_agent
|
85
103
|
# --- End Read User-Agent ---
|
86
104
|
|
87
105
|
# --- Determine which organizers to process ---
|
@@ -98,36 +116,54 @@ class Command(BaseCommand):
|
|
98
116
|
self.stdout.write(f"Processing all {len(organizers_to_process)} organizers...")
|
99
117
|
|
100
118
|
# --- Initialize counters ---
|
101
|
-
|
102
|
-
|
103
|
-
|
119
|
+
total_processed_count = 0 # Orders actually attempted (had address or forced)
|
120
|
+
total_geocoded_success = 0
|
121
|
+
total_geocode_failed = 0 # Geocoder returned None
|
122
|
+
total_skipped_no_address = 0
|
123
|
+
total_skipped_db_error = 0
|
124
|
+
total_checked_count = 0 # All orders matching initial filter
|
104
125
|
|
105
|
-
# --- Iterate through organizers
|
126
|
+
# --- Iterate through organizers ---
|
106
127
|
for organizer in organizers_to_process:
|
107
128
|
self.stdout.write(f"\n--- Processing Organizer: {organizer.name} ({organizer.slug}) ---")
|
129
|
+
# current_organizer_pk = organizer.pk # No longer needed for task
|
108
130
|
|
109
131
|
with scope(organizer=organizer):
|
110
132
|
# --- Get orders ---
|
111
|
-
orders_qs = Order.objects.filter(status=Order.STATUS_PAID)
|
133
|
+
orders_qs = Order.objects.filter(status=Order.STATUS_PAID).select_related(
|
134
|
+
'invoice_address', 'invoice_address__country', 'event'
|
135
|
+
)
|
112
136
|
|
113
|
-
# --- Filter by event
|
137
|
+
# --- Filter by event ---
|
114
138
|
if event_slug and organizer.slug == organizer_slug:
|
115
139
|
try:
|
116
140
|
event = Event.objects.get(slug=event_slug)
|
117
141
|
orders_qs = orders_qs.filter(event=event)
|
118
|
-
self.stdout.write(f" Filtering
|
142
|
+
self.stdout.write(f" Filtering for event: {event.name} ({event_slug})")
|
119
143
|
except Event.DoesNotExist:
|
120
|
-
self.stderr.write(
|
121
|
-
f" Event '{event_slug}' not found
|
144
|
+
self.stderr.write(
|
145
|
+
self.style.WARNING(f" Event '{event_slug}' not found. Skipping event filter."))
|
122
146
|
if organizer_slug and event_slug: continue
|
123
147
|
|
124
|
-
# ---
|
125
|
-
relation_name = 'geocode_data' #
|
126
|
-
|
148
|
+
# --- Determine which orders to process ---
|
149
|
+
relation_name = 'geocode_data' # Check model
|
150
|
+
orders_to_geocode_list = []
|
151
|
+
current_checked_count = orders_qs.count() # Count before filtering for geocode status
|
152
|
+
total_checked_count += current_checked_count
|
153
|
+
|
154
|
+
if force_recode:
|
155
|
+
orders_to_geocode_list = list(orders_qs)
|
156
|
+
self.stdout.write(self.style.WARNING(
|
157
|
+
f" Will process all {len(orders_to_geocode_list)} orders (--force-recode)..."))
|
158
|
+
else:
|
127
159
|
try:
|
128
|
-
Order._meta.get_field(relation_name)
|
129
|
-
|
130
|
-
|
160
|
+
Order._meta.get_field(relation_name)
|
161
|
+
existing_pks = set(OrderGeocodeData.objects.filter(
|
162
|
+
order__in=orders_qs
|
163
|
+
).values_list('order_id', flat=True))
|
164
|
+
orders_to_geocode_list = list(orders_qs.exclude(pk__in=existing_pks))
|
165
|
+
self.stdout.write(
|
166
|
+
f" Found {len(orders_to_geocode_list)} orders missing geocode data (out of {current_checked_count} checked).")
|
131
167
|
except FieldDoesNotExist:
|
132
168
|
self.stderr.write(
|
133
169
|
self.style.ERROR(f" Relation '{relation_name}' not found. Skipping organizer."))
|
@@ -135,58 +171,101 @@ class Command(BaseCommand):
|
|
135
171
|
except Exception as e:
|
136
172
|
self.stderr.write(self.style.ERROR(f" Error checking relation: {e}. Skipping organizer."))
|
137
173
|
continue
|
138
|
-
else:
|
139
|
-
orders_to_process_qs = orders_qs
|
140
|
-
self.stdout.write(self.style.WARNING(" Processing ALL selected paid orders (--force-recode)..."))
|
141
|
-
|
142
|
-
# --- Process orders for this scope ---
|
143
|
-
current_org_orders_count = orders_to_process_qs.count()
|
144
|
-
all_checked_for_org = orders_qs.count()
|
145
|
-
total_processed_orders += all_checked_for_org
|
146
174
|
|
147
|
-
if
|
148
|
-
self.stdout.write(
|
175
|
+
if not orders_to_geocode_list:
|
176
|
+
self.stdout.write(" No orders require geocoding for this selection.")
|
149
177
|
continue
|
150
178
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
179
|
+
# --- Process Orders Sequentially ---
|
180
|
+
count_this_org = 0
|
181
|
+
org_geocoded = 0
|
182
|
+
org_failed = 0
|
183
|
+
org_skipped_no_addr = 0
|
184
|
+
org_skipped_db = 0
|
185
|
+
|
186
|
+
for i, order in enumerate(orders_to_geocode_list):
|
187
|
+
count_this_org += 1
|
188
|
+
self.stdout.write(
|
189
|
+
f" Processing order {count_this_org}/{len(orders_to_geocode_list)}: {order.code} ...",
|
190
|
+
ending="")
|
191
|
+
|
192
|
+
address_str = get_formatted_address_from_order(order)
|
193
|
+
if not address_str:
|
194
|
+
self.stdout.write(self.style.WARNING(" No address. Skipping."))
|
195
|
+
total_skipped_no_address += 1
|
196
|
+
org_skipped_no_addr += 1
|
197
|
+
# Save null coords to prevent re-processing if not forcing
|
198
|
+
if not dry_run and not force_recode:
|
199
|
+
try:
|
200
|
+
with transaction.atomic():
|
201
|
+
OrderGeocodeData.objects.update_or_create(order=order, defaults={'latitude': None,
|
202
|
+
'longitude': None})
|
203
|
+
except Exception as db_err:
|
204
|
+
self.stdout.write(self.style.ERROR(f" FAILED (DB Error saving null: {db_err})"))
|
205
|
+
logger.exception(
|
206
|
+
f"Failed to save null geocode data via command for order {order.code}: {db_err}")
|
207
|
+
total_skipped_db_error += 1
|
208
|
+
org_skipped_db += 1
|
209
|
+
continue # Move to next order
|
210
|
+
|
211
|
+
# Only increment this if we actually attempt geocoding
|
212
|
+
total_processed_count += 1
|
155
213
|
|
156
|
-
for order in orders_to_process_qs.iterator():
|
157
214
|
if dry_run:
|
158
|
-
self.stdout.write(
|
159
|
-
|
160
|
-
org_queued += 1
|
215
|
+
self.stdout.write(self.style.SUCCESS(" [DRY RUN] Would geocode."))
|
216
|
+
org_geocoded += 1 # Simulate success for dry run count
|
161
217
|
else:
|
218
|
+
# --- Perform Geocoding Directly ---
|
219
|
+
coordinates = geocode_address(address_str, nominatim_user_agent=user_agent)
|
220
|
+
|
221
|
+
# --- Save Result ---
|
162
222
|
try:
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
223
|
+
with transaction.atomic():
|
224
|
+
obj, created = OrderGeocodeData.objects.update_or_create(
|
225
|
+
order=order,
|
226
|
+
defaults={'latitude': coordinates[0] if coordinates else None,
|
227
|
+
'longitude': coordinates[1] if coordinates else None}
|
228
|
+
)
|
229
|
+
if coordinates:
|
230
|
+
self.stdout.write(
|
231
|
+
self.style.SUCCESS(f" OK ({coordinates[0]:.4f}, {coordinates[1]:.4f})"))
|
232
|
+
org_geocoded += 1
|
233
|
+
else:
|
234
|
+
self.stdout.write(self.style.WARNING(" FAILED (Geocode returned None)"))
|
235
|
+
org_failed += 1
|
169
236
|
except Exception as e:
|
170
|
-
self.
|
171
|
-
logger.exception(f"Failed to
|
172
|
-
|
237
|
+
self.stdout.write(self.style.ERROR(f" FAILED (DB Error: {e})"))
|
238
|
+
logger.exception(f"Failed to save geocode data via command for order {order.code}: {e}")
|
239
|
+
org_skipped_db += 1 # Count DB errors separately
|
240
|
+
|
241
|
+
# --- Apply Delay ---
|
242
|
+
if delay > 0 and i < len(
|
243
|
+
orders_to_geocode_list) - 1: # Don't sleep after the last one or if delay is 0
|
244
|
+
time.sleep(delay)
|
173
245
|
|
174
|
-
|
175
|
-
|
176
|
-
|
246
|
+
# Add org counts to totals
|
247
|
+
total_geocoded_success += org_geocoded
|
248
|
+
total_geocode_failed += org_failed
|
249
|
+
total_skipped_db_error += org_skipped_db
|
250
|
+
|
251
|
+
self.stdout.write(f" Finished Organizer: Succeeded: {org_geocoded}, Failed Geocode: {org_failed}, "
|
252
|
+
f"Skipped (No Addr): {org_skipped_no_addr}, Skipped (DB Err): {org_skipped_db}.")
|
177
253
|
# End scope
|
178
254
|
|
179
|
-
# --- Final Report ---
|
255
|
+
# --- Final Overall Report ---
|
180
256
|
self.stdout.write("=" * 40)
|
181
|
-
self.stdout.write("Overall Summary:")
|
257
|
+
self.stdout.write("Overall Geocoding Summary:")
|
182
258
|
self.stdout.write(f" Organizers processed: {len(organizers_to_process)}")
|
183
|
-
self.stdout.write(f" Total
|
259
|
+
self.stdout.write(f" Total Orders Checked (paid, matching filters): {total_checked_count}")
|
260
|
+
self.stdout.write(f" Total Orders Attempted (had address or forced): {total_processed_count}")
|
184
261
|
if dry_run:
|
185
|
-
self.stdout.write(
|
186
|
-
|
262
|
+
self.stdout.write(self.style.SUCCESS(
|
263
|
+
f"[DRY RUN] Complete. Would have attempted geocoding for {total_processed_count} orders "
|
264
|
+
f"(+ {total_skipped_no_address} skipped due to no address)."))
|
187
265
|
else:
|
188
|
-
self.stdout.write(self.style.SUCCESS(f"
|
189
|
-
|
190
|
-
|
191
|
-
|
266
|
+
self.stdout.write(self.style.SUCCESS(f" Successfully Geocoded & Saved: {total_geocoded_success}"))
|
267
|
+
self.stdout.write(self.style.WARNING(f" Geocoding Failed (None returned): {total_geocode_failed}"))
|
268
|
+
self.stdout.write(f" Skipped (No Address): {total_skipped_no_address}")
|
269
|
+
if total_skipped_db_error > 0:
|
270
|
+
self.stdout.write(self.style.ERROR(f" Skipped (DB Save Error): {total_skipped_db_error} (check logs)"))
|
192
271
|
self.stdout.write("=" * 40)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Generated by Django 4.2.20 on 2025-04-16 01:22
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('pretix_mapplugin', '0001_initial'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.RemoveField(
|
14
|
+
model_name='ordergeocodedata',
|
15
|
+
name='geocoded_timestamp',
|
16
|
+
),
|
17
|
+
migrations.AddField(
|
18
|
+
model_name='ordergeocodedata',
|
19
|
+
name='last_geocoded_at',
|
20
|
+
field=models.DateTimeField(auto_now=True),
|
21
|
+
),
|
22
|
+
migrations.AlterField(
|
23
|
+
model_name='ordergeocodedata',
|
24
|
+
name='latitude',
|
25
|
+
field=models.FloatField(null=True),
|
26
|
+
),
|
27
|
+
migrations.AlterField(
|
28
|
+
model_name='ordergeocodedata',
|
29
|
+
name='longitude',
|
30
|
+
field=models.FloatField(null=True),
|
31
|
+
),
|
32
|
+
]
|
pretix_mapplugin/models.py
CHANGED
@@ -2,26 +2,46 @@ from django.db import models
|
|
2
2
|
from pretix.base.models import LoggedModel, Order
|
3
3
|
|
4
4
|
|
5
|
-
class OrderGeocodeData(LoggedModel):
|
5
|
+
class OrderGeocodeData(LoggedModel): # Keep LoggedModel if you want audit logs
|
6
6
|
"""
|
7
7
|
Stores the geocoded coordinates for a Pretix Order's invoice address.
|
8
|
+
Allows null coordinates for failed geocoding attempts.
|
8
9
|
"""
|
9
10
|
order = models.OneToOneField(
|
10
11
|
Order,
|
11
|
-
on_delete=models.CASCADE,
|
12
|
-
related_name='geocode_data',
|
13
|
-
primary_key=True
|
12
|
+
on_delete=models.CASCADE,
|
13
|
+
related_name='geocode_data',
|
14
|
+
primary_key=True # Keep this if you want order PK as primary key
|
14
15
|
)
|
15
|
-
latitude = models.FloatField(
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
latitude = models.FloatField(
|
17
|
+
null=True, # Allow NULL in the database if geocoding fails
|
18
|
+
blank=True # Allow blank in forms/admin (good practice with null=True)
|
19
|
+
)
|
20
|
+
longitude = models.FloatField(
|
21
|
+
null=True, # Allow NULL in the database if geocoding fails
|
22
|
+
blank=True # Allow blank in forms/admin
|
23
|
+
)
|
24
|
+
|
25
|
+
# Change to auto_now to update timestamp on every save (successful or null)
|
26
|
+
last_geocoded_at = models.DateTimeField(
|
27
|
+
auto_now=True, # Set/Update timestamp every time the record is saved
|
28
|
+
help_text="Timestamp when geocoding was last attempted/updated."
|
19
29
|
)
|
20
30
|
|
21
31
|
class Meta:
|
22
|
-
# Optional: Define how instances are named in logs/admin
|
23
32
|
verbose_name = "Order Geocode Data"
|
24
33
|
verbose_name_plural = "Order Geocode Data"
|
34
|
+
# Optional: Indexing coordinates can speed up map data queries if you have many entries
|
35
|
+
# indexes = [
|
36
|
+
# models.Index(fields=['latitude', 'longitude']),
|
37
|
+
# ]
|
25
38
|
|
26
39
|
def __str__(self):
|
27
|
-
|
40
|
+
# Provide more informative string representation
|
41
|
+
if self.latitude is not None and self.longitude is not None:
|
42
|
+
return f"Geocode for Order {self.order.code}: ({self.latitude:.4f}, {self.longitude:.4f})"
|
43
|
+
else:
|
44
|
+
# Indicate if it's pending (never attempted) or failed (null coords stored)
|
45
|
+
# This requires knowing if the record exists but has nulls vs doesn't exist yet
|
46
|
+
# The current __str__ assumes the record exists if called.
|
47
|
+
return f"Geocode data for Order {self.order.code} (Coordinates: None)"
|
pretix_mapplugin/signals.py
CHANGED
@@ -3,7 +3,6 @@ from django.dispatch import receiver
|
|
3
3
|
from django.urls import reverse, NoReverseMatch
|
4
4
|
from django.utils.translation import gettext_lazy as _
|
5
5
|
from django.http import HttpRequest
|
6
|
-
# Import Django settings to read config in web process
|
7
6
|
from django.conf import settings
|
8
7
|
|
9
8
|
# --- Pretix Signals ---
|
@@ -13,50 +12,56 @@ from pretix.control.signals import nav_event
|
|
13
12
|
# --- Tasks ---
|
14
13
|
from .tasks import geocode_order_task
|
15
14
|
# --- Geocoding Default ---
|
16
|
-
from .geocoding import DEFAULT_NOMINATIM_USER_AGENT
|
15
|
+
from .geocoding import DEFAULT_NOMINATIM_USER_AGENT
|
17
16
|
|
18
17
|
logger = logging.getLogger(__name__)
|
19
18
|
|
20
19
|
# --- Constants ---
|
21
20
|
MAP_VIEW_URL_NAME = 'plugins:pretix_mapplugin:event.settings.salesmap.show'
|
22
21
|
REQUIRED_MAP_PERMISSION = 'can_view_orders'
|
23
|
-
PLUGIN_NAME = 'pretix_mapplugin'
|
22
|
+
PLUGIN_NAME = 'pretix_mapplugin'
|
24
23
|
|
25
24
|
|
26
|
-
# --- Signal Receiver for Geocoding (
|
25
|
+
# --- Signal Receiver for Geocoding (Passes organizer_pk) ---
|
27
26
|
@receiver(order_paid, dispatch_uid="sales_mapper_order_paid_geocode")
|
28
27
|
def trigger_geocoding_on_payment(sender, order, **kwargs):
|
29
28
|
"""
|
30
29
|
Listens for the order_paid signal, reads geocoding config,
|
31
|
-
and queues the geocoding task with
|
30
|
+
and queues the geocoding task with order_pk, organizer_pk, and config.
|
32
31
|
"""
|
33
|
-
user_agent = DEFAULT_NOMINATIM_USER_AGENT
|
32
|
+
user_agent = DEFAULT_NOMINATIM_USER_AGENT
|
33
|
+
organizer_pk = None # Initialize
|
34
34
|
try:
|
35
|
-
#
|
36
|
-
|
35
|
+
# Ensure order has event and organizer before proceeding
|
36
|
+
if not order or not order.event or not order.event.organizer:
|
37
|
+
logger.error(f"Order {order.code} is missing event or organizer information. Cannot queue task.")
|
38
|
+
return
|
39
|
+
|
40
|
+
organizer_pk = order.event.organizer.pk # Get organizer PK
|
41
|
+
|
42
|
+
# --- Read User-Agent from settings ---
|
37
43
|
if hasattr(settings, 'plugins') and hasattr(settings.plugins, PLUGIN_NAME):
|
38
44
|
plugin_settings = getattr(settings.plugins, PLUGIN_NAME)
|
39
|
-
user_agent = plugin_settings.get(
|
40
|
-
'nominatim_user_agent', # Setting name in pretix.cfg
|
41
|
-
DEFAULT_NOMINATIM_USER_AGENT
|
42
|
-
)
|
45
|
+
user_agent = plugin_settings.get('nominatim_user_agent', DEFAULT_NOMINATIM_USER_AGENT)
|
43
46
|
else:
|
44
|
-
logger.warning(
|
45
|
-
f"Could not access settings.plugins.{PLUGIN_NAME}, "
|
46
|
-
"using default Nominatim User-Agent for task."
|
47
|
-
)
|
47
|
+
logger.warning(f"Could not access settings.plugins.{PLUGIN_NAME}, using default User-Agent.")
|
48
48
|
|
49
|
-
# --- Queue task with user_agent as keyword
|
49
|
+
# --- Queue task with user_agent and organizer_pk as keyword arguments ---
|
50
50
|
geocode_order_task.apply_async(
|
51
|
-
args=[order.pk],
|
52
|
-
kwargs={
|
51
|
+
args=[order.pk], # Keep order_pk as positional argument
|
52
|
+
kwargs={
|
53
|
+
'nominatim_user_agent': user_agent,
|
54
|
+
'organizer_pk': organizer_pk # Pass organizer PK
|
55
|
+
}
|
53
56
|
)
|
54
|
-
logger.info(f"Geocoding task queued for paid order {order.code} (PK: {order.pk}).")
|
57
|
+
logger.info(f"Geocoding task queued for paid order {order.code} (PK: {order.pk}, Org PK: {organizer_pk}).")
|
55
58
|
|
56
|
-
except ImportError:
|
59
|
+
except ImportError:
|
57
60
|
logger.exception("Could not import geocode_order_task. Check tasks.py.")
|
58
61
|
except Exception as e:
|
59
|
-
|
62
|
+
# Log the organizer PK as well if available
|
63
|
+
org_info = f" (Org PK: {organizer_pk})" if organizer_pk else ""
|
64
|
+
logger.exception(f"Failed to queue geocoding task for order {order.code}{org_info}: {e}")
|
60
65
|
|
61
66
|
|
62
67
|
# --- Signal Receiver for Adding Navigation Item (No changes needed) ---
|
@@ -1,8 +1,36 @@
|
|
1
|
+
.plugin-map-content-wrapper {
|
2
|
+
display: flex;
|
3
|
+
flex-direction: column;
|
4
|
+
height: calc(100vh - 200px);
|
5
|
+
min-height: 450px;
|
6
|
+
}
|
7
|
+
|
8
|
+
.plugin-map-content-wrapper h1,
|
9
|
+
.plugin-map-content-wrapper .form-group {
|
10
|
+
flex-shrink: 0;
|
11
|
+
margin-bottom: 1em;
|
12
|
+
}
|
13
|
+
|
14
|
+
.map-wrapper {
|
15
|
+
flex-grow: 1;
|
16
|
+
position: relative;
|
17
|
+
min-height: 0;
|
18
|
+
border: 1px solid #ccc;
|
19
|
+
}
|
20
|
+
|
1
21
|
#sales-map-container {
|
2
|
-
height:
|
22
|
+
height: 100%;
|
3
23
|
width: 100%;
|
4
|
-
|
5
|
-
|
24
|
+
display: block;
|
25
|
+
position: relative;
|
26
|
+
}
|
27
|
+
|
28
|
+
#map-status-overlay {
|
29
|
+
position: absolute;
|
30
|
+
top: 0;
|
31
|
+
left: 0;
|
32
|
+
width: 100%;
|
33
|
+
height: 100%;
|
6
34
|
}
|
7
35
|
|
8
36
|
#map-status-overlay p {
|
@@ -12,8 +40,13 @@
|
|
12
40
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
13
41
|
}
|
14
42
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
43
|
+
#heatmap-options-panel input[type=range] {
|
44
|
+
width: 100%;
|
45
|
+
display: block;
|
46
|
+
}
|
47
|
+
|
48
|
+
#map-status-overlay p.text-danger {
|
49
|
+
color: #a94442;
|
50
|
+
background-color: #f2dede;
|
51
|
+
border-color: #ebccd1;
|
19
52
|
}
|