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
  • komasolver
  • main
  • renovate/django_csp-4.x
3 results

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
  • ak-import
  • feature/clear-schedule-button
  • feature/json-export-via-rest-framework
  • feature/json-schedule-import-tests
  • feature/preference-polling
  • feature/preference-polling-form
  • feature/preference-polling-form-rebased
  • feature/preference-polling-rebased
  • fix/add-room-import-only-once
  • main
  • merge-to-upstream
  • renovate/django-5.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-simple-history-3.x
  • renovate/mysqlclient-2.x
15 results
Show changes
Showing
with 1563 additions and 61 deletions
{% load tz %} {% load tz %}
{% load fontawesome_6 %}
{% timezone event.timezone %} {% timezone event.timezone %}
<table class="table table-striped"> <table class="table table-striped">
...@@ -7,7 +8,10 @@ ...@@ -7,7 +8,10 @@
<span class="text-secondary float-end"> <span class="text-secondary float-end">
{{ message.timestamp|date:"Y-m-d H:i:s" }} {{ message.timestamp|date:"Y-m-d H:i:s" }}
</span> </span>
<h5><a href="{{ message.ak.detail_url }}">{{ message.ak }}</a></h5> <h5><a href="{{ message.ak.detail_url }}">
{% if message.resolved %}{% fa6_icon "check-circle" %} {% endif %}
{{ message.ak }}
</a></h5>
<p>{{ message.text }}</p> <p>{{ message.text }}</p>
</td></tr> </td></tr>
{% endfor %} {% endfor %}
......
...@@ -3,35 +3,65 @@ from django.apps import apps ...@@ -3,35 +3,65 @@ from django.apps import apps
from django.conf import settings from django.conf import settings
from django.utils.html import format_html, mark_safe, conditional_escape from django.utils.html import format_html, mark_safe, conditional_escape
from django.templatetags.static import static from django.templatetags.static import static
from django.template.defaultfilters import date
from fontawesome_6.app_settings import get_css from fontawesome_6.app_settings import get_css
from AKModel.models import Event
register = template.Library() register = template.Library()
# Get Footer Info from settings
@register.simple_tag @register.simple_tag
def footer_info(): def footer_info():
"""
Get Footer Info from settings
:return: a dict of several strings like the impress URL to use in the footer
:rtype: Dict[str, str]
"""
return settings.FOOTER_INFO return settings.FOOTER_INFO
@register.filter @register.filter
def check_app_installed(name): def check_app_installed(name):
"""
Check whether the app with the given name is active in this instance
:param name: name of the app to check for
:return: true if app is installed
:rtype: bool
"""
return apps.is_installed(name) return apps.is_installed(name)
@register.filter @register.filter
def message_bootstrap_class(tag): def message_bootstrap_class(tag):
"""
Turn message severity classes into corresponding bootstrap css classes
:param tag: severity of the message
:return: matching bootstrap class
"""
if tag == "error": if tag == "error":
return "alert-danger" return "alert-danger"
elif tag == "success": if tag == "success":
return "alert-success" return "alert-success"
elif tag == "warning": if tag == "warning":
return "alert-warning" return "alert-warning"
return "alert-info" return "alert-info"
@register.filter @register.filter
def wiki_owners_export(owners, event): def wiki_owners_export(owners, event):
"""
Preserve owner link information for wiki export by using internal links if possible
but external links when owner specified a non-wikilink. This is applied to the full list of owners
:param owners: list of owners
:param event: event this owner belongs to and that is currently exported (specifying this directly prevents unnecessary database lookups) #pylint: disable=line-too-long
:return: linkified owners list in wiki syntax
:rtype: str
"""
def to_link(owner): def to_link(owner):
if owner.link != '': if owner.link != '':
event_link_prefix, _ = event.base_url.rsplit("/", 1) event_link_prefix, _ = event.base_url.rsplit("/", 1)
...@@ -44,17 +74,45 @@ def wiki_owners_export(owners, event): ...@@ -44,17 +74,45 @@ def wiki_owners_export(owners, event):
return ", ".join(to_link(owner) for owner in owners.all()) return ", ".join(to_link(owner) for owner in owners.all())
@register.filter
def event_month_year(event:Event):
"""
Print rough event date (month and year)
:param event: event to print the date for
:return: string containing rough date information for event
"""
if event.start.month == event.end.month:
return f"{date(event.start, 'F')} {event.start.year}"
event_start_string = date(event.start, 'F')
if event.start.year != event.end.year:
event_start_string = f"{event_start_string} {event.start.year}"
return f"{event_start_string} - {date(event.end, 'F')} {event.end.year}"
# get list of relevant css fontawesome css files for this instance
css = get_css() css = get_css()
@register.simple_tag @register.simple_tag
def fontawesome_6_css(): def fontawesome_6_css():
"""
Create html code to load all required fontawesome css files
:return: HTML code to load css
:rtype: str
"""
return mark_safe(conditional_escape('\n').join(format_html( return mark_safe(conditional_escape('\n').join(format_html(
'<link href="{}" rel="stylesheet" media="all">', stylesheet) for stylesheet in css)) '<link href="{}" rel="stylesheet" media="all">', stylesheet) for stylesheet in css))
@register.simple_tag @register.simple_tag
def fontawesome_6_js(): def fontawesome_6_js():
"""
Create html code to load all required fontawesome javascript files
:return: HTML code to load js
:rtype: str
"""
return mark_safe(format_html( return mark_safe(format_html(
'<script type="text/javascript" src="{}"></script>', static('fontawesome_6/js/django-fontawesome.js') '<script type="text/javascript" src="{}"></script>', static('fontawesome_6/js/django-fontawesome.js')
)) ))
\ No newline at end of file
This diff is collapsed.
...@@ -4,12 +4,16 @@ from django.urls import include, path ...@@ -4,12 +4,16 @@ from django.urls import include, path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
import AKModel.views.api import AKModel.views.api
from AKModel.views.manage import ExportSlidesView from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, DefaultSlotEditorView, \
from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView AKsByUserView, AKScheduleJSONImportView
from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKJSONExportView, AKWikiExportView, \
AKMessageDeleteView
from AKModel.views.event_wizard import NewEventWizardStartView, NewEventWizardPrepareImportView, \ from AKModel.views.event_wizard import NewEventWizardStartView, NewEventWizardPrepareImportView, \
NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, NewEventWizardSettingsView NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, NewEventWizardSettingsView
from AKModel.views.room import RoomBatchCreationView
from AKModel.views.status import EventStatusView from AKModel.views.status import EventStatusView
# Register basic API views/endpoints
api_router = DefaultRouter() api_router = DefaultRouter()
api_router.register('akowner', AKModel.views.api.AKOwnerViewSet, basename='AKOwner') api_router.register('akowner', AKModel.views.api.AKOwnerViewSet, basename='AKOwner')
api_router.register('akcategory', AKModel.views.api.AKCategoryViewSet, basename='AKCategory') api_router.register('akcategory', AKModel.views.api.AKCategoryViewSet, basename='AKCategory')
...@@ -18,7 +22,9 @@ api_router.register('ak', AKModel.views.api.AKViewSet, basename='AK') ...@@ -18,7 +22,9 @@ api_router.register('ak', AKModel.views.api.AKViewSet, basename='AK')
api_router.register('room', AKModel.views.api.RoomViewSet, basename='Room') api_router.register('room', AKModel.views.api.RoomViewSet, basename='Room')
api_router.register('akslot', AKModel.views.api.AKSlotViewSet, basename='AKSlot') api_router.register('akslot', AKModel.views.api.AKSlotViewSet, basename='AKSlot')
# TODO Can we move this functionality to the individual apps instead?
extra_paths = [] extra_paths = []
# If AKScheduling is active, register additional API endpoints
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, DefaultSlotsView ConstraintViolationsViewSet, DefaultSlotsView
...@@ -33,9 +39,10 @@ if apps.is_installed("AKScheduling"): ...@@ -33,9 +39,10 @@ if apps.is_installed("AKScheduling"):
name='scheduling-room-availabilities')), name='scheduling-room-availabilities')),
extra_paths.append(path('api/scheduling-default-slots/', DefaultSlotsView.as_view(), extra_paths.append(path('api/scheduling-default-slots/', DefaultSlotsView.as_view(),
name='scheduling-default-slots')) name='scheduling-default-slots'))
#If AKSubmission is active, register an additional API endpoint for increasing the interest counter
if apps.is_installed("AKSubmission"): if apps.is_installed("AKSubmission"):
from AKSubmission.api import increment_interest_counter from AKSubmission.api import increment_interest_counter
extra_paths.append(path('api/ak/<pk>/indicate-interest/', increment_interest_counter, name='submission-ak-indicate-interest')) extra_paths.append(path('api/ak/<pk>/indicate-interest/', increment_interest_counter, name='submission-ak-indicate-interest'))
event_specific_paths = [ event_specific_paths = [
...@@ -45,6 +52,7 @@ event_specific_paths.extend(extra_paths) ...@@ -45,6 +52,7 @@ event_specific_paths.extend(extra_paths)
app_name = 'model' app_name = 'model'
# Included all these extra view paths at a path starting with the event slug
urlpatterns = [ urlpatterns = [
path( path(
'<slug:event_slug>/', '<slug:event_slug>/',
...@@ -55,6 +63,9 @@ urlpatterns = [ ...@@ -55,6 +63,9 @@ urlpatterns = [
def get_admin_urls_event_wizard(admin_site): def get_admin_urls_event_wizard(admin_site):
"""
Defines all additional URLs for the event creation wizard
"""
return [ return [
path('add/wizard/start/', admin_site.admin_view(NewEventWizardStartView.as_view()), path('add/wizard/start/', admin_site.admin_view(NewEventWizardStartView.as_view()),
name="new_event_wizard_start"), name="new_event_wizard_start"),
...@@ -75,15 +86,30 @@ def get_admin_urls_event_wizard(admin_site): ...@@ -75,15 +86,30 @@ def get_admin_urls_event_wizard(admin_site):
def get_admin_urls_event(admin_site): def get_admin_urls_event(admin_site):
"""
Defines all additional event-related view URLs that will be included in the event admin interface
"""
return [ return [
path('<slug:event_slug>/status/', admin_site.admin_view(EventStatusView.as_view()), name="event_status"), path('<slug:event_slug>/status/', admin_site.admin_view(EventStatusView.as_view()), name="event_status"),
path('<slug:event_slug>/requirements/', admin_site.admin_view(AKRequirementOverview.as_view()), path('<slug:event_slug>/requirements/', admin_site.admin_view(AKRequirementOverview.as_view()),
name="event_requirement_overview"), name="event_requirement_overview"),
path('<slug:event_slug>/aks/owner/<pk>/', admin_site.admin_view(AKsByUserView.as_view()),
name="aks_by_owner"),
path('<slug:event_slug>/ak-csv-export/', admin_site.admin_view(AKCSVExportView.as_view()), path('<slug:event_slug>/ak-csv-export/', admin_site.admin_view(AKCSVExportView.as_view()),
name="ak_csv_export"), name="ak_csv_export"),
path('<slug:event_slug>/ak-json-export/', admin_site.admin_view(AKJSONExportView.as_view()),
name="ak_json_export"),
path('<slug:event_slug>/ak-schedule-json-import/', admin_site.admin_view(AKScheduleJSONImportView.as_view()),
name="ak_schedule_json_import"),
path('<slug:slug>/ak-wiki-export/', admin_site.admin_view(AKWikiExportView.as_view()), path('<slug:slug>/ak-wiki-export/', admin_site.admin_view(AKWikiExportView.as_view()),
name="ak_wiki_export"), name="ak_wiki_export"),
path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()), path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()),
name="ak_delete_orga_messages"), name="ak_delete_orga_messages"),
path('<slug:event_slug>/ak-slide-export/', admin_site.admin_view(ExportSlidesView.as_view()), name="ak_slide_export"), path('<slug:event_slug>/ak-slide-export/', admin_site.admin_view(ExportSlidesView.as_view()), name="ak_slide_export"),
path('plan/publish/', admin_site.admin_view(PlanPublishView.as_view()), name="plan-publish"),
path('plan/unpublish/', admin_site.admin_view(PlanUnpublishView.as_view()), name="plan-unpublish"),
path('<slug:event_slug>/defaultSlots/', admin_site.admin_view(DefaultSlotEditorView.as_view()),
name="default-slots-editor"),
path('<slug:event_slug>/importRooms/', admin_site.admin_view(RoomBatchCreationView.as_view()),
name="room-import"),
] ]
from pathlib import Path
import referencing.retrieval
from jsonschema import Draft202012Validator
from jsonschema.protocols import Validator
from referencing import Registry
from AKPlanning import settings
def _construct_schema_path(uri: str | Path) -> Path:
"""Construct a schema URI.
This function also checks for unallowed directory traversals
out of the 'schema' subfolder.
"""
schema_base_path = Path(settings.BASE_DIR).resolve()
uri_path = (schema_base_path / uri).resolve()
if not uri_path.is_relative_to(schema_base_path / "schemas"):
raise ValueError("Unallowed dictionary traversal")
return uri_path
@referencing.retrieval.to_cached_resource()
def retrieve_schema_from_disk(uri: str) -> str:
"""Retrieve schemas from disk by URI."""
uri_path = _construct_schema_path(uri)
with uri_path.open("r") as ff:
return ff.read()
def construct_schema_validator(schema: str | dict) -> Validator:
"""Construct a validator for a JSON schema.
In particular, all schemas from the 'schemas' directory
are loaded into the registry.
"""
registry = Registry(retrieve=retrieve_schema_from_disk)
if isinstance(schema, str):
schema_uri = str(Path("schemas") / schema)
schema = registry.get_or_retrieve(schema_uri).value.contents
return Draft202012Validator(schema=schema, registry=registry)
import json
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView
...@@ -9,6 +12,9 @@ from AKModel.models import AKRequirement, AKSlot, Event, AKOrgaMessage, AK ...@@ -9,6 +12,9 @@ from AKModel.models import AKRequirement, AKSlot, Event, AKOrgaMessage, AK
class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView): class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView):
"""
View: Display requirements for the given event
"""
model = AKRequirement model = AKRequirement
context_object_name = "requirements" context_object_name = "requirements"
title = _("Requirements for Event") title = _("Requirements for Event")
...@@ -22,6 +28,9 @@ class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView): ...@@ -22,6 +28,9 @@ class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView):
class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
"""
View: Export all AK slots of this event in CSV format ordered by tracks
"""
template_name = "admin/AKModel/ak_csv_export.html" template_name = "admin/AKModel/ak_csv_export.html"
model = AKSlot model = AKSlot
context_object_name = "slots" context_object_name = "slots"
...@@ -30,12 +39,50 @@ class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): ...@@ -30,12 +39,50 @@ class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().order_by("ak__track") return super().get_queryset().order_by("ak__track")
class AKJSONExportView(AdminViewMixin, DetailView):
"""
View: Export all AK slots of this event in JSON format ordered by tracks
"""
template_name = "admin/AKModel/ak_json_export.html"
model = Event
context_object_name = "event"
title = _("AK JSON Export")
slug_url_kwarg = "event_slug"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
try:
data = context["event"].as_json_dict()
context["json_data_oneline"] = json.dumps(data, ensure_ascii=False)
context["json_data"] = json.dumps(data, indent=2, ensure_ascii=False)
context["is_valid"] = True
except ValueError as ex:
messages.add_message(
self.request,
messages.ERROR,
_("Exporting AKs for the solver failed! Reason: ") + str(ex),
)
return context return context
def get(self, request, *args, **kwargs):
# as this code is adapted from BaseDetailView::get
# pylint: disable=attribute-defined-outside-init
self.object = self.get_object()
context = self.get_context_data(object=self.object)
# if serialization failed in `get_context_data` we redirect to
# the status page and show a message instead
if not context.get("is_valid", False):
return redirect("admin:event_status", context["event"].slug)
return self.render_to_response(context)
class AKWikiExportView(AdminViewMixin, DetailView): class AKWikiExportView(AdminViewMixin, DetailView):
"""
View: Export AKs of this event in wiki syntax
This will show one text field per category, with a separate category/field for wishes
"""
template_name = "admin/AKModel/wiki_export.html" template_name = "admin/AKModel/wiki_export.html"
model = Event model = Event
context_object_name = "event" context_object_name = "event"
...@@ -46,7 +93,7 @@ class AKWikiExportView(AdminViewMixin, DetailView): ...@@ -46,7 +93,7 @@ class AKWikiExportView(AdminViewMixin, DetailView):
categories_with_aks, ak_wishes = context["event"].get_categories_with_aks( categories_with_aks, ak_wishes = context["event"].get_categories_with_aks(
wishes_seperately=True, wishes_seperately=True,
filter=lambda ak: ak.include_in_export filter_func=lambda ak: ak.include_in_export
) )
context["categories_with_aks"] = [(category.name, ak_list) for category, ak_list in categories_with_aks] context["categories_with_aks"] = [(category.name, ak_list) for category, ak_list in categories_with_aks]
...@@ -56,10 +103,18 @@ class AKWikiExportView(AdminViewMixin, DetailView): ...@@ -56,10 +103,18 @@ class AKWikiExportView(AdminViewMixin, DetailView):
class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView): class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView):
"""
View: Confirmation page to delete confidential AK-related messages to orga
Confirmation functionality provided by :class:`AKModel.metaviews.admin.IntermediateAdminView`
"""
template_name = "admin/AKModel/message_delete.html" template_name = "admin/AKModel/message_delete.html"
title = _("Delete AK Orga Messages") title = _("Delete AK Orga Messages")
def get_orga_messages_for_event(self, event): def get_orga_messages_for_event(self, event):
"""
Get all orga messages for the given event
"""
return AKOrgaMessage.objects.filter(ak__event=event) return AKOrgaMessage.objects.filter(ak__event=event)
def get_success_url(self): def get_success_url(self):
...@@ -77,6 +132,11 @@ class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView): ...@@ -77,6 +132,11 @@ class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView):
class AKResetInterestView(IntermediateAdminActionView): class AKResetInterestView(IntermediateAdminActionView):
"""
View: Confirmation page to reset all manually specified interest values
Confirmation functionality provided by :class:`AKModel.metaviews.admin.IntermediateAdminView`
"""
title = _("Reset interest in AKs") title = _("Reset interest in AKs")
model = AK model = AK
confirmation_message = _("Interest of the following AKs will be set to not filled (-1):") confirmation_message = _("Interest of the following AKs will be set to not filled (-1):")
...@@ -87,6 +147,11 @@ class AKResetInterestView(IntermediateAdminActionView): ...@@ -87,6 +147,11 @@ class AKResetInterestView(IntermediateAdminActionView):
class AKResetInterestCounterView(IntermediateAdminActionView): class AKResetInterestCounterView(IntermediateAdminActionView):
"""
View: Confirmation page to reset all interest counters (online interest indication)
Confirmation functionality provided by :class:`AKModel.metaviews.admin.IntermediateAdminView`
"""
title = _("Reset AKs' interest counters") title = _("Reset AKs' interest counters")
model = AK model = AK
confirmation_message = _("Interest counter of the following AKs will be set to 0:") confirmation_message = _("Interest counter of the following AKs will be set to 0:")
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# Create your tests here.
# Register your models here.
# Create your models here.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.