Skip to content

Commit

Permalink
Eventyay Common: Create an event dashboard (#484)
Browse files Browse the repository at this point in the history
* Add dashboard eventyay common

* Add custom css

* Change background

* Add component link for eventyay_common

* Update css style

* Fix isort

* Add custom style

* Add alert pop up, add line break

* Resolve conversation: update template url

* Refactor code eventyay_common

* Refactor context and navigation

* Add missing url return
  • Loading branch information
HungNgien authored Jan 9, 2025
1 parent c824b0d commit 3ddbd09
Show file tree
Hide file tree
Showing 14 changed files with 752 additions and 86 deletions.
21 changes: 15 additions & 6 deletions src/pretix/base/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
from ..settings import settings_hierarkey
from .organizer import Organizer, OrganizerBillingModel, Team

TALK_HOSTNAME = settings.TALK_HOSTNAME


class EventMixin:

Expand Down Expand Up @@ -1134,20 +1136,27 @@ def has_paid_things(self):

@property
def talk_schedule_url(self):
talk_host = settings.TALK_HOSTNAME
url = urljoin(talk_host, f"{self.slug}/schedule")
url = urljoin(TALK_HOSTNAME, f"{self.slug}/schedule")
return url

@property
def talk_session_url(self):
talk_host = settings.TALK_HOSTNAME
url = urljoin(talk_host, f"{self.slug}/talk")
url = urljoin(TALK_HOSTNAME, f"{self.slug}/talk")
return url

@property
def talk_speaker_url(self):
talk_host = settings.TALK_HOSTNAME
url = urljoin(talk_host, f"{self.slug}/speaker")
url = urljoin(TALK_HOSTNAME, f"{self.slug}/speaker")
return url

@property
def talk_dashboard_url(self):
url = urljoin(TALK_HOSTNAME, f"/orga/event/{self.slug}")
return url

@property
def talk_settings_url(self):
url = urljoin(TALK_HOSTNAME, f"/orga/event/{self.slug}/settings")
return url

@cached_property
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{% load i18n %}
<div class="navigation-button">
{% if request.organizer and request.event %}
<a href='{% url "eventyay_common:event.update" organizer=request.organizer.slug event=request.event.slug %}' class="header-nav btn btn-outline-success">
<a href='{% url "eventyay_common:event.index" organizer=request.organizer.slug event=request.event.slug %}' class="header-nav btn btn-outline-success">
<i class="fa fa-home"></i> {% trans "Home" %}
</a>
<a href="#" class="header-nav btn btn-outline-success active">
<i class="fa fa-ticket"></i> {% trans "Tickets" %}
</a>
{% if is_talk_event_created %}
<a href="{{ talk_edit_url }}" class="header-nav btn btn-outline-success">
<a href="{{ request.event.talk_dashboard_url }}" class="header-nav btn btn-outline-success">
<i class="fa fa-group"></i> {% trans "Talk" %}
</a>
{% endif %}
Expand Down
160 changes: 91 additions & 69 deletions src/pretix/eventyay_common/context.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,120 @@
from importlib import import_module
import logging
from urllib.parse import urljoin

from django.conf import settings
from django.db.models import Q
from django.urls import Resolver404, get_script_prefix, resolve, reverse
from django.utils.translation import gettext_lazy as _
from django.http import HttpRequest
from django.urls import Resolver404, get_script_prefix, resolve
from django_scopes import scope

from pretix.base.models.auth import StaffSession
from pretix.base.settings import GlobalSettingsObject
from pretix.control.navigation import merge_in
from pretix.control.signals import nav_global
from pretix.eventyay_common.navigation import (
get_event_navigation, get_global_navigation,
)

SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
from ..helpers.plugin_enable import is_video_enabled
from ..multidomain.urlreverse import get_event_domain
from .views.event import EventCreatedFor

logger = logging.getLogger(__name__)

def contextprocessor(request):
"""
Adds data to all template contexts
"""
if not hasattr(request, '_eventyay_common_default_context'):

def contextprocessor(request: HttpRequest):
if not hasattr(request, "_eventyay_common_default_context"):
request._eventyay_common_default_context = _default_context(request)
return request._eventyay_common_default_context


def _default_context(request):
def _default_context(request: HttpRequest):
try:
url = resolve(request.path_info)
except Resolver404:
return {}

if not request.path.startswith(get_script_prefix() + 'common'):
if not request.path.startswith(f"{get_script_prefix()}common"):
return {}
ctx = {
'url_name': url.url_name,
'settings': settings,
'django_settings': settings,
'DEBUG': settings.DEBUG,
"url_name": url.url_name,
"settings": settings,
"django_settings": settings,
"DEBUG": settings.DEBUG,
"talk_hostname": settings.TALK_HOSTNAME,
}
if getattr(request, 'event', None) and hasattr(request, 'organizer') and request.user.is_authenticated:
ctx['nav_items'] = get_global_navigation(request)

elif request.user.is_authenticated:
ctx['nav_items'] = get_global_navigation(request)

gs = GlobalSettingsObject()
ctx['global_settings'] = gs.settings
ctx["global_settings"] = gs.settings

if request.user.is_authenticated:
ctx['staff_session'] = request.user.has_active_staff_session(request.session.session_key)
ctx['staff_need_to_explain'] = (
StaffSession.objects.filter(user=request.user, date_end__isnull=False).filter(
Q(comment__isnull=True) | Q(comment="")
)
if request.user.is_staff and settings.PRETIX_ADMIN_AUDIT_COMMENTS else StaffSession.objects.none()
ctx["nav_items"] = get_global_navigation(request)

ctx["staff_session"] = request.user.has_active_staff_session(
request.session.session_key
)
ctx["staff_need_to_explain"] = (
StaffSession.objects.filter(
user=request.user, date_end__isnull=False
).filter(Q(comment__isnull=True) | Q(comment=""))
if request.user.is_staff and settings.PRETIX_ADMIN_AUDIT_COMMENTS
else StaffSession.objects.none()
)

ctx['talk_hostname'] = settings.TALK_HOSTNAME
if event := getattr(request, "event", None):
ctx["talk_edit_url"] = urljoin(
settings.TALK_HOSTNAME, f"/orga/event/{request.event.slug}"
)
ctx["is_video_enabled"] = is_video_enabled(event)
ctx["is_talk_event_created"] = False
if (
request.event.settings.create_for == EventCreatedFor.BOTH.value
or request.event.settings.talk_schedule_public is not None
):
ctx["is_talk_event_created"] = True

if organizer := getattr(request, "organizer", None):
ctx["nav_items"] = get_event_navigation(request)
ctx["has_domain"] = (
get_event_domain(request.event, fallback=True) is not None
)
if not request.event.testmode:
with scope(organizer=organizer):
complain_testmode_orders = request.event.cache.get(
"complain_testmode_orders"
)
if complain_testmode_orders is None:
complain_testmode_orders = request.event.orders.filter(
testmode=True
).exists()
request.event.cache.set(
"complain_testmode_orders", complain_testmode_orders, 30
)
ctx["complain_testmode_orders"] = (
complain_testmode_orders
and request.user.has_event_permission(
organizer, request.event, "can_view_orders", request=request
)
)
else:
ctx["complain_testmode_orders"] = False

if not request.event.live and ctx["has_domain"]:
child_sess_key = f"child_session_{request.event.pk}"
child_sess = request.session.get(child_sess_key)

if not child_sess:
request.session[child_sess_key] = request.session.session_key
else:
ctx["new_session"] = child_sess
request.session["event_access"] = True
if request.GET.get("subevent", ""):
subevent_id = request.GET.get("subevent", "").strip()
try:
pk = int(subevent_id)
# Do not use .get() for lazy evaluation
ctx["selected_subevents"] = request.event.subevents.filter(
pk=pk
)
except ValueError as e:
logger.error("Error parsing subevent ID: %s", e)

return ctx


def get_global_navigation(request):
url = request.resolver_match
if not url:
return []
nav = [
{
'label': _('Dashboard'),
'url': reverse('eventyay_common:dashboard'),
'active': (url.url_name == 'dashboard'),
'icon': 'dashboard',
},
{
'label': _('My Events'),
'url': reverse('eventyay_common:events'),
'active': 'events' in url.url_name,
'icon': 'calendar',
},
{
'label': _('Organizers'),
'url': reverse('eventyay_common:organizers'),
'active': 'organizers' in url.url_name,
'icon': 'group',
},
{
'label': _('Account'),
'url': reverse('eventyay_common:account'),
'active': 'account' in url.url_name,
'icon': 'user',
}

]

merge_in(nav, sorted(
sum((list(a[1]) for a in nav_global.send(request, request=request)), []),
key=lambda r: (1 if r.get('parent') else 0, r['label'])
))
return nav
104 changes: 104 additions & 0 deletions src/pretix/eventyay_common/navigation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from typing import Any, Dict, List

from django.http import HttpRequest
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from pretix.control.navigation import merge_in
from pretix.control.signals import nav_event, nav_global


def get_global_navigation(request: HttpRequest) -> List[Dict[str, Any]]:
"""Generate navigation items for global."""
url = request.resolver_match
if not url:
return []
nav = [
{
"label": _("Dashboard"),
"url": reverse("eventyay_common:dashboard"),
"active": (url.url_name == "dashboard"),
"icon": "dashboard",
},
{
"label": _("My Events"),
"url": reverse("eventyay_common:events"),
"active": "events" in url.url_name,
"icon": "calendar",
},
{
"label": _("Organizers"),
"url": reverse("eventyay_common:organizers"),
"active": "organizers" in url.url_name,
"icon": "group",
},
{
"label": _("Account"),
"url": reverse("eventyay_common:account"),
"active": "account" in url.url_name,
"icon": "user",
},
]

# Merge plugin-provided navigation items
plugin_nav_items = sum(
(list(a[1]) for a in nav_global.send(request, request=request)), []
)

# Sort navigation items, prioritizing non-parent items and alphabetically
sorted_plugin_items = sorted(
plugin_nav_items, key=lambda r: (1 if r.get("parent") else 0, r["label"])
)

# Merge plugin items into default navigation
merge_in(nav, sorted_plugin_items)

return nav


def get_event_navigation(request: HttpRequest) -> List[Dict[str, Any]]:
"""Generate navigation items for an event."""
url = request.resolver_match
if not url:
return []
nav = [
{
"label": _("Dashboard"),
"url": reverse(
"eventyay_common:event.index",
kwargs={
"event": request.event.slug,
"organizer": request.event.organizer.slug,
},
),
"active": (url.url_name == "event.index"),
"icon": "dashboard",
},
{
"label": _("Settings"),
"url": reverse(
"eventyay_common:event.update",
kwargs={
"event": request.event.slug,
"organizer": request.event.organizer.slug,
},
),
"active": (url.url_name == "event.update"),
"icon": "wrench",
},
]

# Merge plugin-provided navigation items
plugin_nav_items = sum(
(list(a[1]) for a in nav_event.send(request.event, request=request)), []
)

# Sort navigation items, prioritizing non-parent items and alphabetically
sorted_plugin_items = sorted(
plugin_nav_items, key=lambda r: (1 if r.get("parent") else 0, r["label"])
)

# Merge plugin items into default navigation
merge_in(nav, sorted_plugin_items)

return nav
37 changes: 37 additions & 0 deletions src/pretix/eventyay_common/templates/eventyay_common/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,43 @@
</ul>
<div class="navbar-default sidebar" role="navigation">
<div class="sidebar-nav navbar-nav-collapse navbar-collapse">
<div class="dropdown context-selector">
{% block nav_top_header %}
{% if request.event %}
<a href="#" class="dropdown-toggle">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-calendar fa-stack-1x fa-inverse"></i>
</span>
<div class="context-indicator">
<span class="context-name">{{ request.event }}</span>
<span class="context-meta">{{ request.event.get_date_range_display }}</span>
</div>
</a>
{% elif request.organizer %}
<a href="#" class="dropdown-toggle">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-group fa-stack-1x fa-inverse"></i>
</span>
<div class="context-indicator">
<span class="context-name">{{ request.organizer }}</span>
<span class="context-meta">{% trans "Organizer account" %}</span>
</div>
</a>
{% else %}
<a href="#" class="dropdown-toggle">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-user fa-stack-1x fa-inverse"></i>
</span>
<div class="context-indicator">
<span class="context-name">{{ request.user }}</span>
</div>
</a>
{% endif %}
{% endblock %}
</div>
<ul class="nav" id="side-menu">
{% block nav %}
{% for nav in nav_items %}
Expand Down
Loading

0 comments on commit 3ddbd09

Please sign in to comment.