Skip to content

Commit 949e03b

Browse files
authored
Merge branch 'enext' into organizer-creation
2 parents 0052c5a + 17a16af commit 949e03b

File tree

107 files changed

+156714
-222
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+156714
-222
lines changed

app/eventyay/cfp/templates/cfp/event/invitation.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ <h2>{% translate "Accept invitation?" %}</h2>
3030
</p>
3131

3232
{% if submission.abstract %}
33-
<div class="card ml-auto mr-auto col-md-9">
33+
<div class="card ml-auto mr-auto p-0 col-md-9">
3434
<div class="card-header">{% translate "Abstract:" %} {{ submission.title }}</div>
3535
<div class="card-body mt-0 mb-0 mr-3 ml-3">
3636
<p class="card-text">{{ submission.abstract|rich_text }}</p>

app/eventyay/config/settings.py

Lines changed: 80 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from pathlib import Path
1919
from urllib.parse import urlparse
2020
import django.conf.locale
21+
from django.contrib.messages import constants as messages # NOQA
2122
from django.utils.translation import gettext_lazy as _
2223
from django.utils.crypto import get_random_string
2324
from kombu import Queue
@@ -91,16 +92,6 @@ def instance_name(request):
9192
# URL settings
9293
ROOT_URLCONF = 'eventyay.multidomain.maindomain_urlconf'
9394

94-
95-
HAS_CELERY = config.has_option('celery', 'broker')
96-
if HAS_CELERY:
97-
CELERY_BROKER_URL = config.get('celery', 'broker') if not DEBUG else 'redis://localhost:6379/2'
98-
CELERY_RESULT_BACKEND = config.get('celery', 'backend') if not DEBUG else 'redis://localhost:6379/1'
99-
CELERY_TASK_ALWAYS_EAGER = False
100-
else:
101-
CELERY_TASK_ALWAYS_EAGER = True
102-
103-
10495
AUTH_USER_MODEL = 'base.User'
10596
_LIBRARY_APPS = (
10697
'bootstrap3',
@@ -740,45 +731,80 @@ def instance_name(request):
740731
'LOCATION': config.get('memcached', 'location'),
741732
}
742733

