diff --git a/README.md b/README.md index e184e71..e18f503 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ http://0.0.0.0:8234/admin/ # TODO - [x] casy (id_timeru, cas, id_karty) -- [ ] laps (star, end, id_karty) +- [x] laps (star, end, id_karty) - [ ] bar zaznamy (id_karty, polozka, cas) - [x] doplnit mysql kontajner a zmenit connetor v settings.py - [ ] doplnit phpmyadmin kontajner @@ -61,4 +61,5 @@ http://0.0.0.0:8234/admin/ - [x] nginx container - [x] bezpecnostne kody pre email pre pristup k registraciam - [x] zoznam registracii pre email zabezpeceny bezpecnostnym kodom -- [x] moznost priamej registracie bez mailoveho potvrdenia \ No newline at end of file +- [x] moznost priamej registracie bez mailoveho potvrdenia +- [x] hromadne akcie v adminovi na registraciach \ No newline at end of file diff --git a/src/events/admin.py b/src/events/admin.py index 9c10cc1..4bf37ab 100644 --- a/src/events/admin.py +++ b/src/events/admin.py @@ -1,4 +1,4 @@ -from django.contrib import admin +from django.contrib import admin, messages from events import models @@ -13,11 +13,43 @@ class EventAdmin(admin.ModelAdmin): list_display = ('start', 'name', 'is_actual', 'is_public', 'registrations_enabled') +def presentation(modeladmin, request, queryset): + for registration in queryset: + registration.is_presented = True + + try: + registration.save() + except Exception as e: + messages.error(request, f"Registraciu {registration} sa nepodarilo zaprezentovat: {e}") +presentation.short_description = "Zaprezentovat registraciu" + +def pay(modeladmin, request, queryset): + for registration in queryset: + registration.is_paid = True + + try: + registration.save() + except Exception as e: + messages.error(request, f"Registraciu {registration} sa nepodarilo uhradit: {e}") +pay.short_description = "Oznacit registraciu ako uhradenu" + +def finish(modeladmin, request, queryset): + for registration in queryset: + registration.is_finished = True + + try: + registration.save() + except Exception as e: + messages.error(request, f"Registraciu {registration} sa nepodarilo dokoncit: {e}") +finish.short_description = "Dokoncit registraciu" + + @admin.register(models.Registration) class RegistrationAdmin(admin.ModelAdmin): list_display = ('created', 'variable_symbol', 'event', 'email', 'first_name', 'last_name', 'is_confirmed' ,'is_paid', 'is_finished') - list_filter = ('event', 'is_confirmed', 'is_paid', 'is_finished', 'email') + list_filter = ('event', 'category', 'is_confirmed', 'is_paid', 'is_finished', 'email') search_fields = ('email', 'first_name', 'last_name', 'variable_symbol') + actions = [presentation, pay, finish] @admin.register(models.HTMLSection) diff --git a/src/events/migrations/0003_alter_registration_event.py b/src/events/migrations/0003_alter_registration_event.py new file mode 100644 index 0000000..854c609 --- /dev/null +++ b/src/events/migrations/0003_alter_registration_event.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.6 on 2025-03-06 18:51 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0002_category_mapy_cz_url'), + ] + + operations = [ + migrations.AlterField( + model_name='registration', + name='event', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='registrations', to='events.event', verbose_name='Event'), + ), + ] diff --git a/src/events/models.py b/src/events/models.py index 4f63925..06eaf24 100644 --- a/src/events/models.py +++ b/src/events/models.py @@ -118,7 +118,8 @@ class Category(models.Model): class Registration(TimeStampedModel): uid = models.UUIDField(blank=True, null=True, verbose_name=_('UUID')) - event = models.ForeignKey(Event, null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_('Event')) + event = models.ForeignKey(Event, null=True, blank=True, on_delete=models.SET_NULL, + related_name='registrations', verbose_name=_('Event')) variable_symbol = models.CharField(max_length=10, blank=True, null=True, verbose_name=_('Variable symbol')) # data from form @@ -196,7 +197,8 @@ class Lap(TimeStampedModel): :return: """ if self.start and self.end: - return self.end - self.start + return (self.end - self.start) + return timedelta(0) diff --git a/src/events/urls.py b/src/events/urls.py index 3fcdabb..b4330e4 100644 --- a/src/events/urls.py +++ b/src/events/urls.py @@ -26,6 +26,7 @@ urlpatterns = [ "my-registrations////", MyRegistrationsListView.as_view(), name="my_registrations_list", - ) + ), - ] \ No newline at end of file + +] \ No newline at end of file diff --git a/src/events/views.py b/src/events/views.py index 305d8b1..772e932 100644 --- a/src/events/views.py +++ b/src/events/views.py @@ -6,6 +6,7 @@ from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views.generic import RedirectView, TemplateView from django.shortcuts import get_object_or_404 +from django.contrib.auth.mixins import LoginRequiredMixin from core.mailer import Emailer from core.utils import generate_random_string diff --git a/src/templates/layout/landing_footer.html b/src/templates/layout/landing_footer.html index 995cf26..da92817 100644 --- a/src/templates/layout/landing_footer.html +++ b/src/templates/layout/landing_footer.html @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/src/templates/layout/landing_page.html b/src/templates/layout/landing_page.html index a012529..a4e6122 100644 --- a/src/templates/layout/landing_page.html +++ b/src/templates/layout/landing_page.html @@ -4,6 +4,7 @@ {% block content %} {% include 'menu/landing_page_menu.html' %} + {% include 'partials/messages.html' %} {% block landing_content %}{% endblock %} {% endblock %} diff --git a/src/templates/layout/master.html b/src/templates/layout/master.html index b08faee..21e00af 100644 --- a/src/templates/layout/master.html +++ b/src/templates/layout/master.html @@ -1,24 +1,23 @@ {% load static %} - - Kalské Borec //25 - - - - - + + Kalské Borec //25 + + + + + - - - - + + + + - {% block extra_head %}{% endblock %} - - -{% include 'partials/messages.html' %} -{% block content %}{% endblock %} -{% block js %}{% endblock %} - + {% block extra_head %}{% endblock %} + + + {% block content %}{% endblock %} + {% block js %}{% endblock %} + \ No newline at end of file diff --git a/src/timer/admin.py b/src/timer/admin.py index 38462d5..7833e5f 100644 --- a/src/timer/admin.py +++ b/src/timer/admin.py @@ -5,7 +5,7 @@ from timer import models @admin.register(models.TimeRecord) class TimeRecordAdmin(admin.ModelAdmin): - list_display = ('timer_id', 'card_id', 'time') - search_fields = ('timer_id', 'card_id') - list_filter = ('timer_id', 'card_id') - list_display_links = ('timer_id', 'card_id') + list_display = ('timer_id', 'chip_id', 'time') + search_fields = ('timer_id', 'chip_id') + list_filter = ('timer_id', 'chip_id') + list_display_links = ('timer_id', 'chip_id') diff --git a/src/timer/migrations/0002_remove_timerecord_card_id_timerecord_chip_id.py b/src/timer/migrations/0002_remove_timerecord_card_id_timerecord_chip_id.py new file mode 100644 index 0000000..cdaf836 --- /dev/null +++ b/src/timer/migrations/0002_remove_timerecord_card_id_timerecord_chip_id.py @@ -0,0 +1,22 @@ +# Generated by Django 5.1.6 on 2025-03-06 18:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('timer', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='timerecord', + name='card_id', + ), + migrations.AddField( + model_name='timerecord', + name='chip_id', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Chip identifier'), + ), + ] diff --git a/src/timer/models.py b/src/timer/models.py index 0cc6dc3..b0ac6ec 100644 --- a/src/timer/models.py +++ b/src/timer/models.py @@ -15,7 +15,7 @@ class TimeRecord(TimeStampedModel): TimeRecord database model holding the time records from the timer. """ timer_id = models.CharField(max_length=20, null=True, blank=True, verbose_name=_('Timer identifier')) - card_id = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Card identifier')) + chip_id = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Chip identifier')) time = models.DateTimeField(verbose_name=_('Time')) class Meta: @@ -23,4 +23,4 @@ class TimeRecord(TimeStampedModel): verbose_name_plural = _('Time Records') def __str__(self): - return f'{self.timer_id} - {self.card_id}' + return f'{self.timer_id} - {self.chip_id}' diff --git a/src/timer/urls.py b/src/timer/urls.py index afbc28d..ad4a235 100644 --- a/src/timer/urls.py +++ b/src/timer/urls.py @@ -4,7 +4,7 @@ from timer.views import WriteTimeApiView urlpatterns = [ path( - "write////", + "write////", WriteTimeApiView.as_view(), name="write_time", ), diff --git a/src/timer/views.py b/src/timer/views.py index 7982cac..eecd8e4 100644 --- a/src/timer/views.py +++ b/src/timer/views.py @@ -5,7 +5,7 @@ from datetime import datetime from django.views.generic import View from django.http import JsonResponse -from events.models import Event +from events.models import Event, Lap logger = logging.getLogger(__name__) @@ -27,39 +27,47 @@ class WriteTimeApiView(View): # create new TimeRecord object and save it to the database TimeRecord( - card_id=kwargs['card_id'], + chip_id=kwargs['chip_id'], timer_id=kwargs['timer_id'], time=record_time, ).save() # find the active event event = Event.objects.filter( - is_active=True, + is_actual=True, ) # if there is no active event, log error if not event: logger.error("No active event found") - return JsonResponse({"error": "No active event found"}, status=http.HTTPStatus.NOT_FOUND, safe=False) + return JsonResponse({"status": "ok"}, status=http.HTTPStatus.OK, safe=False) # find registration with the given card_id registration = event[0].registrations.filter( - card_id=kwargs['card_id'], + chip_id=kwargs['chip_id'], ) + if not registration: - logger.error(f"No registration found for card_id: {kwargs['card_id']}") - return JsonResponse({"status": "ok"}, status=http.HTTPStatus.OK ,safe=False) + logger.error(f"No registration found for chip_id: {kwargs['chip_id']}") + return JsonResponse({"status": "ok"}, status=http.HTTPStatus.OK, safe=False) # if there is more than one registration with the same card_id, log error if len(registration) > 1: - logger.error(f"Multiple registrations found for card_id: {kwargs['card_id']}") + logger.error(f"Multiple registrations found for chip_id: {kwargs['chip_id']}") # create new lap or update existing lap - registration[0].laps.filter(end = None).update(end = record_time) - - + laps = Lap.objects.filter(registration=registration[0]) + open_lap = laps.filter(end=None) + if not open_lap: + registration[0].laps.create( + start=record_time, + number=len(laps) + 1, + ) + else: + open_lap[0].end = record_time + open_lap[0].save() return JsonResponse({"status": "ok"}, status=http.HTTPStatus.OK ,safe=False) except Exception as e: - logger.error(f"Error while getting prometheus data: {e}") - return JsonResponse({"error": f"Error while getting prometheus data: {e}"}, status=http.HTTPStatus.INTERNAL_SERVER_ERROR , safe=False) \ No newline at end of file + logger.error(f"Error while writing lap: {e}") + return JsonResponse({"status": "ok"}, status=http.HTTPStatus.OK, safe=False) \ No newline at end of file