Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • konstantin/akplanning
  • matedealer/akplanning
  • kif/akplanning
  • mirco/akplanning
  • lordofthevoid/akplanning
  • voidptr/akplanning
  • xayomer/akplanning-fork
  • mollux/akplanning
  • neumantm/akplanning
  • mmarx/akplanning
  • nerf/akplanning
  • felix_bonn/akplanning
  • sebastian.uschmann/akplanning
13 results
Show changes
Commits on Source (15)
Showing with 201 additions and 16 deletions
# Generated by Django 3.1.8 on 2022-05-12 16:57
from django.db import migrations, models
import django.db.models.deletion
def forwards_func(apps, schema_editor):
# Set event to the corresponding even (from the AK) each
AKOrgaMessage = apps.get_model("AKModel", "AKOrgaMessage")
for message in AKOrgaMessage.objects.all():
message.event = message.ak.event
message.save()
def reverse_func(apps, schema_editor):
# No need to do something here, field will be deleted anyway
pass
class Migration(migrations.Migration):
dependencies = [
('AKModel', '0049_interest_window'),
]
operations = [
migrations.AddField(
model_name='akorgamessage',
name='event',
field=models.ForeignKey(blank=True, help_text='Associated event', null=True,
on_delete=django.db.models.deletion.CASCADE, to='AKModel.event',
verbose_name='Event'),
),
migrations.RunPython(forwards_func, reverse_func),
migrations.AlterField(
model_name='akorgamessage',
name='event',
field=models.ForeignKey(help_text='Associated event', on_delete=django.db.models.deletion.CASCADE,
to='AKModel.event', verbose_name='Event'),
),
]
......@@ -2,6 +2,7 @@ import itertools
from datetime import timedelta
from django.db import models
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.datetime_safe import datetime
from django.utils.text import slugify
......@@ -443,6 +444,8 @@ class AKOrgaMessage(models.Model):
text = models.TextField(verbose_name=_("Message text"),
help_text=_("Message to the organizers. This is not publicly visible."))
timestamp = models.DateTimeField(auto_now_add=True)
event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'),
help_text=_('Associated event'))
class Meta:
verbose_name = _('AK Orga Message')
......@@ -527,7 +530,7 @@ class ConstraintViolation(models.Model):
# Stringify all other fields
for field in self.fields:
a = getattr(self, field, None)
if a is not None:
if a is not None and str(a) != '':
output.append(f"{field}: {a}")
return ", ".join(output)
......@@ -537,6 +540,10 @@ class ConstraintViolation(models.Model):
def details(self):
return self.get_details()
@property
def edit_url(self):
return reverse_lazy('admin:AKModel_constraintviolation_change', kwargs={'object_id': self.pk})
@property
def level_display(self):
return self.get_level_display()
......
......@@ -25,7 +25,8 @@ class EventSlugMixin:
def _load_event(self):
# Find event based on event slug
self.event = get_object_or_404(Event, slug=self.kwargs.get("event_slug", None))
if self.event is None:
self.event = get_object_or_404(Event, slug=self.kwargs.get("event_slug", None))
def get(self, request, *args, **kwargs):
self._load_event()
......
......@@ -81,7 +81,7 @@
{% block content %}
<div class="float-right">
<ul class="nav nav-pills">
{% if event.room_set.count > 0 %}
{% if rooms|length > 0 %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button"
aria-haspopup="true"
......@@ -94,13 +94,13 @@
</div>
</li>
{% endif %}
{% if event.aktrack_set.count > 0 %}
{% if tracks|length > 0 %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button"
aria-haspopup="true"
aria-expanded="false">{% trans "Tracks" %}</a>
<div class="dropdown-menu">
{% for t in event.aktrack_set.all %}
{% for t in tracks %}
<a class="dropdown-item"
href="{% url "plan:plan_track" event_slug=event.slug pk=t.pk %}">{{ t }}</a>
{% endfor %}
......
......@@ -16,7 +16,7 @@
{% block encode %}
[
{% for slot in room.akslot_set.all %}
{% for slot in slots %}
{% if slot.start %}
{'title': '{{ slot.ak }}',
'start': '{{ slot.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
......
......@@ -18,7 +18,7 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
def get_queryset(self):
# Ignore slots not scheduled yet
return super().get_queryset().filter(start__isnull=False)
return super().get_queryset().filter(start__isnull=False).select_related('ak', 'room', 'ak__category')
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
......@@ -54,6 +54,8 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
if settings.PLAN_SHOW_HIERARCHY:
context["buildings"] = sorted(buildings)
context["tracks"] = self.event.aktrack_set.all()
return context
......@@ -94,6 +96,11 @@ class PlanRoomView(FilterByEventSlugMixin, DetailView):
model = Room
context_object_name = "room"
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context["slots"] = AKSlot.objects.filter(room=context['room']).select_related('ak', 'ak__category', 'ak__track')
return context
class PlanTrackView(FilterByEventSlugMixin, DetailView):
template_name = "AKPlan/plan_track.html"
......@@ -102,9 +109,5 @@ class PlanTrackView(FilterByEventSlugMixin, DetailView):
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context["slots"] = []
for ak in context["track"].ak_set.all():
context["slots"].extend(ak.akslot_set.all())
context["slots"] = AKSlot.objects.filter(event=self.event, ak__track=context['track']).select_related('ak', 'room', 'ak__category')
return context
......@@ -218,4 +218,8 @@ CSP_IMG_SRC = ("'self'", "data:")
CSP_FRAME_SRC = ("'self'", )
CSP_FONT_SRC = ("'self'", "data:", "fonts.gstatic.com")
# Emails
SEND_MAILS = True
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
include(optional("settings/*.py"))
......@@ -35,4 +35,8 @@ DATABASES = {
}
}
### EMAILS ###
SEND_MAILS = True
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# TODO: caching
......@@ -114,7 +114,7 @@ class EventsViewSet(EventSlugMixin, viewsets.ModelViewSet):
class ConstraintViolationSerializer(serializers.ModelSerializer):
class Meta:
model = ConstraintViolation
fields = ['pk', 'type_display', 'aks', 'ak_slots', 'ak_owner', 'room', 'requirement', 'category', 'comment', 'timestamp_display', 'manually_resolved', 'level_display', 'details']
fields = ['pk', 'type_display', 'aks', 'ak_slots', 'ak_owner', 'room', 'requirement', 'category', 'comment', 'timestamp_display', 'manually_resolved', 'level_display', 'details', 'edit_url']
class ConstraintViolationsViewSet(EventSlugMixin, viewsets.ModelViewSet):
......
......@@ -63,7 +63,7 @@
// Update violations table
for(let i=0;i<response.length;i++) {
table_html += "<tr><td>" + response[i].level_display + "</td><td>" + response[i].type_display + "</td><td>" + response[i].details + "</td><td>" + response[i].timestamp_display + "</td><td></td></tr>";
table_html += "<tr><td>" + response[i].level_display + "</td><td>" + response[i].type_display + "</td><td>" + response[i].details + "</td><td class='nowrap'>" + response[i].timestamp_display + "</td><td><a href='" + response[i].edit_url + "'><i class='btn btn-primary fa fa-pen'></i></a></td></tr>";
}
}
else {
......
......@@ -175,8 +175,9 @@ class AKDurationForm(forms.ModelForm):
class AKOrgaMessageForm(forms.ModelForm):
class Meta:
model = AKOrgaMessage
fields = ['ak', 'text']
fields = ['ak', 'text', 'event']
widgets = {
'ak': forms.HiddenInput,
'event': forms.HiddenInput,
'text': forms.Textarea,
}
# Create your models here.
from django.apps import apps
from django.conf import settings
from django.core.mail import EmailMessage
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse_lazy
from AKModel.models import AKOrgaMessage, AKSlot
@receiver(post_save, sender=AKOrgaMessage)
def orga_message_saved_handler(sender, instance: AKOrgaMessage, created, **kwargs):
# React to newly created Orga message by sending an email
if created and settings.SEND_MAILS:
host = 'https://' + settings.ALLOWED_HOSTS[0] if len(settings.ALLOWED_HOSTS) > 0 else 'http://127.0.0.1:8000'
url = f"{host}{reverse_lazy('submit:ak_detail', kwargs={'pk': instance.ak.pk, 'event_slug': instance.ak.event.slug})}"
mail = EmailMessage(
f"[AKPlanning] New message for AK '{instance.ak}' ({instance.ak.event})",
f"{instance.text}\n\n{url}",
settings.DEFAULT_FROM_EMAIL,
[instance.ak.event.contact_email]
)
mail.send(fail_silently=True)
@receiver(post_save, sender=AKSlot)
def slot_created_handler(sender, instance: AKSlot, created, **kwargs):
# React to slots that are created after the plan was already published by sending an email
if created and settings.SEND_MAILS and apps.is_installed("AKPlan") and not instance.event.plan_hidden and instance.room is None and instance.start is None:
host = 'https://' + settings.ALLOWED_HOSTS[0] if len(settings.ALLOWED_HOSTS) > 0 else 'http://127.0.0.1:8000'
url = f"{host}{reverse_lazy('submit:ak_detail', kwargs={'pk': instance.ak.pk, 'event_slug': instance.ak.event.slug})}"
mail = EmailMessage(
f"[AKPlanning] New slot for AK '{instance.ak}' ({instance.ak.event}) added",
f"New slot requested.\n\nAK: {instance.ak}\nDuration: {instance.duration}\n\n{url}",
settings.DEFAULT_FROM_EMAIL,
[instance.ak.event.contact_email]
)
mail.send(fail_silently=True)
......@@ -437,6 +437,7 @@ class AKAddOrgaMessageView(EventSlugMixin, CreateView):
def get_initial(self):
initials = super(AKAddOrgaMessageView, self).get_initial()
initials['ak'] = get_object_or_404(AK, pk=self.kwargs['pk'])
initials['event'] = initials['ak'].event
return initials
def get_context_data(self, *, object_list=None, **kwargs):
......
......@@ -31,3 +31,4 @@ Afterwards, you may check your setup by executing ``Utils/check.sh`` or ``Utils/
## Developer Notes
* to regenerate translations use ````python manage.py makemessages -l de_DE --ignore venv````
* to create a data backup use ````python manage.py dumpdata --indent=2 > db.json --traceback````
* to export all database items belonging to a certain event use ````.\Utils\json_export.sh <event_id> <export_prefix> [--prod]````. The results will be saved in ````backups/<export_prefix>.json````
......@@ -7,3 +7,4 @@ All scripts should be executed from the project folder (repository root).
* **setup** installation script for development setup
* **update** update script for development or production (--prod) setup
* **check** setup checking script for development and production (--prod) setup
* **json_export** export script for development and production (--prod) -- can be used to export all database items belonging to a given event
\ No newline at end of file
import json
import sys
event_id = int(sys.argv[1])
target_name = sys.argv[2]
print(f"Creating export for event '{event_id}' as '{target_name}'")
# Load json file just created by django
with open('backups/akplanning_only.json', 'r') as json_file:
exported_entries = json.load(json_file)
print(f"Loaded {len(exported_entries)} entries in total, restricting to event...")
entries_without_event = 0
entries_out = []
virtual_rooms_to_preserve = set()
# Loop over all dumped entries
for entry in exported_entries:
# Handle all entries with event reference
if "event" in entry['fields']:
event = int(entry['fields']['event'])
# Does this entry belong to the event we are looking for?
if event == event_id:
# Store for backup
entries_out.append(entry)
# Remember the primary keys of all rooms of this event
# Required for special handling of virtual rooms,
# since they inherit from normal rooms and have no direct event reference
if entry['model'] == "AKModel.room":
virtual_rooms_to_preserve.add(entry['pk'])
# Handle entries without event reference
else:
# Backup virtual rooms of that event
if entry['model'] == "AKOnline.virtualroom":
if entry['pk'] in virtual_rooms_to_preserve:
entries_out.append(entry)
# Backup the event itself
elif entry['model'] == "AKModel.event":
if int(entry['pk']) == event_id:
entries_out.append(entry)
# Backup tags
elif entry['model'] == "AKModel.aktag":
# No restriction needed, backup all tags
entries_out.append(entry)
else:
# This should normally not happen (all other models should have a reference to the event)
entries_without_event += 1
print(entry)
print(f"Ignored entries without event: {entries_without_event}")
print(f"Exporting {len(entries_out)} entries for event")
with open(f'backups/{target_name}.json', 'w') as json_file:
json.dump(entries_out, json_file, indent=2)
#!/usr/bin/env bash
# Update AKPlanning
# execute as Utils/update.sh id_to_export target_name_to_export_to [--prod]
# abort on error, print executed commands
set -ex
# activate virtualenv if necessary
if [ -z ${VIRTUAL_ENV+x} ]; then
source venv/bin/activate
fi
# set environment variable when we want to update in production
if [ "$3" = "--prod" ]; then
export DJANGO_SETTINGS_MODULE=AKPlanning.settings_production
fi
mkdir -p ../backups/
python manage.py dumpdata AKDashboard AKModel AKOnline AKPlan AKScheduling AKSubmission --indent=2 > "backups/akplanning_only.json" --traceback
python ./Utils/json_export.py $1 $2
rm backups/akplanning_only.json