subDesTagesMitExtraKaese пре 1 година
родитељ
комит
1f5fe27fc3

+ 6 - 1
locale/de/LC_MESSAGES/django.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-12-15 18:55+0000\n"
+"POT-Creation-Date: 2022-12-17 11:32+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -19,10 +19,15 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
 #: zitap/templates/zitap/create-event.html:5
+#: zitap/templates/zitap/event-not-found.html:10
 #: zitap/templates/zitap/index.html:23
 msgid "Create Event"
 msgstr ""
 
+#: zitap/templates/zitap/event-not-found.html:8
+msgid "Event not found"
+msgstr ""
+
 #: zitap/templates/zitap/index.html:16
 msgid "Home"
 msgstr ""

+ 89 - 0
zitap/helpers.py

@@ -0,0 +1,89 @@
+import datetime
+from .models import Participant, Date, Event
+
+
+def slots2string( participant : Participant, n_slots : int ) -> str :
+    """ Convert the slots of a participant to a string. """
+
+    # Get the slots of the participant
+    byte_array = participant.slots
+
+    # Convert the slots to a string
+    string_value = bin(int.from_bytes(byte_array, byteorder='big'))[2:]
+
+    # Pad the string with 0s if necessary
+    return '0' * (n_slots - len(string_value)) + string_value
+
+def string2slots( string : str ) -> bytes :
+    """ Convert a string to a byte array. """
+
+    # Convert the string to a byte array
+    return int(string, 2).to_bytes((len(string) + 7) // 8, byteorder='big')
+
+def get_slot_count( event ) -> int :
+    """ Get the number of slots in an event. """
+
+    # Get the timespan of the event
+    start_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
+    end_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
+    timespan = abs(end_time - start_time)
+
+    # Get the number of slots in the event
+    days = event.date_set.count()
+    slots_per_day = timespan.total_seconds() // event.slot_interval.total_seconds()
+    return int(days * slots_per_day)
+
+def slots2grid( event : Event ) -> dict :
+    """ Convert the slots of an event to data for the grid. """
+
+    # Get the number of slots in the event
+    n_slots = get_slot_count(event)
+
+    data = {
+        'days': [],
+        'rows': [],
+    }
+    # Get the timespan of the event
+    start_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
+    end_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
+    timespan = abs(end_time - start_time)
+    
+    # Get the slots in a day
+    slots_per_day = timespan.total_seconds() // event.slot_interval.total_seconds()
+
+    # Fill the rows of the grid
+    last_hour = None
+    for i in range(slots_per_day):
+        current_time = start_time + i * event.slot_interval
+        data['rows'].append({
+            'time': current_time,
+            'is_full_hour': current_time.hour != last_hour,
+        })
+        last_hour = current_time.hour
+
+    # Get the slots of each day
+    participants = event.participant_set.all()
+    participant_slot_strings = [slots2string(participant, n_slots) for participant in participants]
+    max_occupancy = 0
+
+    for date in event.date_set.all():
+        # Get participants for each slot
+        slot_participants = [[] for i in range(slots_per_day)]
+        for i, participant in enumerate(participants):
+            for j in range(slots_per_day):
+                if participant_slot_strings[i][j] == '1':
+                    slot_participants[j].append(participant)
+        # Fill the slots of the day
+        slots = []
+        for ps in slot_participants:
+            slots.append({
+                'tooltip': ', '.join([p.user.username for p in ps]),
+                'occupancy': len(ps),
+            })
+            max_occupancy = max(max_occupancy, len(ps))
+        
+        day = {
+            'date': date.date,
+            'slots': slots,
+        }
+        data['days'].append(day)

+ 0 - 0
zitap/templates/zitap/edit.html


+ 12 - 0
zitap/templates/zitap/event-not-found.html

@@ -0,0 +1,12 @@
+{% extends "zitap/index.html" %}
+{% load static %}
+{% load i18n %}
+
+{% block title %}Zitap{% endblock %}
+
+{% block content %}
+<h1 class="error">{% trans "Event not found" %}</h1>
+<button class="create-event" onclick="location.href='{% url 'create-event' %}'">
+  {% trans "Create Event" %}
+</button>
+{% endblock %}

+ 33 - 0
zitap/templates/zitap/event.html

@@ -0,0 +1,33 @@
+{% extends "zitap/index.html" %}
+{% load static %}
+{% load i18n %}
+
+{% block title %}{{ event.name }}{% endblock %}
+
+{% block content %}
+<h1>{{ event.name }}</h1>
+
+
+
+
+{% if update_form %}
+    <form action="{% url 'update-slots' url=event.url %}" method="post">
+        {% csrf_token %}
+        <table>
+            {{ update_form.as_table }}
+        </table>
+        <input type="submit" value="Update Slots">
+    </form>
+    <button class="logout" onclick="location.href='{% url 'logout' url=event.url %}'">
+        {% trans "Logout" %}
+    </button>
+{% else %}
+    <form action="{% url 'login' url=event.url %}" method="post">
+        {% csrf_token %}
+        <table>
+            {{ login_form.as_table }}
+        </table>
+        <input type="submit" value="Login to Update Slots">
+    </form>
+{% endif %}
+{% endblock %}

+ 5 - 0
zitap/urls.py

@@ -6,4 +6,9 @@ urlpatterns = [
     path('', views.index, name='index'),
     path('about', views.about, name='about'),
     path('create-event', views.create_event, name='create-event'),
+    path('<str:url>/login', views.login, name='login'),
+    path('<str:url>/logout', views.logout, name='logout'),
+    path('<str:url>/update-slots', views.update_slots, name='update-slots'),
+    path('<str:url>/slots', views.slots_api, name='slots-api'),
+    path('<str:url>', views.event, name='event'),
 ]

+ 119 - 4
zitap/views.py

@@ -1,8 +1,13 @@
-from django.http import HttpResponseRedirect
+import datetime
+from django.db import IntegrityError
+from django.http import HttpResponseNotFound, HttpResponseRedirect, JsonResponse, HttpResponseNotAllowed
 from django.shortcuts import render
+from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout
+from django.contrib.auth.models import User
 
-from .forms import CreateEventForm
-from .models import Event
+from .forms import CreateEventForm, LoginForm, UpdateSlotsForm
+from .models import Event, Participant, Date
+from .helpers import slots2string, string2slots, get_slot_count, slots2grid
 
 def index(request):
     return render(request, 'zitap/index.html')
@@ -23,4 +28,114 @@ def create_event(request):
             return HttpResponseRedirect(f'/{event.url}')
     else:
         form = CreateEventForm()
-    return render(request, 'zitap/create-event.html', {'form': form})
+    return render(request, 'zitap/create-event.html', {'form': form})
+
+def event(request, url):
+    try:
+        event = Event.objects.get(url=url)
+    except Event.DoesNotExist:
+        return render(request, 'zitap/event-not-found.html')
+    
+    # Check if the user is logged in
+    if 'participant_id' in request.session:
+        participant = Participant.objects.get(id=request.session['participant_id'])
+        login_form = None
+        update_form = UpdateSlotsForm(initial={'slots': participant.slots})
+    else:
+        login_form = LoginForm()
+        update_form = None
+
+    return render(
+        request, 
+        'zitap/event.html', 
+        {'event': event, 'grid': slots2grid(event), 'login_form': login_form, 'update_form': update_form}
+    )
+
+def login(request, url):
+    try:
+        event = Event.objects.get(url=url)
+    except Event.DoesNotExist:
+        return render(request, 'zitap/event-not-found.html')
+
+    if request.method != 'POST':
+        return HttpResponseNotAllowed(['POST'])
+
+    form = LoginForm(request.POST)
+    if form.is_valid():
+        data = form.cleaned_data
+        user = authenticate(request, username=data['username'], password=data.get('password'))
+        if user is None:
+            try:
+                user = User.objects.create_user(data['username'], password=data.get('password'))
+            except IntegrityError:
+                form.add_error('username', 'Username already exists')
+                return render(
+                    request, 
+                    'zitap/event.html', 
+                    {'event': event, 'grid': slots2grid(event), 'login_form': form, 'update_form': None}
+                )
+
+        auth_login(request, user)
+        participant, created = Participant.objects.get_or_create(event=event, user=user)
+        request.session['participant_id'] = participant.id
+        return HttpResponseRedirect(f'/{event.url}')
+
+def logout(request, url):
+    try:
+        event = Event.objects.get(url=url)
+    except Event.DoesNotExist:
+        return render(request, 'zitap/event-not-found.html')
+    
+    if request.method != 'POST':
+        return HttpResponseNotAllowed(['POST'])
+
+    if 'participant_id' in request.session:
+        del request.session['participant_id']
+    return HttpResponseRedirect(f'/{event.url}')
+
+def update_slots(request, url):
+    try:
+        event = Event.objects.get(url=url)
+    except Event.DoesNotExist:
+        return render(request, 'zitap/event-not-found.html')
+
+    if request.method != 'POST':
+        return HttpResponseNotAllowed(['POST'])
+
+    form = UpdateSlotsForm(request.POST)
+    if form.is_valid():
+        data = form.cleaned_data
+        participant = Participant.objects.get(id=request.session['participant_id'])
+        participant.slots = string2slots(data['slots'])
+        participant.save()
+        return HttpResponseRedirect(f'/{event.url}')
+
+def slots_api(request, url):
+    """
+    REST JSON API for slots of all participants of an event
+    Slots are represented as a string of 0s and 1s where 0 means the slot is available and 1 means the slot is taken.
+    The string begins with the first slot of the first day of the event and ends with the last slot of the last day of the event.
+    """
+    try:
+        event = Event.objects.get(url=url)
+        slot_count = get_slot_count(event)
+
+        # Check if the user is logged in and wants to update their slots
+        if 'participant_id' in request.session and request.method == 'POST':
+            participant = Participant.objects.get(id=request.session['participant_id'])
+            form = UpdateSlotsForm(request.POST)
+            if form.is_valid():
+                data = form.cleaned_data
+                participant.slots = string2slots(data['slots'])
+                participant.save()
+
+        # Get the slots of each participant
+        data = {}
+        participants = event.participant_set.all()
+        for participant in participants:
+            data[participant.name] = slots2string(participant, slot_count)
+        
+        return JsonResponse(data)
+
+    except Event.DoesNotExist:
+        return HttpResponseNotFound()