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-5.x
  • renovate/django-debug-toolbar-5.x
  • renovate/django_csp-4.x
  • renovate/djangorestframework-3.x
  • renovate/tzdata-2025.x
  • renovate/uwsgi-2.x
8 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 665 additions and 523 deletions
...@@ -45,7 +45,9 @@ ...@@ -45,7 +45,9 @@
\faUser~ {{ ak.owners_list | latex_escape }} \faUser~ {{ ak.owners_list | latex_escape }}
\faClock~ {{ak.durations_list}} {% if not result_presentation_mode %}
\faClock~ {{ak.durations_list}}
{% endif %}
{% if ak.reso %} {% if ak.reso %}
\faScroll \faScroll
...@@ -84,9 +86,11 @@ ...@@ -84,9 +86,11 @@
\faFilter~ {{ ak.category.name | latex_escape }} \faFilter~ {{ ak.category.name | latex_escape }}
{% if space_for_notes_in_wishes %}
\faUser~ \faUser~
\faClock~ \faClock~
{% endif %}
{{ ak.description | truncatechars(280) | latex_escape }} {{ ak.description | truncatechars(280) | latex_escape }}
......
{% extends "admin/base_site.html" %} {% extends "admin/AKModel/action_intermediate.html" %}
{% load tags_AKModel %} {% load tags_AKModel %}
{% load i18n %} {% load i18n %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% block title %}{{event}}: {% trans "Delete Orga-Messages" %}{% endblock %} {% block action_preview %}
{% block content %}
<h2>{% trans "Delete AK Orga Messages" %}</h2>
<p>{% blocktrans with message_count=ak_messages.count %}Are you sure you want to delete all orga messages for {{ event }}? This will permanently delete {{ message_count }} message(s):{% endblocktrans %}</p> <p>{% blocktrans with message_count=ak_messages.count %}Are you sure you want to delete all orga messages for {{ event }}? This will permanently delete {{ message_count }} message(s):{% endblocktrans %}</p>
{% include "admin/AKModel/render_ak_messages.html" %} {% include "admin/AKModel/render_ak_messages.html" %}
<form method="post">{% csrf_token %}
<button type="submit" class="save btn btn-danger float-right" value="Confirm">
{% fa5_icon "check" 'fas' %} {% trans "Delete" %}
</button>
<a href="{% url 'admin:event_status' slug=event.slug %}" class="btn btn-info">
{% fa5_icon "times" 'fas' %} {% trans "Cancel" %}
</a>
</form>
{% endblock %} {% endblock %}
<table class="table table-striped"> {% load tz %}
{% for message in ak_messages %} {% load fontawesome_6 %}
<tr><td>
<span class="text-secondary float-right"> {% timezone event.timezone %}
{{ message.timestamp|date:"Y-m-d H:i:s" }} <table class="table table-striped">
</span> {% for message in ak_messages %}
<h5><a href="{% url 'submit:ak_detail' event_slug=message.ak.event.slug pk=message.ak.pk %}">{{ message.ak }}</a></h5> <tr><td>
<p>{{ message.text }}</p> <span class="text-secondary float-end">
</td></tr> {{ message.timestamp|date:"Y-m-d H:i:s" }}
{% endfor %} </span>
</table> <h5><a href="{{ message.ak.detail_url }}">
{% if message.resolved %}{% fa6_icon "check-circle" %} {% endif %}
{{ message.ak }}
</a></h5>
<p>{{ message.text }}</p>
</td></tr>
{% endfor %}
</table>
{% endtimezone %}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% load i18n %} {% load i18n %}
{% load tz %} {% load tz %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% block title %}{% trans "Status" %}: {{event}}{% endblock %} {% block title %}{% trans "Status" %}: {{event}}{% endblock %}
...@@ -22,14 +22,14 @@ ...@@ -22,14 +22,14 @@
<tr> <tr>
<td>{{ ak }}</td> <td>{{ ak }}</td>
{% if "AKSubmission"|check_app_installed %} {% if "AKSubmission"|check_app_installed %}
<td class="text-right"> <td class="text-end">
<a href="{% url 'submit:ak_detail' event_slug=ak.event.slug pk=ak.pk %}" data-toggle="tooltip" <a href="{{ ak.detail_url }}" data-bs-toggle="tooltip"
title="{% trans 'Details' %}" title="{% trans 'Details' %}"
class="btn btn-primary">{% fa5_icon 'info' 'fas' %}</a> class="btn btn-primary">{% fa6_icon 'info' 'fas' %}</a>
{% if event.active %} {% if event.active %}
<a href="{% url 'submit:ak_edit' event_slug=event.slug pk=ak.pk %}" data-toggle="tooltip" <a href="{{ ak.edit_url }}" data-bs-toggle="tooltip"
title="{% trans 'Edit' %}" title="{% trans 'Edit' %}"
class="btn btn-success">{% fa5_icon 'pencil-alt' 'fas' %}</a> class="btn btn-success">{% fa6_icon 'pencil-alt' 'fas' %}</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
......
{% extends "admin/change_form.html" %} {% extends "admin/change_form.html" %}
{% load i18n admin_urls %} {% load i18n admin_urls %}
{% load static %} {% load static %}
{% load bootstrap4 %} {% load django_bootstrap5 %}
{% load tz %}
{% block extrahead %} {% block extrahead %}
{{ block.super }} {{ block.super }}
{% bootstrap_javascript jquery='slim' %} {% bootstrap_javascript %}
<link href='{% static 'AKSubmission/vendor/fullcalendar3/fullcalendar.min.css' %}' rel='stylesheet'/> <script src="{% static 'common/vendor/jquery/jquery-3.6.3.min.js' %}"></script>
<link href='{% static 'AKSubmission/css/availabilities.css' %}' rel='stylesheet'/> {% if original.event %}
{% include "AKModel/load_fullcalendar_availabilities.html" %}
<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>
{% endif %}
{% endblock %} {% endblock %}
{% extends "admin/base_site.html" %}
{% load tags_AKModel %}
{% load i18n %}
{% load django_bootstrap5 %}
{% load fontawesome_6 %}
{% block title %}{% trans "Create room" %}{% endblock %}
{% block content %}
<h2>{% trans "Create room" %}</h2>
<form method="post">{% csrf_token %}
{% block form_details %}
{% bootstrap_form form %}
{% endblock %}
<div class="float-end">
<button type="submit" name="save_action" class="save btn btn-secondary" value="save_add_another">
{% fa6_icon "plus" 'fas' %} {% trans "Save and add another" %}
</button>
<button type="submit" name="save_action" class="save btn btn-secondary" value="save_continue">
{% fa6_icon "pen" 'fas' %} {% trans "Save and continue editing" %}
</button>
<button type="submit" name="save_action" class="save btn btn-primary" value="save">
{% fa6_icon "check" 'fas' %} {% trans "Save" %}
</button>
</div>
<a href="javascript:history.back()" class="btn btn-info">
{% fa6_icon "times" 'fas' %} {% trans "Cancel" %}
</a>
</form>
{% endblock %}
{% extends "admin/base_site.html" %}
{% load tags_AKModel %}
{% load i18n %}
{% load tz %}
{% block title %}{% trans "Status" %}: {{event}}{% endblock %}
{% block content %}
{% timezone event.timezone %}
<h2><a href="{% url 'admin:AKModel_event_change' event.pk %}">{{event}}</a></h2>
<h5>{{ event.start }} - {{ event.end }}</h5>
<div class="row">
<div class="col-md-8">
<h3 class="block-header">{% trans "Categories" %}</h3>
{% if event.akcategory_set.count == 0 %}
<p class="text-danger">{% trans "No categories yet" %}</p>
{% else %}
<p>
{{ event.akcategory_set.count }}:
{% for category in event.akcategory_set.all %}
{% if forloop.counter0 > 0 %}
&middot;
{% endif %}
<a href="{% url 'admin:AKModel_akcategory_change' category.pk %}">{{ category }}</a>
({{ category.ak_set.count }})
{% endfor %}
</p>
{% endif %}
<a class="btn btn-success" href="{% url 'admin:AKModel_akcategory_add' %}">{% trans "Add category" %}</a>
<h3 class="block-header">{% trans "Rooms" %}</h3>
{% if event.room_set.count == 0 %}
<p class="text-danger">{% trans "No rooms yet" %}</p>
{% else %}
<p>
{{ event.room_set.count }}:
{% for room in event.room_set.all %}
{% if forloop.counter0 > 0 %}
&middot;
{% endif %}
<a href="{% url 'admin:AKModel_room_change' room.pk %}">{{ room }}</a>
{% endfor %}
</p>
{% endif %}
<a class="btn btn-success" href="{% url 'admin:AKModel_room_add' %}">{% trans "Add Room" %}</a>
<h3 class="block-header">{% trans "AKs" %}</h3>
{% if event.ak_set.count == 0 %}
<p class="text-danger">{% trans "No AKs yet" %}</p>
{% else %}
<table>
<tbody>
<tr>
<td>{% trans "AKs" %}</td><td>{{ event.ak_set.count }}</td>
</tr>
<tr>
<td>{% trans "Slots" %}</td><td>{{ event.akslot_set.count }}</td>
</tr>
<tr>
<td>{% trans "Unscheduled Slots" %}</td><td>
{% if "AKScheduling"|check_app_installed %}
<a href="{% url 'admin:slots_unscheduled' event_slug=event.slug %}">
{{ unscheduled_slots_count }}
</a>
{% else %}
{{ unscheduled_slots_count }}
{% endif %}
</td>
</tr>
</tbody>
</table>
<a class="btn btn-success"
href="{% url 'admin:schedule' event_slug=event.slug %}">{% trans "Scheduling" %}</a>
{% if "AKScheduling | is_installed" %}
<a class="btn btn-success"
href="{% url 'admin:constraint-violations' slug=event.slug %}">{% trans "Constraint Violations" %} <span class="badge badge-secondary">{{ event.constraintviolation_set.count }}</span></a>
{% endif %}
<a class="btn btn-success"
href="{% url 'admin:tracks_manage' event_slug=event.slug %}">{% trans "Manage ak tracks" %}</a>
<a class="btn btn-success"
href="{% url 'admin:ak_csv_export' event_slug=event.slug %}">{% trans "Export AKs as CSV" %}</a>
<a class="btn btn-success"
href="{% url 'admin:ak_wiki_export' slug=event.slug %}">{% trans "Export AKs for Wiki" %}</a>
<a class="btn btn-success"
href="{% url 'admin:ak_slide_export' event_slug=event.slug %}">{% trans "Export AK Slides" %}</a>
{% endif %}
<h3 class="block-header">{% trans "Requirements" %}</h3>
{% if event.akrequirement_set.count == 0 %}
<p class="text-danger">{% trans "No requirements yet" %}</p>
{% else %}
<p>
{{ event.akrequirement_set.count }}:
{% for requirement in event.akrequirement_set.all %}
{% if forloop.counter0 > 0 %}
&middot;
{% endif %}
<a href="{% url 'admin:AKModel_akrequirement_change' requirement.pk %}">{{ requirement }}</a>
({{ requirement.ak_set.count }})
{% endfor %}
</p>
{% endif %}
<a class="btn btn-success" href="{% url 'admin:event_requirement_overview' event.slug %}">{% trans "Show AKs for requirements" %}</a>
<a class="btn btn-success" href="{% url 'admin:AKModel_akrequirement_add' %}">{% trans "Add Requirement" %}</a>
</div>
<div class="col-md-4">
<h3 class="block-header">{% trans "Messages" %}</h3>
{% include "admin/AKModel/render_ak_messages.html" %}
<a class="btn btn-danger" href="{% url 'admin:ak_delete_orga_messages' slug=event.slug %}">{% trans "Delete all messages" %}</a>
</div>
</div>
{% endtimezone %}
{% endblock %}
{% load i18n %}
{% load tags_AKModel %}
{% if event.ak_set.count == 0 %}
<p class="text-danger">{% trans "No AKs yet" %}</p>
{% else %}
<table>
<tbody>
<tr>
<td>{% trans "AKs" %}</td><td>{{ ak_count }}</td>
</tr>
<tr>
<td>{% trans "Slots" %}</td><td>{{ event.akslot_set.count }}</td>
</tr>
<tr>
<td>{% trans "Unscheduled Slots" %}</td><td>
{% if "AKScheduling"|check_app_installed %}
<a href="{% url 'admin:slots_unscheduled' event_slug=event.slug %}">
{{ unscheduled_slots_count }}
</a>
{% else %}
{{ unscheduled_slots_count }}
{% endif %}
</td>
</tr>
</tbody>
</table>
{% endif %}
{% load i18n %}
{% if event.akcategory_set.count == 0 %}
<p class="text-danger">{% trans "No categories yet" %}</p>
{% else %}
<ul>
{% for category in event.akcategory_set.all %}
<li>
<a href="{% url 'admin:AKModel_akcategory_change' category.pk %}">{{ category }}</a>
({{ category.ak_set.count }})
</li>
{% endfor %}
</ul>
{% endif %}
{% load i18n %}
{% load tz %}
{% timezone event.timezone %}
<h2><a href="{% url 'admin:AKModel_event_change' event.pk %}">{{event}}</a></h2>
<h5>{{ event.start }} - {{ event.end }}</h5>
<div class="form-check form-switch mt-2 mb-2">
<input type="checkbox" class="form-check-input" id="planPublishedSwitch"
{% if not event.plan_hidden %}checked{% endif %}
onclick="location.href='{% if event.plan_hidden %}{% url 'admin:plan-publish' %}{% else %}{% url 'admin:plan-unpublish' %}{% endif %}?pks={{event.pk}}';">
<label class="form-check-label" for="planPublishedSwitch">{% trans "Plan published?" %}</label>
</div>
{% endtimezone %}
{% load i18n %}
{% if event.akrequirement_set.count == 0 %}
<p class="text-danger">{% trans "No requirements yet" %}</p>
{% else %}
<ul>
{% for requirement in event.akrequirement_set.all %}
<li>
<a href="{% url 'admin:AKModel_akrequirement_change' requirement.pk %}">{{ requirement }}</a>
({{ requirement.ak_set.count }})
</li>
{% endfor %}
</ul>
{% endif %}
{% load i18n %}
{% if event.room_set.count == 0 %}
<p class="text-danger">{% trans "No rooms yet" %}</p>
{% else %}
<p>
{% for room in event.room_set.all %}
{% if forloop.counter0 > 0 %}
&middot;
{% endif %}
<a href="{% url 'admin:AKModel_room_change' room.pk %}">{{ room }}</a>
{% endfor %}
</p>
{% endif %}
{% extends "admin/base_site.html" %}
{% load tags_AKModel %}
{% load i18n %}
{% load tz %}
{% load fontawesome_6 %}
{% block title %}{% trans "Status" %}: {{ event }}{% endblock %}
{% block content %}
{% timezone event.timezone %}
<div class="row">
{% for widget in widgets %}
<div class="card border-{{ widget.status }} mb-3 me-2 col-xl-3 col-md-4 col-sm-6 p-0">
<div class="card-header">
{% if widget.actions %}
<div class="float-end">
<a style="cursor: pointer;" data-bs-toggle="dropdown" aria-expanded="false">
&nbsp;{% fa6_icon "ellipsis-vertical" %}&nbsp;
</a>
<ul class="dropdown-menu dropdown-menu-end">
{% for action in widget.actions %}
<li class="dropdown-item">
<a href="{{ action.url }}">{{ action.text }}</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{{ widget.title }}
</div>
<div class="card-body">
{{ widget.body }}
</div>
</div>
{% endfor %}
</div>
{% endtimezone %}
{% endblock %}
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<a href="{% url 'admin:AKModel_event_change' event.pk %}">{{ event }}</a> <a href="{% url 'admin:AKModel_event_change' event.pk %}">{{ event }}</a>
({{ event.start|timezone:event.timezone|date:"d.m.y" }} - ({{ event.start|timezone:event.timezone|date:"d.m.y" }} -
{{ event.end|timezone:event.timezone|date:"d.m.y" }}) &middot; {{ event.end|timezone:event.timezone|date:"d.m.y" }}) &middot;
<a href="{% url 'admin:event_status' slug=event.slug %}">{% trans "Status" %}</a> &middot; <a href="{% url 'admin:event_status' event_slug=event.slug %}">{% trans "Status" %}</a> &middot;
<a href="{% url 'admin:schedule' event_slug=event.slug %}">{% trans "Scheduling" %}</a> <a href="{% url 'admin:schedule' event_slug=event.slug %}">{% trans "Scheduling" %}</a>
</li> </li>
{% endfor %} {% endfor %}
......
{% extends "admin/login.html" %}
{% load i18n static %}
{% block content %}
{% if form.errors and not form.non_field_errors %}
<p class="errornote">
{% if form.errors.items|length == 1 %}{% translate "Please correct the error below." %}{% else %}{% translate "Please correct the errors below." %}{% endif %}
</p>
{% endif %}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="errornote">
{{ error }}
</p>
{% endfor %}
{% endif %}
<div id="content-main">
{% if user.is_authenticated %}
<p class="errornote">
{% blocktranslate trimmed %}
You are authenticated as {{ username }}, but are not authorized to
access this page. Would you like to login to a different account?
{% endblocktranslate %}
</p>
{% endif %}
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
<div class="form-row">
{{ form.username.errors }}
{{ form.username.label_tag }} {{ form.username }}
</div>
<div class="form-row">
{{ form.password.errors }}
{{ form.password.label_tag }} {{ form.password }}
<input type="hidden" name="next" value="{{ next }}">
</div>
{% url 'admin_password_reset' as password_reset_url %}
{% if password_reset_url %}
<div class="password-reset-link">
<a href="{{ password_reset_url }}">{% translate 'Forgotten your password or username?' %}</a>
</div>
{% endif %}
<div class="submit-row">
<input type="submit" value="{% translate 'Log in' %}">
</div>
<div class="text-center mt-3">
<a href="{% url "registration_register" %}">{% translate 'Register' %}</a>
</div>
</form>
</div>
{% endblock %}
from django import template from django import template
from django.apps import apps 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.templatetags.static import static
from django.template.defaultfilters import date
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)
...@@ -39,3 +72,47 @@ def wiki_owners_export(owners, event): ...@@ -39,3 +72,47 @@ def wiki_owners_export(owners, event):
return str(owner) return str(owner)
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()
@register.simple_tag
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(
'<link href="{}" rel="stylesheet" media="all">', stylesheet) for stylesheet in css))
@register.simple_tag
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(
'<script type="text/javascript" src="{}"></script>', static('fontawesome_6/js/django-fontawesome.js')
))
# Create your tests here. import traceback
from typing import List
from django.contrib.auth import get_user_model
from django.contrib.messages import get_messages
from django.contrib.messages.storage.base import Message
from django.test import TestCase
from django.urls import reverse_lazy, reverse
from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKRequirement, AK, Room, AKSlot, AKOrgaMessage, \
ConstraintViolation, DefaultSlot
class BasicViewTests:
"""
Parent class for "standard" tests of views
Provided with a list of views and arguments (if necessary), this will test that views
- render correctly without errors
- are only reachable with the correct rights (neither too freely nor too restricted)
To do this, the test creates sample users, fixtures are loaded automatically by the django test framework.
It also provides helper functions, e.g., to check for correct messages to the user or more simply generate
the URLs to test
In this class, methods from :class:`TestCase` will be called at multiple places event though TestCase is not a
parent of this class but has to be included as parent in concrete implementations of this class seperately.
It however still makes sense to treat this class as some kind of mixin and not implement it as a child of TestCase,
since the test framework does not understand the concept of abstract test definitions and would handle this class
as real test case otherwise, distorting the test results.
"""
# pylint: disable=no-member
VIEWS = []
APP_NAME = ''
VIEWS_STAFF_ONLY = []
EDIT_TESTCASES = []
def setUp(self): # pylint: disable=invalid-name
"""
Setup testing by creating sample users
"""
user_model = get_user_model()
self.staff_user = user_model.objects.create(
username='Test Staff User', email='teststaff@example.com', password='staffpw',
is_staff=True, is_active=True
)
self.admin_user = user_model.objects.create(
username='Test Admin User', email='testadmin@example.com', password='adminpw',
is_staff=True, is_superuser=True, is_active=True
)
self.deactivated_user = user_model.objects.create(
username='Test Deactivated User', email='testdeactivated@example.com', password='deactivatedpw',
is_staff=True, is_active=False
)
def _name_and_url(self, view_name):
"""
Get full view name (with prefix if there is one) and url from raw view definition
:param view_name: raw definition of a view
:type view_name: (str, dict)
:return: full view name with prefix if applicable, url of the view
:rtype: str, str
"""
view_name_with_prefix = f"{self.APP_NAME}:{view_name[0]}" if self.APP_NAME != "" else view_name[0]
url = reverse(view_name_with_prefix, kwargs=view_name[1])
return view_name_with_prefix, url
def _assert_message(self, response, expected_message, msg_prefix=""):
"""
Assert that the correct message is shown and cause test to fail if not
:param response: response to check
:param expected_message: message that should be shown
:param msg_prefix: prefix for the error message when test fails
"""
messages:List[Message] = list(get_messages(response.wsgi_request))
msg_count = "No message shown to user"
msg_content = "Wrong message, expected '{expected_message}'"
if msg_prefix != "":
msg_count = f"{msg_prefix}: {msg_count}"
msg_content = f"{msg_prefix}: {msg_content}"
# Check that the last message correctly reports the issue
# (there might be more messages from previous calls that were not already rendered)
self.assertGreater(len(messages), 0, msg=msg_count)
self.assertEqual(messages[-1].message, expected_message, msg=msg_content)
def test_views_for_200(self):
"""
Test the list of public views (as specified in "VIEWS") for error-free rendering
"""
for view_name in self.VIEWS:
view_name_with_prefix, url = self._name_and_url(view_name)
try:
response = self.client.get(url)
self.assertEqual(response.status_code, 200, msg=f"{view_name_with_prefix} ({url}) broken")
except Exception: # pylint: disable=broad-exception-caught
self.fail(f"An error occurred during rendering of {view_name_with_prefix} ({url}):"
f"\n\n{traceback.format_exc()}")
def test_access_control_staff_only(self):
"""
Test whether internal views (as specified in "VIEWS_STAFF_ONLY" are visible to staff users and staff users only
"""
# Not logged in? Views should not be visible
self.client.logout()
for view_name_info in self.VIEWS_STAFF_ONLY:
expected_response_code = 302 if len(view_name_info) == 2 else view_name_info[2]
view_name_with_prefix, url = self._name_and_url(view_name_info)
response = self.client.get(url)
self.assertEqual(response.status_code, expected_response_code,
msg=f"{view_name_with_prefix} ({url}) accessible by non-staff")
# Logged in? Views should be visible
self.client.force_login(self.staff_user)
for view_name_info in self.VIEWS_STAFF_ONLY:
view_name_with_prefix, url = self._name_and_url(view_name_info)
try:
response = self.client.get(url)
self.assertEqual(response.status_code, 200,
msg=f"{view_name_with_prefix} ({url}) should be accessible for staff (but isn't)")
except Exception: # pylint: disable=broad-exception-caught
self.fail(f"An error occurred during rendering of {view_name_with_prefix} ({url}):"
f"\n\n{traceback.format_exc()}")
# Disabled user? Views should not be visible
self.client.force_login(self.deactivated_user)
for view_name_info in self.VIEWS_STAFF_ONLY:
expected_response_code = 302 if len(view_name_info) == 2 else view_name_info[2]
view_name_with_prefix, url = self._name_and_url(view_name_info)
response = self.client.get(url)
self.assertEqual(response.status_code, expected_response_code,
msg=f"{view_name_with_prefix} ({url}) still accessible for deactivated user")
def _to_sendable_value(self, val):
"""
Create representation sendable via POST from form data
Needed to automatically check create, update and delete views
:param val: value to prepare
:type val: any
:return: prepared value (normally either raw value or primary key of complex object)
"""
if isinstance(val, list):
return [e.pk for e in val]
if type(val) == "RelatedManager": # pylint: disable=unidiomatic-typecheck
return [e.pk for e in val.all()]
return val
def test_submit_edit_form(self):
"""
Test edit forms (as specified in "EDIT_TESTCASES") in the most simple way (sending them again unchanged)
"""
for testcase in self.EDIT_TESTCASES:
self._test_submit_edit_form(testcase)
def _test_submit_edit_form(self, testcase):
"""
Test a single edit form by rendering and sending it again unchanged
This will test for correct rendering, dispatching/redirecting, messages and access control handling
:param testcase: details of the form to test
"""
name, url = self._name_and_url((testcase["view"], testcase["kwargs"]))
form_name = testcase.get("form_name", "form")
expected_code = testcase.get("expected_code", 302)
if "target_view" in testcase.keys():
kwargs = testcase.get("target_kwargs", testcase["kwargs"])
_, target_url = self._name_and_url((testcase["target_view"], kwargs))
else:
target_url = url
expected_message = testcase.get("expected_message", "")
admin_user = testcase.get("admin", False)
if admin_user:
self.client.force_login(self.admin_user)
else:
self.client.logout()
response = self.client.get(url)
self.assertEqual(response.status_code, 200, msg=f"{name}: Could not load edit form via GET ({url})")
form = response.context[form_name]
data = {k:self._to_sendable_value(v) for k,v in form.initial.items()}
response = self.client.post(url, data=data)
if expected_code == 200:
self.assertEqual(response.status_code, 200, msg=f"{name}: Did not return 200 ({url}")
elif expected_code == 302:
self.assertRedirects(response, target_url, msg_prefix=f"{name}: Did not redirect ({url} -> {target_url}")
if expected_message != "":
self._assert_message(response, expected_message, msg_prefix=f"{name}")
class ModelViewTests(BasicViewTests, TestCase):
"""
Basic view test cases for views from AKModel plus some custom tests
"""
fixtures = ['model.json']
ADMIN_MODELS = [
(Event, 'event'), (AKOwner, 'akowner'), (AKCategory, 'akcategory'), (AKTrack, 'aktrack'),
(AKRequirement, 'akrequirement'), (AK, 'ak'), (Room, 'room'), (AKSlot, 'akslot'),
(AKOrgaMessage, 'akorgamessage'), (ConstraintViolation, 'constraintviolation'),
(DefaultSlot, 'defaultslot')
]
VIEWS_STAFF_ONLY = [
('admin:index', {}),
('admin:event_status', {'event_slug': 'kif42'}),
('admin:event_requirement_overview', {'event_slug': 'kif42'}),
('admin:ak_csv_export', {'event_slug': 'kif42'}),
('admin:ak_wiki_export', {'slug': 'kif42'}),
('admin:ak_delete_orga_messages', {'event_slug': 'kif42'}),
('admin:ak_slide_export', {'event_slug': 'kif42'}),
('admin:default-slots-editor', {'event_slug': 'kif42'}),
('admin:room-import', {'event_slug': 'kif42'}),
('admin:new_event_wizard_start', {}),
]
EDIT_TESTCASES = [
{'view': 'admin:default-slots-editor', 'kwargs': {'event_slug': 'kif42'}, "admin": True},
]
def test_admin(self):
"""
Test basic admin functionality (displaying and interacting with model instances)
"""
self.client.force_login(self.admin_user)
for model in self.ADMIN_MODELS:
# Special treatment for a subset of views (where we exchanged default functionality, e.g., create views)
if model[1] == "event":
_, url = self._name_and_url(('admin:new_event_wizard_start', {}))
elif model[1] == "room":
_, url = self._name_and_url(('admin:room-new', {}))
# Otherwise, just call the creation form view
else:
_, url = self._name_and_url((f'admin:AKModel_{model[1]}_add', {}))
response = self.client.get(url)
self.assertEqual(response.status_code, 200, msg=f"Add form for model {model[1]} ({url}) broken")
for model in self.ADMIN_MODELS:
# Test the update view using the first existing instance of each model
m = model[0].objects.first()
if m is not None:
_, url = self._name_and_url(
(f'admin:AKModel_{model[1]}_change', {'object_id': m.pk})
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200, msg=f"Edit form for model {model[1]} ({url}) broken")
def test_wiki_export(self):
"""
Test wiki export
This will test whether the view renders at all and whether the export list contains the correct AKs
"""
self.client.force_login(self.admin_user)
export_url = reverse_lazy("admin:ak_wiki_export", kwargs={'slug': 'kif42'})
response = self.client.get(export_url)
self.assertEqual(response.status_code, 200, "Export not working at all")
export_count = 0
for _, aks in response.context["categories_with_aks"]:
for ak in aks:
self.assertEqual(ak.include_in_export, True,
f"AK with export flag set to False (pk={ak.pk}) included in export")
self.assertNotEqual(ak.pk, 1, "AK known to be excluded from export (PK 1) included in export")
export_count += 1
self.assertEqual(export_count, AK.objects.filter(event_id=2, include_in_export=True).count(),
"Wiki export contained the wrong number of AKs")
...@@ -3,23 +3,30 @@ from django.apps import apps ...@@ -3,23 +3,30 @@ from django.apps import apps
from django.urls import include, path from django.urls import include, path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from AKModel import views import AKModel.views.api
from AKModel.views import NewEventWizardStartView, NewEventWizardSettingsView, NewEventWizardPrepareImportView, \ from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, DefaultSlotEditorView, \
NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, EventStatusView, \ AKsByUserView
AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView, export_slides from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView
from AKModel.views.event_wizard import NewEventWizardStartView, NewEventWizardPrepareImportView, \
NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, NewEventWizardSettingsView
from AKModel.views.room import RoomBatchCreationView
from AKModel.views.status import EventStatusView
# Register basic API views/endpoints
api_router = DefaultRouter() api_router = DefaultRouter()
api_router.register('akowner', views.AKOwnerViewSet, basename='AKOwner') api_router.register('akowner', AKModel.views.api.AKOwnerViewSet, basename='AKOwner')
api_router.register('akcategory', views.AKCategoryViewSet, basename='AKCategory') api_router.register('akcategory', AKModel.views.api.AKCategoryViewSet, basename='AKCategory')
api_router.register('aktrack', views.AKTrackViewSet, basename='AKTrack') api_router.register('aktrack', AKModel.views.api.AKTrackViewSet, basename='AKTrack')
api_router.register('ak', views.AKViewSet, basename='AK') api_router.register('ak', AKModel.views.api.AKViewSet, basename='AK')
api_router.register('room', views.RoomViewSet, basename='Room') api_router.register('room', AKModel.views.api.RoomViewSet, basename='Room')
api_router.register('akslot', views.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 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,10 +35,13 @@ if apps.is_installed("AKScheduling"): ...@@ -28,10 +35,13 @@ 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 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 = [
...@@ -41,16 +51,20 @@ event_specific_paths.extend(extra_paths) ...@@ -41,16 +51,20 @@ 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>/',
include(event_specific_paths) include(event_specific_paths)
), ),
path('user/', views.UserView.as_view(), name="user"), path('user/', AKModel.views.manage.UserView.as_view(), name="user"),
] ]
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"),
...@@ -71,16 +85,26 @@ def get_admin_urls_event_wizard(admin_site): ...@@ -71,16 +85,26 @@ 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: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: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: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/', export_slides, 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 itertools import zip_longest
from django.contrib import admin, messages
from django.contrib.admin.views.decorators import staff_member_required
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView, DetailView, ListView, DeleteView, CreateView, FormView, UpdateView
from django_tex.shortcuts import render_to_pdf
from rest_framework import viewsets, permissions, mixins
from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \
NewEventWizardImportForm, NewEventWizardActivateForm
from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement
from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \
AKOwnerSerializer
class EventSlugMixin:
"""
Mixin to handle views with event slugs
"""
event = None
def _load_event(self):
# Find event based on event slug
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()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self._load_event()
return super().post(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
self._load_event()
return super().list(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
self._load_event()
return super().create(request, *args, **kwargs)
def dispatch(self, request, *args, **kwargs):
if self.event is None:
self._load_event()
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
# Add event to context (to make it accessible in templates)
context["event"] = self.event
return context
class FilterByEventSlugMixin(EventSlugMixin):
"""
Mixin to filter different querysets based on a event slug from the request url
"""
def get_queryset(self):
# Filter current queryset based on url event slug or return 404 if event slug is invalid
return super().get_queryset().filter(event=self.event)
class AdminViewMixin:
site_url = ''
title = ''
def get_context_data(self, **kwargs):
extra = admin.site.each_context(self.request)
extra.update(super().get_context_data(**kwargs))
if self.site_url != '':
extra["site_url"] = self.site_url
if self.title != '':
extra["title"] = self.title
return extra
class AKOwnerViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = AKOwnerSerializer
def get_queryset(self):
return AKOwner.objects.filter(event=self.event)
class AKCategoryViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = AKCategorySerializer
def get_queryset(self):
return AKCategory.objects.filter(event=self.event)
class AKTrackViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = AKTrackSerializer
def get_queryset(self):
return AKTrack.objects.filter(event=self.event)
class AKViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin,
viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = AKSerializer
def get_queryset(self):
return AK.objects.filter(event=self.event)
class RoomViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = RoomSerializer
def get_queryset(self):
return Room.objects.filter(event=self.event)
class AKSlotViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = AKSlotSerializer
def get_queryset(self):
return AKSlot.objects.filter(event=self.event)
class UserView(TemplateView):
template_name = "AKModel/user.html"
class EventStatusView(AdminViewMixin, DetailView):
template_name = "admin/AKModel/status.html"
model = Event
context_object_name = "event"
title = _("Event Status")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["unscheduled_slots_count"] = context["event"].akslot_set.filter(start=None).count
context["site_url"] = reverse_lazy("dashboard:dashboard_event", kwargs={'slug': context["event"].slug})
context["ak_messages"] = AKOrgaMessage.objects.filter(ak__event=context["event"])
return context
class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView):
model = AKRequirement
context_object_name = "requirements"
title = _("Requirements for Event")
template_name = "admin/AKModel/requirements_overview.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["event"] = self.event
context["site_url"] = reverse_lazy("dashboard:dashboard_event", kwargs={'slug': context["event"].slug})
return context
class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
template_name = "admin/AKModel/ak_csv_export.html"
model = AKSlot
context_object_name = "slots"
title = _("AK CSV Export")
def get_queryset(self):
return super().get_queryset().order_by("ak__track")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
class AKWikiExportView(AdminViewMixin, DetailView):
template_name = "admin/AKModel/wiki_export.html"
model = Event
context_object_name = "event"
title = _("AK Wiki Export")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
categories_with_aks, ak_wishes = context["event"].get_categories_with_aks(wishes_seperately=True)
context["categories_with_aks"] = [(category.name, ak_list) for category, ak_list in categories_with_aks]
context["categories_with_aks"].append((_("Wishes"), ak_wishes))
return context
class AKMessageDeleteView(AdminViewMixin, DeleteView):
model = Event
template_name = "admin/AKModel/message_delete.html"
def get_orga_messages_for_event(self, event):
return AKOrgaMessage.objects.filter(ak__event=event)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["ak_messages"] = self.get_orga_messages_for_event(self.get_object())
return context
def post(self, request, *args, **kwargs):
self.get_orga_messages_for_event(self.get_object()).delete()
messages.add_message(self.request, messages.SUCCESS, _("AK Orga Messages successfully deleted"))
return HttpResponseRedirect(reverse_lazy('admin:event_status', kwargs={'slug': self.get_object().slug}))
class WizardViewMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["wizard_step"] = self.wizard_step
context["wizard_steps"] = [
_("Start"),
_("Settings"),
_("Event created, Prepare Import"),
_("Import categories & requirements"),
_("Activate?"),
_("Finish")
]
context["wizard_step_text"] = context["wizard_steps"][self.wizard_step - 1]
context["wizard_steps_total"] = len(context["wizard_steps"])
return context
class NewEventWizardStartView(AdminViewMixin, WizardViewMixin, CreateView):
model = Event
form_class = NewEventWizardStartForm
template_name = "admin/AKModel/event_wizard/start.html"
wizard_step = 1
class NewEventWizardSettingsView(AdminViewMixin, WizardViewMixin, CreateView):
model = Event
form_class = NewEventWizardSettingsForm
template_name = "admin/AKModel/event_wizard/settings.html"
wizard_step = 2
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["timezone"] = context["form"].cleaned_data["timezone"]
return context
def get_success_url(self):
return reverse_lazy("admin:new_event_wizard_prepare_import", kwargs={"event_slug": self.object.slug})
class NewEventWizardPrepareImportView(WizardViewMixin, EventSlugMixin, FormView):
form_class = NewEventWizardPrepareImportForm
template_name = "admin/AKModel/event_wizard/created_prepare_import.html"
wizard_step = 3
def form_valid(self, form):
# Selected a valid event to import from? Use this to go to next step of wizard
return redirect("admin:new_event_wizard_import", event_slug=self.event.slug,
import_slug=form.cleaned_data["import_event"].slug)
class NewEventWizardImportView(EventSlugMixin, WizardViewMixin, FormView):
form_class = NewEventWizardImportForm
template_name = "admin/AKModel/event_wizard/import.html"
wizard_step = 4
def get_initial(self):
initial = super().get_initial()
initial["import_event"] = Event.objects.get(slug=self.kwargs["import_slug"])
return initial
def form_valid(self, form):
for import_type in ["import_categories", "import_requirements"]:
for import_obj in form.cleaned_data.get(import_type):
# clone existing entry
try:
import_obj.event = self.event
import_obj.pk = None
import_obj.save()
messages.add_message(self.request, messages.SUCCESS, _("Copied '%(obj)s'" % {'obj': import_obj}))
except BaseException as e:
messages.add_message(self.request, messages.ERROR,
_("Could not copy '%(obj)s' (%(error)s)" % {'obj': import_obj,
"error": str(e)}))
return redirect("admin:new_event_wizard_activate", slug=self.event.slug)
class NewEventWizardActivateView(WizardViewMixin, UpdateView):
model = Event
template_name = "admin/AKModel/event_wizard/activate.html"
form_class = NewEventWizardActivateForm
wizard_step = 5
def get_success_url(self):
return reverse_lazy("admin:new_event_wizard_finish", kwargs={"slug": self.object.slug})
class NewEventWizardFinishView(WizardViewMixin, DetailView):
model = Event
template_name = "admin/AKModel/event_wizard/finish.html"
wizard_step = 6
@staff_member_required
def export_slides(request, event_slug):
template_name = 'admin/AKModel/export/slides.tex'
event = get_object_or_404(Event, slug=event_slug)
NEXT_AK_LIST_LENGTH = int(request.GET["num_next"]) if "num_next" in request.GET else 3
RESULT_PRESENTATION_MODE = True if "presentation_mode" in request.GET else False
translations = {
'symbols': _("Symbols"),
'who': _("Who?"),
'duration': _("Duration(s)"),
'reso': _("Reso intention?"),
'category': _("Category (for Wishes)"),
'wishes': _("Wishes"),
}
def build_ak_list_with_next_aks(ak_list):
next_aks_list = zip_longest(*[ak_list[i + 1:] for i in range(NEXT_AK_LIST_LENGTH)], fillvalue=None)
return [(ak, next_aks) for ak, next_aks in zip_longest(ak_list, next_aks_list, fillvalue=list())]
categories_with_aks, ak_wishes = event.get_categories_with_aks(wishes_seperately=True, filter=lambda
ak: not RESULT_PRESENTATION_MODE or (ak.present or (ak.present is None and ak.category.present_by_default)))
context = {
'title': event.name,
'categories_with_aks': [(category, build_ak_list_with_next_aks(ak_list)) for category, ak_list in
categories_with_aks],
'subtitle': _("AKs"),
"wishes": build_ak_list_with_next_aks(ak_wishes),
"translations": translations,
"result_presentation_mode": RESULT_PRESENTATION_MODE,
}
return render_to_pdf(request, template_name, context, filename='slides.pdf')