Skip to content
Snippets Groups Projects
Commit 3e370d00 authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Merge branch '33-checkpoint-system' into 'master'

Resolve "Checkpoint system"

Closes #33

See merge request !30
parents 5e2c1f5d 5d5a1ef9
No related branches found
No related tags found
1 merge request!30Resolve "Checkpoint system"
Pipeline #76079 canceled
......@@ -501,3 +501,48 @@ class PersonGroupFormPerson(forms.Form):
widget=forms.TextInput(attrs={"autofocus": "", "autocomplete": "off"}),
help_text=_("Please enter a username."),
)
class EventCheckpointForm(forms.Form):
class Media:
js = ("https://unpkg.com/html5-qrcode", "js/paweljong/qrscanner.js", "js/paweljong/checkpoint.js")
layout = Layout(
"comment", "use_latlon",
)
comment = forms.CharField(
required=True,
label=_("Comment"),
help_text=_("Please enter a comment describing the checkpoint (e.g. Dinner)."),
)
username = forms.CharField(
required=True,
label=_("Person"),
help_text=_("Please enter a username."),
widget=forms.HiddenInput(),
)
use_latlon = forms.BooleanField(
required=False,
label=_("Submit geolocation"),
initial=True,
)
lat = forms.DecimalField(
required=False,
min_value=-90.0,
max_value=90.0,
max_digits=10,
decimal_places=8,
widget=forms.HiddenInput(),
)
lon = forms.DecimalField(
required=False,
min_value=-180.0,
max_value=180.0,
max_digits=11,
decimal_places=8,
widget=forms.HiddenInput(),
)
# Generated by Django 3.2.13 on 2022-06-24 18:31
import django.contrib.sites.managers
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sites', '0002_alter_domain_unique'),
('core', '0041_update_gender_choices'),
('paweljong', '0020_check_in'),
]
operations = [
migrations.AlterField(
model_name='registrationstate',
name='name',
field=models.CharField(max_length=255, verbose_name='Name'),
),
migrations.CreateModel(
name='Checkpoint',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('extended_data', models.JSONField(default=dict, editable=False)),
('comment', models.CharField(max_length=60, verbose_name='Comment')),
('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='Date and time of check')),
('lat', models.DecimalField(blank=True, decimal_places=8, max_digits=10, null=True, verbose_name='Latitude of check')),
('lon', models.DecimalField(blank=True, decimal_places=8, max_digits=11, null=True, verbose_name='Longitude of check')),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checkpoints', to='paweljong.event', verbose_name='Related event')),
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_checkpoints', to='core.person', verbose_name='Checked person')),
('checked_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_checkpoints_created', to='core.person', verbose_name='Checked by person')),
('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
],
options={
'abstract': False,
},
managers=[
('objects', django.contrib.sites.managers.CurrentSiteManager()),
],
),
]
......@@ -406,3 +406,16 @@ class EventRegistration(ExtensibleModel):
fields=["person", "event"], name="unique_person_registration_per_event"
)
]
class Checkpoint(ExtensibleModel):
event = models.ForeignKey(Event, verbose_name=_("Related event"), related_name="checkpoints", on_delete=models.CASCADE)
person = models.ForeignKey(Person, verbose_name=_("Checked person"), related_name="event_checkpoints", on_delete=models.CASCADE)
checked_by = models.ForeignKey(Person, verbose_name=_("Checked by person"), related_name="event_checkpoints_created", on_delete=models.CASCADE)
comment = models.CharField(max_length=60, verbose_name=_("Comment"))
timestamp = models.DateTimeField(verbose_name=_("Date and time of check"), auto_now_add=True)
lat = models.DecimalField(max_digits=10, decimal_places=8, verbose_name=_("Latitude of check"), blank=True, null=True)
lon = models.DecimalField(max_digits=11, decimal_places=8, verbose_name=_("Longitude of check"), blank=True, null=True)
......@@ -73,6 +73,10 @@ change_event_predicate = has_person & (
)
rules.add_perm("paweljong.change_event_rule", change_event_predicate)
# Checkpoint
checkpoint_predicate = change_event_predicate
rules.add_perm("paweljong.event_checkpoint_rule", checkpoint_predicate)
# View event
view_event_predicate = (
is_event_published | (has_person & is_organiser) | has_object_perm("paweljong.view_event")
......
function getCheckpointCoords() {
navigator.geolocation.getCurrentPosition(setCheckpointCoords);
}
function setCheckpointCoords(position) {
$("[name='lat']").val(position.coords.latitude);
$("[name='lon']").val(position.coords.longitude);
window.setTimeout(function() {
navigator.geolocation.getCurrentPosition(setCheckpointCoords);
}, 3000);
}
$(document).ready(function($) {
if (navigator.geolocation) {
getCheckpointCoords();
}
});
const scannerDivId = "qr-reader";
const scannerDiv = $("div#" + scannerDivId);
const scanner = new Html5Qrcode(scannerDivId);
function onScanSuccess(decodedText, decodedResult) {
let targetId = scannerDiv.data("target-input");
let target = $("[name='" + targetId + "']");
scanner.stop();
target.val(decodedText);
target.closest("form").submit();
}
$(document).ready(function($) {
let cameraConfig = { facingMode: "environment" };
let scannerConfig = { fps: 10, qrbox: {width: 250, height: 250} };
scanner.start(cameraConfig, scannerConfig, onScanSuccess);
});
{% extends "core/base.html" %}
{% load material_form i18n any_js %}
{% block page_title %}{% blocktrans %}Checkpoint{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Checkpoint{% endblocktrans %}{% endblock %}
{% block extra_head %}
{{ form.media.css }}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% form %}
{% include "core/partials/save_button.html" %}
</form>
<div id="qr-reader" data-target-input="username"></div>
{{ form.media.js }}
{% endblock %}
......@@ -15,15 +15,22 @@
<h4>{{ event }}</h4>
{% has_perm 'paweljong.change_event_rule' user event as can_change_event_rule %}
{% has_perm 'paweljong.event_checkpoint_rulw' user event as can_checkpoint_rule %}
{% if can_change_event_rule %}
<p>
{% if can_change_event_rule %}
<a href="{% url 'edit_event_by_slug' event.slug %}" class="btn waves-effect waves-light">
<i class="material-icons left iconify" data-icon="mdi:edit"></i>
{% trans "Edit" %}
</a>
{% endif %}
{% if can_checkpoint %}
<a href="{% url 'event_by_name_checkpoint' event.slug %}" class="btn waves-effect waves-light">
<i class="material-icons left iconify" data-icon="mdi:access-point-check"></i>
{% trans "Checkpoint" %}
</a>
{% endif %}
</p>
{% endif %}
<div class="card">
<div class="card-content">
......
......@@ -50,6 +50,7 @@ urlpatterns = [
),
path("event/<slug:slug>", views.EventFullView.as_view(), name="event_by_name"),
path("event/<slug:slug>/detail", views.EventDetailView.as_view(), name="event_detail_by_name"),
path("event/<slug:slug>/checkpoint", views.EventCheckpointView.as_view(), name="event_by_name_checkpoint"),
path(
"event/<slug:slug>/start",
views.RegisterEventStart.as_view(),
......
......@@ -5,10 +5,11 @@ from django.contrib.auth import get_user_model
from django.contrib.syndication.views import Feed
from django.core.exceptions import ValidationError
from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect, render
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.http import urlencode
from django.utils.text import slugify
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
......@@ -36,12 +37,13 @@ from .forms import (
EditInfoMailingForm,
EditTermForm,
EditVoucherForm,
EventCheckpointForm,
GenerateListForm,
PersonGroupFormPerson,
RegistrationNotificationForm,
RegistrationStatesForm,
)
from .models import Event, EventRegistration, InfoMailing, RegistrationState, Terms, Voucher
from .models import Checkpoint, Event, EventRegistration, InfoMailing, RegistrationState, Terms, Voucher
from .tables import (
AdditionalFieldsTable,
ChildGroupsTable,
......@@ -969,6 +971,43 @@ class PersonGroupView(PermissionRequiredMixin, FormView):
return reverse("add_persons_to_group", kwargs={"pk": self.kwargs["pk"]})
class EventCheckpointView(PermissionRequiredMixin, FormView):
template_name = "paweljong/event/checkpoint.html"
permission_required = "paweljong.can_checkpoint"
form_class = EventCheckpointForm
def get_initial(self):
initial = super().get_initial() or {}
if "comment" in self.request.GET:
initial["comment"] = self.request.GET.get("comment")
return initial
def form_valid(self, form):
checkpoint = Checkpoint()
checkpoint.event = get_object_or_404(Event, slug=self.kwargs["slug"])
try:
checkpoint.person = Person.objects.get(user__username=form.cleaned_data["username"])
except Person.DoesNotExist:
messages.error(self.request, _("The provided username is not linked to a person."))
checkpoint.checked_by = self.request.user.person
checkpoint.comment = form.cleaned_data["comment"]
checkpoint.timestamp = timezone.now()
if form.cleaned_data["use_latlon"]:
checkpoint.lat = form.cleaned_data["lat"]
checkpoint.lon = form.cleaned_data["lon"]
checkpoint.save()
messages.success(self.request, _("{} successfully checked for {}.").format(str(checkpoint.person), str(checkpoint.comment)))
self._comment = checkpoint.comment
return super().form_valid(self)
def get_success_url(self):
return reverse("event_by_name_checkpoint", kwargs={"slug": self.kwargs["slug"]}) + "?" + urlencode({"comment": self._comment})
class ViewTerms(PermissionRequiredMixin, DetailView):
context_object_name = "event"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment