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
Select Git revision

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
Select Git revision
Show changes
Commits on Source (11)
Showing
with 591 additions and 312 deletions
from django import forms from django import forms
from django.apps import apps from django.apps import apps
from django.contrib import admin from django.contrib import admin, messages
from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter, action
from django.db.models import Count, F from django.db.models import Count, F
from django.db.models.functions import Now
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
...@@ -15,7 +16,7 @@ from simple_history.admin import SimpleHistoryAdmin ...@@ -15,7 +16,7 @@ from simple_history.admin import SimpleHistoryAdmin
from AKModel.availability.forms import AvailabilitiesFormMixin from AKModel.availability.forms import AvailabilitiesFormMixin
from AKModel.availability.models import Availability from AKModel.availability.models import Availability
from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \ from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \
ConstraintViolation ConstraintViolation, DefaultSlot
from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event
...@@ -31,11 +32,12 @@ class EventRelatedFieldListFilter(RelatedFieldListFilter): ...@@ -31,11 +32,12 @@ class EventRelatedFieldListFilter(RelatedFieldListFilter):
@admin.register(Event) @admin.register(Event)
class EventAdmin(admin.ModelAdmin): class EventAdmin(admin.ModelAdmin):
model = Event model = Event
list_display = ['name', 'status_url', 'place', 'start', 'end', 'active'] list_display = ['name', 'status_url', 'place', 'start', 'end', 'active', 'plan_hidden']
list_filter = ['active'] list_filter = ['active']
list_editable = ['active'] list_editable = ['active']
ordering = ['-start'] ordering = ['-start']
readonly_fields = ['status_url'] readonly_fields = ['status_url', 'plan_hidden', 'plan_published_at']
actions = ['publish', 'unpublish']
def add_view(self, request, form_url='', extra_context=None): def add_view(self, request, form_url='', extra_context=None):
# Always use wizard to create new events (the built-in form wouldn't work anyways since the timezone cannot # Always use wizard to create new events (the built-in form wouldn't work anyways since the timezone cannot
...@@ -62,6 +64,16 @@ class EventAdmin(admin.ModelAdmin): ...@@ -62,6 +64,16 @@ class EventAdmin(admin.ModelAdmin):
timezone.activate(obj.timezone) timezone.activate(obj.timezone)
return super().get_form(request, obj, change, **kwargs) return super().get_form(request, obj, change, **kwargs)
@action(description=_('Publish plan'))
def publish(self, request, queryset):
queryset.update(plan_published_at=Now(), plan_hidden=False)
self.message_user(request, _('Plan published'), messages.SUCCESS)
@action(description=_('Unpublish plan'))
def unpublish(self, request, queryset):
queryset.update(plan_published_at=None, plan_hidden=True)
self.message_user(request, _('Plan unpublished'), messages.SUCCESS)
@admin.register(AKOwner) @admin.register(AKOwner)
class AKOwnerAdmin(admin.ModelAdmin): class AKOwnerAdmin(admin.ModelAdmin):
...@@ -317,3 +329,23 @@ class ConstraintViolationAdmin(admin.ModelAdmin): ...@@ -317,3 +329,23 @@ class ConstraintViolationAdmin(admin.ModelAdmin):
list_filter = ['event'] list_filter = ['event']
readonly_fields = ['timestamp'] readonly_fields = ['timestamp']
form = ConstraintViolationAdminForm form = ConstraintViolationAdminForm
class DefaultSlotAdminForm(forms.ModelForm):
class Meta:
widgets = {
'primary_categories': forms.CheckboxSelectMultiple
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Filter possible values for foreign keys & m2m when event is specified
if hasattr(self.instance, "event") and self.instance.event is not None:
self.fields["primary_categories"].queryset = AKCategory.objects.filter(event=self.instance.event)
@admin.register(DefaultSlot)
class DefaultSlotAdmin(admin.ModelAdmin):
list_display = ['start', 'end', 'event']
list_filter = ['event']
form = DefaultSlotAdminForm
...@@ -21,7 +21,7 @@ zero_time = datetime.time(0, 0) ...@@ -21,7 +21,7 @@ zero_time = datetime.time(0, 0)
# remove serialization as requirements are not covered # remove serialization as requirements are not covered
# add translation # add translation
# add meta class # add meta class
# enable availabilites for AKs and AKCategories # enable availabilities for AKs and AKCategories
# add verbose names and help texts to model attributes # add verbose names and help texts to model attributes
class Availability(models.Model): class Availability(models.Model):
"""The Availability class models when people or rooms are available for. """The Availability class models when people or rooms are available for.
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# Copyright 2017-2019, Tobias Kunze # Copyright 2017-2019, Tobias Kunze
# Original Copyrights licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 # Original Copyrights licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0
# Changes are marked in the code # Changes are marked in the code
from django.utils import timezone
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
...@@ -10,10 +11,20 @@ from AKModel.availability.models import Availability ...@@ -10,10 +11,20 @@ from AKModel.availability.models import Availability
class AvailabilitySerializer(ModelSerializer): class AvailabilitySerializer(ModelSerializer):
allDay = SerializerMethodField() allDay = SerializerMethodField()
start = SerializerMethodField()
end = SerializerMethodField()
def get_allDay(self, obj): def get_allDay(self, obj):
return obj.all_day return obj.all_day
# Use already localized strings in serialized field
# (default would be UTC, but that would require heavy timezone calculation on client side)
def get_start(self, obj):
return timezone.localtime(obj.start, obj.event.timezone).strftime("%Y-%m-%dT%H:%M:%S")
def get_end(self, obj):
return timezone.localtime(obj.end, obj.event.timezone).strftime("%Y-%m-%dT%H:%M:%S")
class Meta: class Meta:
model = Availability model = Availability
fields = ('id', 'start', 'end', 'allDay') fields = ('id', 'start', 'end', 'allDay')
from bootstrap_datepicker_plus import DateTimePickerInput from bootstrap_datepicker_plus import DateTimePickerInput
from django import forms from django import forms
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from AKModel.models import Event, AKCategory, AKRequirement from AKModel.models import Event, AKCategory, AKRequirement
...@@ -29,6 +29,7 @@ class NewEventWizardSettingsForm(forms.ModelForm): ...@@ -29,6 +29,7 @@ class NewEventWizardSettingsForm(forms.ModelForm):
'start': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}), 'start': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}),
'end': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}), 'end': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}),
'reso_deadline': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}), 'reso_deadline': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}),
'plan_hidden': forms.HiddenInput(),
} }
......
"""
Ensure PO files are generated using forward slashes in the location comments on all operating systems
"""
import os
from django.core.management.commands.makemessages import Command as MakeMessagesCommand
class Command(MakeMessagesCommand):
def find_files(self, root):
all_files = super().find_files(root)
if os.sep != "\\":
return all_files
for file_entry in all_files:
if file_entry.dirpath == ".":
file_entry.dirpath = ""
elif file_entry.dirpath.startswith(".\\"):
file_entry.dirpath = file_entry.dirpath[2:].replace("\\", "/")
return all_files
def build_potfiles(self):
pot_files = super().build_potfiles()
if os.sep != "\\":
return pot_files
for filename in pot_files:
lines = open(filename, "r", encoding="utf-8").readlines()
fixed_lines = []
for line in lines:
if line.startswith("#: "):
line = line.replace("\\", "/")
fixed_lines.append(line)
with open(filename, "w", encoding="utf-8") as f:
f.writelines(fixed_lines)
return pot_files
This diff is collapsed.
# Generated by Django 3.2.16 on 2022-10-15 10:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('AKModel', '0052_history_upgrade'),
]
operations = [
migrations.AddField(
model_name='event',
name='plan_published_at',
field=models.DateTimeField(blank=True, help_text='Timestamp at which the plan was published', null=True, verbose_name='Plan published at'),
),
]
# Generated by Django 3.2.16 on 2022-10-22 12:18
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('AKModel', '0053_plan_published_at'),
]
operations = [
migrations.CreateModel(
name='DefaultSlot',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start', models.DateTimeField(help_text='Time and date the slot begins', verbose_name='Slot Begin')),
('end', models.DateTimeField(help_text='Time and date the slot ends', verbose_name='Slot End')),
('event', models.ForeignKey(help_text='Associated event', on_delete=django.db.models.deletion.CASCADE, to='AKModel.event', verbose_name='Event')),
('primary_categories', models.ManyToManyField(blank=True, help_text='Categories that should be assigned to this slot primarily', to='AKModel.AKCategory', verbose_name='Primary categories')),
],
options={
'verbose_name': 'Default Slot',
'verbose_name_plural': 'Default Slots',
'ordering': ['-start'],
},
),
]
...@@ -39,6 +39,8 @@ class Event(models.Model): ...@@ -39,6 +39,8 @@ class Event(models.Model):
active = models.BooleanField(verbose_name=_('Active State'), help_text=_('Marks currently active events')) active = models.BooleanField(verbose_name=_('Active State'), help_text=_('Marks currently active events'))
plan_hidden = models.BooleanField(verbose_name=_('Plan Hidden'), help_text=_('Hides plan for non-staff users'), plan_hidden = models.BooleanField(verbose_name=_('Plan Hidden'), help_text=_('Hides plan for non-staff users'),
default=True) default=True)
plan_published_at = models.DateTimeField(verbose_name=_('Plan published at'), blank=True, null=True,
help_text=_('Timestamp at which the plan was published'))
base_url = models.URLField(verbose_name=_("Base URL"), help_text=_("Prefix for wiki link construction"), blank=True) base_url = models.URLField(verbose_name=_("Base URL"), help_text=_("Prefix for wiki link construction"), blank=True)
wiki_export_template_name = models.CharField(verbose_name=_("Wiki Export Template Name"), blank=True, max_length=50) wiki_export_template_name = models.CharField(verbose_name=_("Wiki Export Template Name"), blank=True, max_length=50)
...@@ -639,3 +641,30 @@ class ConstraintViolation(models.Model): ...@@ -639,3 +641,30 @@ class ConstraintViolation(models.Model):
if getattr(self, field) != getattr(other, field): if getattr(self, field) != getattr(other, field):
return False return False
return True return True
class DefaultSlot(models.Model):
class Meta:
verbose_name = _('Default Slot')
verbose_name_plural = _('Default Slots')
ordering = ['-start']
start = models.DateTimeField(verbose_name=_('Slot Begin'), help_text=_('Time and date the slot begins'))
end = models.DateTimeField(verbose_name=_('Slot End'), help_text=_('Time and date the slot ends'))
event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'),
help_text=_('Associated event'))
primary_categories = models.ManyToManyField(to=AKCategory, verbose_name=_('Primary categories'), blank=True,
help_text=_('Categories that should be assigned to this slot primarily'))
@property
def start_simplified(self):
return self.start.astimezone(self.event.timezone).strftime('%a %H:%M')
@property
def end_simplified(self):
return self.end.astimezone(self.event.timezone).strftime('%a %H:%M')
def __str__(self):
return f"{self.event}: {self.start_simplified} - {self.end_simplified}"
{% load static %}
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
<link href='{% static 'common/vendor/fullcalendar/main.css' %}' rel='stylesheet'/>
<script src='{% static 'common/vendor/fullcalendar/main.js' %}'></script>
<script src="{% static "common/vendor/moment/moment-with-locales.js" %}"></script>
<script src="{% static "common/js/availabilities.js" %}"></script>
{% with 'common/vendor/fullcalendar/locales/'|add:LANGUAGE_CODE|add:'.js' as locale_file %}
{% if LANGUAGE_CODE != "en" %}
{# Locale 'en' is included in main.js and does not exist separately #}
<script src="{% static locale_file %}"></script>
{% endif %}
{% endwith %}
...@@ -2,15 +2,23 @@ ...@@ -2,15 +2,23 @@
{% load i18n admin_urls %} {% load i18n admin_urls %}
{% load static %} {% load static %}
{% load bootstrap4 %} {% load bootstrap4 %}
{% load tz %}
{% block extrahead %} {% block extrahead %}
{{ block.super }} {{ block.super }}
{% bootstrap_javascript jquery='slim' %} {% bootstrap_javascript jquery='slim' %}
<link href='{% static 'AKSubmission/vendor/fullcalendar3/fullcalendar.min.css' %}' rel='stylesheet'/> {% include "AKModel/load_fullcalendar_availabilities.html" %}
<link href='{% static 'AKSubmission/css/availabilities.css' %}' rel='stylesheet'/>
<script src="{% static "AKSubmission/vendor/moment/moment-with-locales.js" %}"></script> <script>
<script src="{% static "AKSubmission/vendor/moment-timezone/moment-timezone-with-data-10-year-range.js" %}"></script> {% get_current_language as LANGUAGE_CODE %}
<script src='{% static 'AKSubmission/vendor/fullcalendar3/fullcalendar.min.js' %}'></script>
<script src="{% static "common/js/availabilities.js" %}"></script> document.addEventListener('DOMContentLoaded', function () {
createAvailabilityEditors(
'{{ original.event.timezone }}',
'{{ LANGUAGE_CODE }}',
'{{ original.event.start | timezone:original.event.timezone | date:"Y-m-d H:i:s" }}',
'{{ original.event.end | timezone:original.event.timezone | date:"Y-m-d H:i:s" }}'
);
});
</script>
{% endblock %} {% endblock %}
{% extends "admin/base_site.html" %} {% extends "admin/login.html" %}
{% load i18n static %} {% load i18n static %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/login.css" %}">
{{ form.media }}
{% endblock %}
{% block bodyclass %}{{ block.super }} login{% endblock %}
{% block usertools %}{% endblock %}
{% block nav-global %}{% endblock %}
{% block nav-sidebar %}{% endblock %}
{% block content_title %}{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content %} {% block content %}
{% if form.errors and not form.non_field_errors %} {% if form.errors and not form.non_field_errors %}
......
...@@ -19,7 +19,7 @@ api_router.register('akslot', views.AKSlotViewSet, basename='AKSlot') ...@@ -19,7 +19,7 @@ api_router.register('akslot', views.AKSlotViewSet, basename='AKSlot')
extra_paths = [] extra_paths = []
if apps.is_installed("AKScheduling"): if apps.is_installed("AKScheduling"):
from AKScheduling.api import ResourcesViewSet, RoomAvailabilitiesView, EventsView, EventsViewSet, \ from AKScheduling.api import ResourcesViewSet, RoomAvailabilitiesView, EventsView, EventsViewSet, \
ConstraintViolationsViewSet ConstraintViolationsViewSet, DefaultSlotsView
api_router.register('scheduling-resources', ResourcesViewSet, basename='scheduling-resources') api_router.register('scheduling-resources', ResourcesViewSet, basename='scheduling-resources')
api_router.register('scheduling-event', EventsViewSet, basename='scheduling-event') api_router.register('scheduling-event', EventsViewSet, basename='scheduling-event')
...@@ -28,7 +28,9 @@ if apps.is_installed("AKScheduling"): ...@@ -28,7 +28,9 @@ if apps.is_installed("AKScheduling"):
extra_paths.append(path('api/scheduling-events/', EventsView.as_view(), name='scheduling-events')) extra_paths.append(path('api/scheduling-events/', EventsView.as_view(), name='scheduling-events'))
extra_paths.append(path('api/scheduling-room-availabilities/', RoomAvailabilitiesView.as_view(), extra_paths.append(path('api/scheduling-room-availabilities/', RoomAvailabilitiesView.as_view(),
name='scheduling-room-availabilities')) name='scheduling-room-availabilities')),
extra_paths.append(path('api/scheduling-default-slots/', DefaultSlotsView.as_view(),
name='scheduling-default-slots'))
if apps.is_installed("AKSubmission"): if apps.is_installed("AKSubmission"):
from AKSubmission.api import increment_interest_counter from AKSubmission.api import increment_interest_counter
......
...@@ -11,6 +11,11 @@ register = template.Library() ...@@ -11,6 +11,11 @@ register = template.Library()
@register.filter @register.filter
def highlight_change_colors(akslot): def highlight_change_colors(akslot):
# Do not highlight in preview mode or when changes occurred before the plan was published
if akslot.event.plan_hidden or (akslot.event.plan_published_at is not None
and akslot.event.plan_published_at > akslot.updated):
return akslot.ak.category.color
seconds_since_update = akslot.seconds_since_last_update seconds_since_update = akslot.seconds_since_last_update
# Last change long ago? Use default color # Last change long ago? Use default color
......
...@@ -99,6 +99,8 @@ TEMPLATES = [ ...@@ -99,6 +99,8 @@ TEMPLATES = [
WSGI_APPLICATION = 'AKPlanning.wsgi.application' WSGI_APPLICATION = 'AKPlanning.wsgi.application'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Database # Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
......
...@@ -7,7 +7,7 @@ from django.views.generic import ListView ...@@ -7,7 +7,7 @@ from django.views.generic import ListView
from rest_framework import viewsets, mixins, serializers, permissions from rest_framework import viewsets, mixins, serializers, permissions
from AKModel.availability.models import Availability from AKModel.availability.models import Availability
from AKModel.models import Room, AKSlot, ConstraintViolation from AKModel.models import Room, AKSlot, ConstraintViolation, DefaultSlot
from AKModel.views import EventSlugMixin from AKModel.views import EventSlugMixin
...@@ -80,6 +80,30 @@ class RoomAvailabilitiesView(LoginRequiredMixin, EventSlugMixin, ListView): ...@@ -80,6 +80,30 @@ class RoomAvailabilitiesView(LoginRequiredMixin, EventSlugMixin, ListView):
) )
class DefaultSlotsView(LoginRequiredMixin, EventSlugMixin, ListView):
model = DefaultSlot
context_object_name = "default_slots"
def get_queryset(self):
return super().get_queryset().filter(event=self.event)
def render_to_response(self, context, **response_kwargs):
all_room_ids = [r.pk for r in self.event.room_set.all()]
return JsonResponse(
[{
"title": "",
"resourceIds": all_room_ids,
"start": timezone.localtime(a.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"end": timezone.localtime(a.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"display": 'background',
"groupId": 'defaultSlot',
"backgroundColor": '#69b6d4'
} for a in context["default_slots"]],
safe=False,
**response_kwargs
)
class EventSerializer(serializers.ModelSerializer): class EventSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AKSlot model = AKSlot
......
...@@ -147,7 +147,8 @@ ...@@ -147,7 +147,8 @@
resources: '{% url "model:scheduling-resources-list" event_slug=event.slug %}', resources: '{% url "model:scheduling-resources-list" event_slug=event.slug %}',
eventSources: [ eventSources: [
'{% url "model:scheduling-events" event_slug=event.slug %}', '{% url "model:scheduling-events" event_slug=event.slug %}',
'{% url "model:scheduling-room-availabilities" event_slug=event.slug %}' '{% url "model:scheduling-room-availabilities" event_slug=event.slug %}',
'{% url "model:scheduling-default-slots" event_slug=event.slug %}'
], ],
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source', schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
dayMinWidth: 100, dayMinWidth: 100,
......