743-
HAS_REDIS = config.has_option('redis', 'location')
744-
if HAS_REDIS:
745-
redis_options = {
746-
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
747-
'REDIS_CLIENT_KWARGS': {'health_check_interval': 30},
748-
}
749-
redis_tls_config = build_redis_tls_config(config)
750-
if redis_tls_config is not None:
751-
redis_options['CONNECTION_POOL_KWARGS'] = redis_tls_config
752-
redis_options['REDIS_CLIENT_KWARGS'].update(redis_tls_config)
753-
754-
if config.has_option('redis', 'password'):
755-
redis_options['PASSWORD'] = config.get('redis', 'password')
756-
757-
CACHES['redis'] = {
758-
'BACKEND': 'django_redis.cache.RedisCache',
759-
'LOCATION': config.get('redis', 'location') if not DEBUG else 'redis://localhost:6379/0',
760-
'OPTIONS': redis_options,
761-
}
762-
CACHES['redis_sessions'] = {
763-
'BACKEND': 'django_redis.cache.RedisCache',
764-
'LOCATION': config.get('redis', 'location') if not DEBUG else 'redis://localhost:6379/0',
765-
'TIMEOUT': 3600 * 24 * 30,
766-
'OPTIONS': redis_options,
767-
}
768-
REDIS_USE_PUBSUB = config.getboolean('redis', 'use_pubsub', fallback=True)
769-
if not HAS_MEMCACHED:
770-
CACHES['default'] = CACHES['redis']
771-
REAL_CACHE_USED = True
772-
if config.getboolean('redis', 'sessions', fallback=False):
773-
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
774-
SESSION_CACHE_ALIAS = 'redis_sessions'
734+
# Redis Configuration
735+
redis_connection_kwargs = {
736+
"retry": Retry(ExponentialBackoff(), 3),
737+
"health_check_interval": 30,
738+
}
739+
740+
REDIS_URL = config.get('redis', 'location')
741+
HAS_REDIS = bool(REDIS_URL)
742+
REDIS_HOSTS = [{
743+
"address": REDIS_URL,
744+
**redis_connection_kwargs,
745+
}]
746+
747+
REDIS_USE_PUBSUB = True
748+
749+
redis_options = {
750+
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
751+
'REDIS_CLIENT_KWARGS': {'health_check_interval': 30},
752+
}
753+
redis_tls_config = build_redis_tls_config(config)
754+
if redis_tls_config is not None:
755+
redis_options['CONNECTION_POOL_KWARGS'] = redis_tls_config
756+
redis_options['REDIS_CLIENT_KWARGS'].update(redis_tls_config)
757+
758+
if config.has_option('redis', 'password'):
759+
redis_options['PASSWORD'] = config.get('redis', 'password')
760+
761+
CACHES['redis'] = {
762+
'BACKEND': 'django_redis.cache.RedisCache',
763+
'LOCATION': REDIS_URL,
764+
'OPTIONS': redis_options,
765+
}
766+
CACHES['redis_sessions'] = {
767+
'BACKEND': 'django_redis.cache.RedisCache',
768+
'LOCATION': REDIS_URL,
769+
'TIMEOUT': 3600 * 24 * 30,
770+
'OPTIONS': redis_options,
771+
}
772+
773+
# Channels (WebSocket) configuration
774+
CHANNEL_LAYERS = {
775+
"default": {
776+
"BACKEND": (
777+
"channels_redis.pubsub.RedisPubSubChannelLayer"
778+
if REDIS_USE_PUBSUB
779+
else "channels_redis.core.RedisChannelLayer"
780+
),
781+
"CONFIG": {
782+
"hosts": REDIS_HOSTS,
783+
"prefix": "eventyay:{}:asgi:".format(
784+
_config.get("redis", "db", fallback="0")
785+
),
786+
"capacity": 10000,
787+
},
788+
},
789+
}
790+
791+
if not HAS_MEMCACHED:
792+
CACHES['default'] = CACHES['redis']
793+
REAL_CACHE_USED = True
794+
if config.getboolean('redis', 'sessions', fallback=False):
795+
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
796+
SESSION_CACHE_ALIAS = 'redis_sessions'
775797

776798
if not SESSION_ENGINE:
777799
if REAL_CACHE_USED:
778800
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
779801
else:
780802
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
781803

804+
# Celery configuration
805+
CELERY_BROKER_URL = config.get('celery', 'broker')
806+
CELERY_RESULT_BACKEND = config.get('celery', 'backend')
807+
CELERY_TASK_ALWAYS_EAGER = False if not DEBUG else True
782808
CELERY_TASK_SERIALIZER = "json"
783809
CELERY_RESULT_SERIALIZER = "json"
784810
CELERY_TASK_DEFAULT_QUEUE = "default"
@@ -791,12 +817,11 @@ def instance_name(request):
791817
)
792818
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
793819
CELERY_TASK_TRACK_STARTED = True
794-
CELERY_TASK_ROUTES = (
795-
[
796-
('eventyay.base.services.notifications.*', {'queue': 'notifications'}),
797-
('eventyay.api.webhooks.*', {'queue': 'notifications'}),
798-
],
799-
)
820+
CELERY_TASK_ROUTES = {
821+
'eventyay.base.services.notifications.*': {'queue': 'notifications'},
822+
'eventyay.api.webhooks.*': {'queue': 'notifications'},
823+
}
824+
800825

801826
# Static files (CSS, JavaScript, Images)
802827
# https://docs.djangoproject.com/en/5.1/howto/static-files/
@@ -875,7 +900,13 @@ def instance_name(request):
875900
CSP_DEFAULT_SRC = ("'self'", "'unsafe-eval'")
876901
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
877902

878-
MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"
903+
MESSAGE_TAGS = {
904+
messages.INFO: 'alert-info',
905+
messages.ERROR: 'alert-danger',
906+
messages.WARNING: 'alert-warning',
907+
messages.SUCCESS: 'alert-success',
908+
}
909+
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
879910

