Skip to content

Commit f3e3ecb

Browse files
authored
Added Lead API (#4)
* Fix Workflow * Added tests * Added Lead API * Fix Already Scanned * Modified Response * Modified the response for already scanned case to include attendee name and email * Added Option for Tags * Updated Exhibitors to have Booth with ID and Name * Modifications on the settings page * isort Fixes * Add Modification for Booth ID
1 parent fcbea7d commit f3e3ecb

21 files changed

+743
-141
lines changed

exhibitors/api.py

+231-8
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
1-
from rest_framework import viewsets, views, status
2-
from rest_framework.response import Response
31
from django.shortcuts import get_object_or_404
4-
2+
from django.utils import timezone
53
from pretix.api.serializers.i18n import I18nAwareModelSerializer
64
from pretix.api.serializers.order import CompatibleJSONField
5+
from pretix.base.models import OrderPosition
6+
from rest_framework import status, views, viewsets
7+
from rest_framework.response import Response
78

8-
from .models import ExhibitorInfo, ExhibitorItem
9+
from .models import ExhibitorInfo, ExhibitorItem,ExhibitorSettings , ExhibitorTag, Lead
910

1011

1112
class ExhibitorAuthView(views.APIView):
1213
def post(self, request, *args, **kwargs):
13-
email = request.data.get('email')
1414
key = request.data.get('key')
1515

16-
if not email or not key:
16+
if not key:
1717
return Response(
1818
{'detail': 'Missing parameters'},
1919
status=status.HTTP_400_BAD_REQUEST
2020
)
2121

2222
try:
23-
exhibitor = ExhibitorInfo.objects.get(email=email, key=key)
23+
exhibitor = ExhibitorInfo.objects.get(key=key)
2424
return Response(
25-
{'success': True, 'exhibitor_id': exhibitor.id},
25+
{
26+
'success': True,
27+
'exhibitor_id': exhibitor.id,
28+
'exhibitor_name': exhibitor.name,
29+
'booth_id': exhibitor.booth_id,
30+
'booth_name': exhibitor.booth_name,
31+
},
2632
status=status.HTTP_200_OK
2733
)
2834
except ExhibitorInfo.DoesNotExist:
@@ -65,3 +71,220 @@ class ExhibitorItemViewSet(viewsets.ReadOnlyModelViewSet):
6571

6672
def get_queryset(self):
6773
return ExhibitorItem.objects.filter(item__event=self.request.event)
74+
75+
76+
class LeadCreateView(views.APIView):
77+
def get_allowed_attendee_data(self, order_position, settings, exhibitor):
78+
"""Helper method to get allowed attendee data based on settings"""
79+
# Get all allowed fields including defaults
80+
allowed_fields = settings.all_allowed_fields
81+
print(allowed_fields)
82+
print(order_position)
83+
attendee_data = {
84+
'name': order_position.attendee_name, # Always included
85+
'email': order_position.attendee_email, # Always included
86+
'company': order_position.company if 'attendee_company' in allowed_fields else None,
87+
'city': order_position.city if 'attendee_city' in allowed_fields else None,
88+
'country': str(order_position.country) if 'attendee_country' in allowed_fields else None,
89+
'note': '',
90+
'tags': []
91+
}
92+
93+
return {k: v for k, v in attendee_data.items() if v is not None}
94+
95+
def post(self, request, *args, **kwargs):
96+
# Extract parameters from the request
97+
pseudonymization_id = request.data.get('lead')
98+
scanned = request.data.get('scanned')
99+
scan_type = request.data.get('scan_type')
100+
device_name = request.data.get('device_name')
101+
key = request.headers.get('Exhibitor')
102+
103+
if not all([pseudonymization_id, scanned, scan_type, device_name]):
104+
return Response(
105+
{'detail': 'Missing parameters'},
106+
status=status.HTTP_400_BAD_REQUEST
107+
)
108+
109+
# Authenticate the exhibitor
110+
try:
111+
exhibitor = ExhibitorInfo.objects.get(key=key)
112+
settings = ExhibitorSettings.objects.get(event=exhibitor.event)
113+
except (ExhibitorInfo.DoesNotExist, ExhibitorSettings.DoesNotExist):
114+
return Response(
115+
{'success': False, 'error': 'Invalid exhibitor key'},
116+
status=status.HTTP_401_UNAUTHORIZED
117+
)
118+
119+
# Get attendee details
120+
try:
121+
order_position = OrderPosition.objects.get(
122+
pseudonymization_id=pseudonymization_id
123+
)
124+
except OrderPosition.DoesNotExist:
125+
return Response(
126+
{'success': False, 'error': 'Attendee not found'},
127+
status=status.HTTP_404_NOT_FOUND
128+
)
129+
130+
# Check for duplicate scan
131+
if Lead.objects.filter(
132+
exhibitor=exhibitor,
133+
pseudonymization_id=pseudonymization_id
134+
).exists():
135+
attendee_data = self.get_allowed_attendee_data(
136+
order_position,
137+
settings,
138+
exhibitor
139+
)
140+
return Response(
141+
{
142+
'success': False,
143+
'error': 'Lead already scanned',
144+
'attendee': attendee_data
145+
},
146+
status=status.HTTP_409_CONFLICT
147+
)
148+
149+
# Get allowed attendee data based on settings
150+
attendee_data = self.get_allowed_attendee_data(
151+
order_position,
152+
settings,
153+
exhibitor
154+
)
155+
print(attendee_data)
156+
# Create the lead entry
157+
lead = Lead.objects.create(
158+
exhibitor=exhibitor,
159+
exhibitor_name=exhibitor.name,
160+
pseudonymization_id=pseudonymization_id,
161+
scanned=timezone.now(),
162+
scan_type=scan_type,
163+
device_name=device_name,
164+
booth_id=exhibitor.booth_id,
165+
booth_name=exhibitor.booth_name,
166+
attendee=attendee_data
167+
)
168+
169+
return Response(
170+
{
171+
'success': True,
172+
'lead_id': lead.id,
173+
'attendee': attendee_data
174+
},
175+
status=status.HTTP_201_CREATED
176+
)
177+
178+
179+
class LeadRetrieveView(views.APIView):
180+
def get(self, request, *args, **kwargs):
181+
# Authenticate the exhibitor using the key
182+
key = request.headers.get('Exhibitor')
183+
try:
184+
exhibitor = ExhibitorInfo.objects.get(key=key)
185+
except ExhibitorInfo.DoesNotExist:
186+
return Response(
187+
{
188+
'success': False,
189+
'error': 'Invalid exhibitor key'
190+
},
191+
status=status.HTTP_401_UNAUTHORIZED
192+
)
193+
194+
# Fetch all leads associated with the exhibitor
195+
leads = Lead.objects.filter(exhibitor=exhibitor).values(
196+
'id',
197+
'pseudonymization_id',
198+
'exhibitor_name',
199+
'scanned',
200+
'scan_type',
201+
'device_name',
202+
'booth_id',
203+
'booth_name',
204+
'attendee'
205+
)
206+
207+
return Response(
208+
{
209+
'success': True,
210+
'leads': list(leads)
211+
},
212+
status=status.HTTP_200_OK
213+
)
214+
215+
216+
class TagListView(views.APIView):
217+
def get(self, request, organizer, event, *args, **kwargs):
218+
key = request.headers.get('Exhibitor')
219+
try:
220+
exhibitor = ExhibitorInfo.objects.get(key=key)
221+
tags = ExhibitorTag.objects.filter(exhibitor=exhibitor)
222+
return Response({
223+
'success': True,
224+
'tags': [tag.name for tag in tags]
225+
})
226+
except ExhibitorInfo.DoesNotExist:
227+
return Response(
228+
{
229+
'success': False,
230+
'error': 'Invalid exhibitor key'
231+
},
232+
status=status.HTTP_401_UNAUTHORIZED
233+
)
234+
235+
class LeadUpdateView(views.APIView):
236+
def post(self, request, organizer, event, lead_id, *args, **kwargs):
237+
key = request.headers.get('Exhibitor')
238+
note = request.data.get('note')
239+
tags = request.data.get('tags', [])
240+
241+
try:
242+
exhibitor = ExhibitorInfo.objects.get(key=key)
243+
except ExhibitorInfo.DoesNotExist:
244+
return Response(
245+
{
246+
'success': False,
247+
'error': 'Invalid exhibitor key'
248+
},
249+
status=status.HTTP_401_UNAUTHORIZED
250+
)
251+
252+
try:
253+
lead = Lead.objects.get(pseudonymization_id=lead_id, exhibitor=exhibitor)
254+
except Lead.DoesNotExist:
255+
return Response(
256+
{
257+
'success': False,
258+
'error': 'Lead not found'
259+
},
260+
status=status.HTTP_404_NOT_FOUND
261+
)
262+
263+
# Update lead's attendee info
264+
attendee_data = lead.attendee or {}
265+
if note is not None:
266+
attendee_data['note'] = note
267+
if tags is not None:
268+
attendee_data['tags'] = tags
269+
270+
# Update tag usage counts and create new tags
271+
for tag_name in tags:
272+
tag, created = ExhibitorTag.objects.get_or_create(
273+
exhibitor=exhibitor,
274+
name=tag_name
275+
)
276+
if not created:
277+
tag.use_count += 1
278+
tag.save()
279+
280+
lead.attendee = attendee_data
281+
lead.save()
282+
283+
return Response(
284+
{
285+
'success': True,
286+
'lead_id': lead.id,
287+
'attendee': lead.attendee
288+
},
289+
status=status.HTTP_200_OK
290+
)

exhibitors/forms.py

+19-36
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,32 @@
11
from django import forms
22
from django.utils.translation import gettext, gettext_lazy as _
3-
43
from pretix.base.forms import SettingsForm
5-
from .models import ExhibitorInfo
64

5+
from .models import ExhibitorInfo
76

8-
class ExhibitorSettingForm(SettingsForm):
9-
exhibitor_url = forms.URLField(
10-
label=_("Exhibitor URL"),
11-
required=False,
12-
)
13-
14-
exhibitor_name = forms.CharField(
15-
label=_("Exhibitor Name"),
16-
required=True,
17-
)
18-
19-
exhibitor_description = forms.CharField(
20-
label=_("Exhibitor Description"),
21-
required=False,
22-
)
237

24-
exhibitor_logo = forms.ImageField(
25-
label=_("Exhibitor Logo"),
26-
required=False,
27-
)
28-
lead_scanning_enabled = forms.BooleanField(
29-
label=_("Lead Scanning Enabled"),
30-
required=False,
31-
initial=True,
8+
class ExhibitorInfoForm(forms.ModelForm):
9+
allow_voucher_access = forms.BooleanField(required=False)
10+
allow_lead_access = forms.BooleanField(required=False)
11+
lead_scanning_scope_by_device = forms.BooleanField(required=False)
12+
comment = forms.CharField(
13+
widget=forms.Textarea(attrs={'rows': 10}),
14+
required=False
3215
)
16+
booth_id = forms.CharField(required=False)
3317

34-
def __init__(self, *args, **kwargs):
35-
self.obj = kwargs.get('obj')
36-
super().__init__(*args, **kwargs)
37-
38-
def clean(self):
39-
data = super().clean()
40-
return data
41-
42-
43-
class ExhibitorInfoForm(forms.ModelForm):
4418
class Meta:
4519
model = ExhibitorInfo
46-
fields = ['name', 'description', 'url', 'email', 'logo']
20+
fields = [
21+
'name',
22+
'description',
23+
'url',
24+
'email',
25+
'logo',
26+
'booth_id',
27+
'booth_name',
28+
'lead_scanning_enabled'
29+
]
4730
widgets = {
4831
'description': forms.Textarea(attrs={'rows': 4}),
4932
}

exhibitors/migrations/0001_initial.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Generated by Django 4.2.14 on 2024-09-02 09:37
22

3-
from django.db import migrations, models
43
import django.db.models.deletion
4+
from django.db import migrations, models
5+
56
import exhibitors.models
67

78

exhibitors/migrations/0002_alter_exhibitorinfo_lead_scanning_enabled_and_more.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Generated by Django 4.2.14 on 2024-09-16 05:42
22

3-
from django.db import migrations, models
43
import django.db.models.deletion
4+
from django.db import migrations, models
55

66

77
class Migration(migrations.Migration):

exhibitors/migrations/0003_lead.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 4.2.14 on 2024-10-14 10:02
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('exhibitors', '0002_alter_exhibitorinfo_lead_scanning_enabled_and_more'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='Lead',
16+
fields=[
17+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
18+
('pseudonymization_id', models.CharField(max_length=190)),
19+
('scanned', models.DateTimeField()),
20+
('scan_type', models.CharField(max_length=50)),
21+
('device_name', models.CharField(max_length=50)),
22+
('attendee', models.JSONField(null=True)),
23+
('exhibitor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exhibitors.exhibitorinfo')),
24+
],
25+
),
26+
]

0 commit comments

Comments
 (0)