880911
# Template configuration
881912
template_loaders = (

app/eventyay/control/forms/filter.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,100 @@ def filter_qs(self, qs):
891891
return qs
892892

893893

894+
class AttendeeFilterForm(FilterForm):
895+
def __init__(self, *args, **kwargs):
896+
super().__init__(*args, **kwargs)
897+
self._checkin_status = None
898+
899+
query = forms.CharField(
900+
label=_('Name or email'),
901+
required=False,
902+
widget=forms.TextInput(attrs={'placeholder': _('Name or email')}),
903+
)
904+
905+
event_query = forms.CharField(
906+
label=_('Event name'),
907+
required=False,
908+
widget=forms.TextInput(attrs={'placeholder': _('Event name')}),
909+
)
910+
911+
event_status = forms.ChoiceField(
912+
label=_('Event filter'),
913+
choices=(
914+
('', _('All events')),
915+
('ongoing', _('Ongoing events')),
916+
('recent', _('Recent events')),
917+
),
918+
required=False,
919+
)
920+
921+
checkin_status = forms.ChoiceField(
922+
label=_('Check-in status'),
923+
choices=(
924+
('', _('All attendees')),
925+
('present', _('Present')),
926+
('left', _('Checked in but left')),
927+
('checked_in', _('Checked in')),
928+
('not_checked_in', _('Not checked in')),
929+
),
930+
required=False,
931+
)
932+
933+
ordering = forms.CharField(required=False, widget=forms.HiddenInput())
934+
935+
def filter_qs(self, qs):
936+
fdata = self.cleaned_data
937+
938+
# Search by attendee name or email
939+
if fdata.get('query'):
940+
qs = qs.filter(
941+
Q(attendee_name_cached__icontains=fdata['query'])
942+
| Q(order__email__icontains=fdata['query'])
943+
| Q(attendee_email__icontains=fdata['query'])
944+
)
945+
946+
# Search by event name
947+
if fdata.get('event_query'):
948+
qs = qs.filter(
949+
Q(order__event__name__icontains=fdata['event_query'])
950+
| Q(order__event__slug__icontains=fdata['event_query'])
951+
)
952+
953+
# Event status filter
954+
if fdata.get('event_status'):
955+
now_ = now()
956+
if fdata['event_status'] == 'ongoing':
957+
qs = qs.filter(
958+
Q(order__event__date_from__lte=now_)
959+
& (Q(order__event__date_to__gte=now_) | Q(order__event__date_to__isnull=True))
960+
)
961+
elif fdata['event_status'] == 'recent':
962+
qs = qs.filter(
963+
Q(order__event__date_to__lt=now_)
964+
| (Q(order__event__date_to__isnull=True) & Q(order__event__date_from__lt=now_))
965+
)
966+
967+
# Check-in status filter
968+
checkin_status = fdata.get('checkin_status')
969+
if checkin_status:
970+
qs = qs.annotate(
971+
entry_time=Max('checkins__datetime', filter=Q(checkins__type=Checkin.TYPE_ENTRY)),
972+
exit_time=Max('checkins__datetime', filter=Q(checkins__type=Checkin.TYPE_EXIT)),
973+
)
974+
if checkin_status == 'present':
975+
qs = qs.filter(entry_time__isnull=False).filter(
976+
Q(exit_time__isnull=True) | Q(exit_time__lt=F('entry_time'))
977+
)
978+
elif checkin_status == 'left':
979+
qs = qs.filter(exit_time__isnull=False, exit_time__gt=F('entry_time'))
980+
elif checkin_status == 'checked_in':
981+
qs = qs.filter(entry_time__isnull=False)
982+
elif checkin_status == 'not_checked_in':
983+
qs = qs.filter(entry_time__isnull=True)
984+
985+
return qs
986+
987+
894988
class SubmissionFilterForm(forms.Form):
895989
query = forms.CharField(
896990
label=_('Title or speaker'),

app/eventyay/control/navigation.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,12 @@ def get_admin_navigation(request):
588588
'active': 'organizers' in url.url_name,
589589
'icon': 'group',
590590
},
591+
{
592+
'label': _('All Attendees'),
593+
'url': reverse('control:admin.attendees'),
594+
'active': 'attendees' in url.url_name,
595+
'icon': 'ticket',
596+
},
591597
{
592598
'label': _('All Sessions'),
593599
'url': reverse('control:admin.submissions'),

0 commit comments

Comments
 (0)