Skip to content
Snippets Groups Projects
Commit 51edf812 authored by Nadja Geisler's avatar Nadja Geisler :sunny:
Browse files

Merge branch 'main' into scheduling-constraints

parents 7d48af05 8d36e628
No related branches found
No related tags found
1 merge request!99Constraint Violation checking & visualization
Pipeline #22729 passed
Showing
with 506 additions and 88 deletions
...@@ -24,4 +24,4 @@ before_script: ...@@ -24,4 +24,4 @@ before_script:
test: test:
script: script:
- source venv/bin/activate - source venv/bin/activate
- python manage.py test --settings AKPlanning.settings_ci - python manage.py test --settings AKPlanning.settings_ci --keepdb
# Create your tests here. import pytz
from django.apps import apps
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils.timezone import now
from AKDashboard.models import DashboardButton
from AKModel.models import Event, AK, AKCategory
class DashboardTests(TestCase):
@classmethod
def setUpTestData(cls):
super().setUpTestData()
cls.event = Event.objects.create(
name="Dashboard Test Event",
slug="dashboardtest",
timezone=pytz.utc,
start=now(),
end=now(),
active=True,
plan_hidden=False,
)
cls.default_category = AKCategory.objects.create(
name="Test Category",
event=cls.event,
)
def test_dashboard_view(self):
url = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_nonexistent_dashboard_view(self):
url = reverse('dashboard:dashboard_event', kwargs={"slug": "nonexistent-event"})
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
@override_settings(DASHBOARD_SHOW_RECENT=True)
def test_history(self):
url = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
# History should be empty
response = self.client.get(url)
self.assertQuerysetEqual(response.context["recent_changes"], [])
AK.objects.create(
name="Test AK",
category=self.default_category,
event=self.event,
)
# History should now contain one AK (Test AK)
response = self.client.get(url)
self.assertEqual(len(response.context["recent_changes"]), 1)
self.assertEqual(response.context["recent_changes"][0]['text'], "New AK: Test AK.")
def test_public(self):
url_dashboard_index = reverse('dashboard:dashboard')
url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
# Non-Public event (should not be part of the global dashboard
# but should have an individual dashboard page for those knowing the url)
self.event.public = False
self.event.save()
response = self.client.get(url_dashboard_index)
print(response)
self.assertEqual(response.status_code, 200)
self.assertFalse(self.event in response.context["events"])
response = self.client.get(url_event_dashboard)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["event"], self.event)
# Public event -- should be part of the global dashboard
self.event.public = True
self.event.save()
response = self.client.get(url_dashboard_index)
self.assertEqual(response.status_code, 200)
self.assertTrue(self.event in response.context["events"])
def test_active(self):
url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
if apps.is_installed('AKSubmission'):
# Non-active event -> No submission
self.event.active = False
self.event.save()
response = self.client.get(url_event_dashboard)
self.assertNotContains(response, "AK Submission")
# Active event -> Submission should be open
self.event.active = True
self.event.save()
response = self.client.get(url_event_dashboard)
self.assertContains(response, "AK Submission")
def test_plan_hidden(self):
url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
if apps.is_installed('AKPlan'):
# Plan hidden? No buttons should show up
self.event.plan_hidden = True
self.event.save()
response = self.client.get(url_event_dashboard)
self.assertNotContains(response, "Current AKs")
self.assertNotContains(response, "AK Wall")
# Plan not hidden?
# Buttons for current AKs and AK Wall should be on the page
self.event.plan_hidden = False
self.event.save()
response = self.client.get(url_event_dashboard)
self.assertContains(response, "Current AKs")
self.assertContains(response, "AK Wall")
def test_dashboard_buttons(self):
url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
response = self.client.get(url_event_dashboard)
self.assertNotContains(response, "Dashboard Button Test")
DashboardButton.objects.create(
text="Dashboard Button Test",
event=self.event
)
response = self.client.get(url_event_dashboard)
self.assertContains(response, "Dashboard Button Test")
...@@ -44,7 +44,8 @@ class EventAdmin(admin.ModelAdmin): ...@@ -44,7 +44,8 @@ class EventAdmin(admin.ModelAdmin):
def status_url(self, obj): def status_url(self, obj):
return format_html("<a href='{url}'>{text}</a>", return format_html("<a href='{url}'>{text}</a>",
url=reverse_lazy('admin:event_status', kwargs={'slug': obj.slug}), text=_("Status")) url=reverse_lazy('admin:event_status', kwargs={'slug': obj.slug}), text=_("Status"))
status_url.short_description = text=_("Status")
status_url.short_description = _("Status")
def get_form(self, request, obj=None, change=False, **kwargs): def get_form(self, request, obj=None, change=False, **kwargs):
# Use timezone of event # Use timezone of event
...@@ -205,7 +206,6 @@ class RoomForm(AvailabilitiesFormMixin, forms.ModelForm): ...@@ -205,7 +206,6 @@ class RoomForm(AvailabilitiesFormMixin, forms.ModelForm):
self.fields["properties"].queryset = AKRequirement.objects.filter(event=self.instance.event) self.fields["properties"].queryset = AKRequirement.objects.filter(event=self.instance.event)
@admin.register(Room) @admin.register(Room)
class RoomAdmin(admin.ModelAdmin): class RoomAdmin(admin.ModelAdmin):
model = Room model = Room
...@@ -265,6 +265,7 @@ class AKSlotAdmin(admin.ModelAdmin): ...@@ -265,6 +265,7 @@ class AKSlotAdmin(admin.ModelAdmin):
link = f"<a href={reverse('submit:ak_detail', args=[akslot.event.slug, akslot.ak.pk])}>{str(akslot.ak)}</a>" link = f"<a href={reverse('submit:ak_detail', args=[akslot.event.slug, akslot.ak.pk])}>{str(akslot.ak)}</a>"
return mark_safe(link) return mark_safe(link)
return "-" return "-"
ak_details_link.short_description = _('AK Details') ak_details_link.short_description = _('AK Details')
......
# environment.py
import re
from django_tex.environment import environment
# Used to filter all very special UTF-8 chars that are probably not contained in the LaTeX fonts
# and would hence cause compilation errors
utf8_replace_pattern = re.compile(u'[^\u0000-\u206F]', re.UNICODE)
def latex_escape_utf8(value):
"""
Escape latex special chars and remove invalid utf-8 values
:param value: string to escape
:type value: str
:return: escaped string
:rtype: str
"""
return utf8_replace_pattern.sub('', value).replace('&', '\&').replace('_', '\_').replace('#', '\#').replace('$',
'\$').replace(
'%', '\%').replace('{', '\{').replace('}', '\}')
def improved_tex_environment(**options):
env = environment(**options)
env.filters.update({
'latex_escape_utf8': latex_escape_utf8,
})
return env
...@@ -2,7 +2,7 @@ msgid "" ...@@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-04-29 22:48+0000\n" "POT-Creation-Date: 2021-05-08 18:07+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -11,7 +11,7 @@ msgstr "" ...@@ -11,7 +11,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: AKModel/admin.py:66 AKModel/admin.py:67 #: AKModel/admin.py:69 AKModel/admin.py:70
#: AKModel/templates/admin/AKModel/event_wizard/activate.html:32 #: AKModel/templates/admin/AKModel/event_wizard/activate.html:32
#: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:48 #: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:48
#: AKModel/templates/admin/AKModel/event_wizard/finish.html:21 #: AKModel/templates/admin/AKModel/event_wizard/finish.html:21
...@@ -21,23 +21,23 @@ msgstr "" ...@@ -21,23 +21,23 @@ msgstr ""
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: AKModel/admin.py:153 #: AKModel/admin.py:156
msgid "Wish" msgid "Wish"
msgstr "AK-Wunsch" msgstr "AK-Wunsch"
#: AKModel/admin.py:159 #: AKModel/admin.py:162
msgid "Is wish" msgid "Is wish"
msgstr "Ist ein Wunsch" msgstr "Ist ein Wunsch"
#: AKModel/admin.py:160 #: AKModel/admin.py:163
msgid "Is not a wish" msgid "Is not a wish"
msgstr "Ist kein Wunsch" msgstr "Ist kein Wunsch"
#: AKModel/admin.py:187 #: AKModel/admin.py:209
msgid "Export to wiki syntax" msgid "Export to wiki syntax"
msgstr "In Wiki-Syntax exportieren" msgstr "In Wiki-Syntax exportieren"
#: AKModel/admin.py:283 #: AKModel/admin.py:317
msgid "AK Details" msgid "AK Details"
msgstr "AK-Details" msgstr "AK-Details"
...@@ -170,7 +170,7 @@ msgstr "Zeitzone" ...@@ -170,7 +170,7 @@ msgstr "Zeitzone"
msgid "Time Zone where this event takes place in" msgid "Time Zone where this event takes place in"
msgstr "Zeitzone in der das Event stattfindet" msgstr "Zeitzone in der das Event stattfindet"
#: AKModel/models.py:25 AKModel/views.py:206 #: AKModel/models.py:25 AKModel/views.py:209
msgid "Start" msgid "Start"
msgstr "Start" msgstr "Start"
...@@ -430,7 +430,7 @@ msgstr "AK präsentieren" ...@@ -430,7 +430,7 @@ msgstr "AK präsentieren"
msgid "Present results of this AK" msgid "Present results of this AK"
msgstr "Die Ergebnisse dieses AKs vorstellen" msgstr "Die Ergebnisse dieses AKs vorstellen"
#: AKModel/models.py:218 AKModel/templates/admin/AKModel/status.html:85 #: AKModel/models.py:218 AKModel/templates/admin/AKModel/status.html:87
msgid "Requirements" msgid "Requirements"
msgstr "Anforderungen" msgstr "Anforderungen"
...@@ -485,7 +485,7 @@ msgstr "Anzahl Personen, die online Interesse bekundet haben" ...@@ -485,7 +485,7 @@ msgstr "Anzahl Personen, die online Interesse bekundet haben"
#: AKModel/models.py:240 AKModel/models.py:440 #: AKModel/models.py:240 AKModel/models.py:440
#: AKModel/templates/admin/AKModel/status.html:49 #: AKModel/templates/admin/AKModel/status.html:49
#: AKModel/templates/admin/AKModel/status.html:56 #: AKModel/templates/admin/AKModel/status.html:56 AKModel/views.py:310
msgid "AKs" msgid "AKs"
msgstr "AKs" msgstr "AKs"
...@@ -761,7 +761,7 @@ msgid "Successfully imported.<br><br>Do you want to activate your event now?" ...@@ -761,7 +761,7 @@ msgid "Successfully imported.<br><br>Do you want to activate your event now?"
msgstr "Erfolgreich importiert.<br><br>Soll das Event jetzt aktiviert werden?" msgstr "Erfolgreich importiert.<br><br>Soll das Event jetzt aktiviert werden?"
#: AKModel/templates/admin/AKModel/event_wizard/activate.html:27 #: AKModel/templates/admin/AKModel/event_wizard/activate.html:27
#: AKModel/views.py:211 #: AKModel/views.py:214
msgid "Finish" msgid "Finish"
msgstr "Abschluss" msgstr "Abschluss"
...@@ -845,7 +845,7 @@ msgid "No AKs with this requirement" ...@@ -845,7 +845,7 @@ msgid "No AKs with this requirement"
msgstr "Kein AK mit dieser Anforderung" msgstr "Kein AK mit dieser Anforderung"
#: AKModel/templates/admin/AKModel/requirements_overview.html:45 #: AKModel/templates/admin/AKModel/requirements_overview.html:45
#: AKModel/templates/admin/AKModel/status.html:101 #: AKModel/templates/admin/AKModel/status.html:103
msgid "Add Requirement" msgid "Add Requirement"
msgstr "Anforderung hinzufügen" msgstr "Anforderung hinzufügen"
...@@ -898,19 +898,23 @@ msgstr "AKs als CSV exportieren" ...@@ -898,19 +898,23 @@ msgstr "AKs als CSV exportieren"
msgid "Export AKs for Wiki" msgid "Export AKs for Wiki"
msgstr "AKs im Wiki-Format exportieren" msgstr "AKs im Wiki-Format exportieren"
#: AKModel/templates/admin/AKModel/status.html:87 #: AKModel/templates/admin/AKModel/status.html:84
msgid "Export AK Slides"
msgstr "AK-Folien exportieren"
#: AKModel/templates/admin/AKModel/status.html:89
msgid "No requirements yet" msgid "No requirements yet"
msgstr "Bisher keine Anforderungen" msgstr "Bisher keine Anforderungen"
#: AKModel/templates/admin/AKModel/status.html:100 #: AKModel/templates/admin/AKModel/status.html:102
msgid "Show AKs for requirements" msgid "Show AKs for requirements"
msgstr "Zu Anforderungen gehörige AKs anzeigen" msgstr "Zu Anforderungen gehörige AKs anzeigen"
#: AKModel/templates/admin/AKModel/status.html:104 #: AKModel/templates/admin/AKModel/status.html:106
msgid "Messages" msgid "Messages"
msgstr "Nachrichten" msgstr "Nachrichten"
#: AKModel/templates/admin/AKModel/status.html:106 #: AKModel/templates/admin/AKModel/status.html:108
msgid "Delete all messages" msgid "Delete all messages"
msgstr "Alle Nachrichten löschen" msgstr "Alle Nachrichten löschen"
...@@ -918,54 +922,78 @@ msgstr "Alle Nachrichten löschen" ...@@ -918,54 +922,78 @@ msgstr "Alle Nachrichten löschen"
msgid "Active Events" msgid "Active Events"
msgstr "Aktive Events" msgstr "Aktive Events"
#: AKModel/views.py:136 #: AKModel/views.py:139
msgid "Event Status" msgid "Event Status"
msgstr "Eventstatus" msgstr "Eventstatus"
#: AKModel/views.py:149 #: AKModel/views.py:152
msgid "Requirements for Event" msgid "Requirements for Event"
msgstr "Anforderungen für das Event" msgstr "Anforderungen für das Event"
#: AKModel/views.py:163 #: AKModel/views.py:166
msgid "AK CSV Export" msgid "AK CSV Export"
msgstr "AK-CSV-Export" msgstr "AK-CSV-Export"
#: AKModel/views.py:177 #: AKModel/views.py:180
msgid "AK Wiki Export" msgid "AK Wiki Export"
msgstr "AK-Wiki-Export" msgstr "AK-Wiki-Export"
#: AKModel/views.py:197 #: AKModel/views.py:200
msgid "AK Orga Messages successfully deleted" msgid "AK Orga Messages successfully deleted"
msgstr "AK-Organachrichten erfolgreich gelöscht" msgstr "AK-Organachrichten erfolgreich gelöscht"
#: AKModel/views.py:207 #: AKModel/views.py:210
msgid "Settings" msgid "Settings"
msgstr "Einstellungen" msgstr "Einstellungen"
#: AKModel/views.py:208 #: AKModel/views.py:211
msgid "Event created, Prepare Import" msgid "Event created, Prepare Import"
msgstr "Event angelegt, Import vorbereiten" msgstr "Event angelegt, Import vorbereiten"
#: AKModel/views.py:209 #: AKModel/views.py:212
msgid "Import categories & requirements" msgid "Import categories & requirements"
msgstr "Kategorien & Anforderungen kopieren" msgstr "Kategorien & Anforderungen kopieren"
#: AKModel/views.py:210 #: AKModel/views.py:213
#, fuzzy #, fuzzy
#| msgid "Active State" #| msgid "Active State"
msgid "Activate?" msgid "Activate?"
msgstr "Aktivieren?" msgstr "Aktivieren?"
#: AKModel/views.py:270 #: AKModel/views.py:271
#, python-format #, python-format
msgid "Copied '%(obj)s'" msgid "Copied '%(obj)s'"
msgstr "'%(obj)s' kopiert" msgstr "'%(obj)s' kopiert"
#: AKModel/views.py:272 #: AKModel/views.py:273
#, python-format #, python-format
msgid "Could not copy '%(obj)s' (%(error)s)" msgid "Could not copy '%(obj)s' (%(error)s)"
msgstr "'%(obj)s' konnte nicht kopiert werden (%(error)s)" msgstr "'%(obj)s' konnte nicht kopiert werden (%(error)s)"
#: AKModel/views.py:300
msgid "Symbols"
msgstr "Symbole"
#: AKModel/views.py:301
msgid "Who?"
msgstr "Wer?"
#: AKModel/views.py:302
msgid "Duration(s)"
msgstr "Dauer(n)"
#: AKModel/views.py:303
msgid "Reso intention?"
msgstr "Resolutionsabsicht?"
#: AKModel/views.py:304
msgid "Category (for Wishes)"
msgstr "Kategorie (für Wünsche)"
#: AKModel/views.py:311
msgid "Wishes"
msgstr "Wünsche"
#~ msgid "Confirm" #~ msgid "Confirm"
#~ msgstr "Bestätigen" #~ msgstr "Bestätigen"
......
...@@ -64,6 +64,41 @@ class Event(models.Model): ...@@ -64,6 +64,41 @@ class Event(models.Model):
event = Event.objects.order_by('start').filter(start__gt=datetime.now()).first() event = Event.objects.order_by('start').filter(start__gt=datetime.now()).first()
return event return event
def get_categories_with_aks(self, wishes_seperately=False, filter=lambda ak: True):
"""
Get AKCategories as well as a list of AKs belonging to the category for this event
:param wishes_seperately: Return wishes as individual list.
:type wishes_seperately: bool
:param filter: Optional filter predicate, only include AK in list if filter returns True
:type filter: (AK)->bool
:return: list of category-AK-list-tuples, optionally the additional list of AK wishes
:rtype: list[(AKCategory, list[AK])] [, list[AK]]
"""
categories = self.akcategory_set.all()
categories_with_aks = []
ak_wishes = []
if wishes_seperately:
for category in categories:
ak_list = []
for ak in category.ak_set.all():
if ak.wish:
ak_wishes.append(ak)
else:
if filter(ak):
ak_list.append(ak)
categories_with_aks.append((category, ak_list))
return categories_with_aks, ak_wishes
else:
for category in categories:
ak_list = []
for ak in category.ak_set.all():
if filter(ak):
ak_list.append(ak)
categories_with_aks.append((category, ak_list))
return categories_with_aks
class AKOwner(models.Model): class AKOwner(models.Model):
""" An AKOwner describes the person organizing/holding an AK. """ An AKOwner describes the person organizing/holding an AK.
...@@ -264,7 +299,7 @@ class AK(models.Model): ...@@ -264,7 +299,7 @@ class AK(models.Model):
@property @property
def durations_list(self): def durations_list(self):
return ", ".join(str(slot.duration) for slot in self.akslot_set.all()) return ", ".join(str(slot.duration_simplified) for slot in self.akslot_set.all())
@property @property
def tags_list(self): def tags_list(self):
......
\documentclass[aspectratio=169]{beamer}
\usetheme[numbering=fraction, progressbar=foot]{metropolis}
\usepackage{fontspec}
\usepackage{fontawesome5}
\title{ {{- title -}} }
\subtitle{ {{- subtitle -}} }
\date{\today}
\begin{document}
\begin{frame}
\maketitle
\end{frame}
\begin{frame}
\frametitle{ {{- translations.symbols -}} }
\faUser~ {{ translations.who }}
\faClock~ {{ translations.duration }}
\faScroll~{{ translations.reso }}
\faFilter~ {{ translations.category }}
\end{frame}
{%for category, ak_list in categories_with_aks %}
\section{ {{- category.name -}} }
{% for ak, next_aks in ak_list %}
{% if not ak.wish %}
%\setbeamertemplate{frame footer}{}
\begin{frame}
\frametitle{ {{- ak.name | latex_escape -}} }
\vspace{1em}
\faUser~ {{ ak.owners_list | latex_escape }}
\faClock~ {{ak.durations_list}}
{% if ak.reso %}
\faScroll
{% endif %}
{{ ak.description | truncatechars(280) | latex_escape }}
\vspace{2em}
\begin{scriptsize}
{% for n_ak in next_aks %}
{% if n_ak %}\hfill \faAngleDoubleRight~ {{- n_ak.name | latex_escape_utf8 -}}{% endif %}
{% endfor %}
\end{scriptsize}
\end{frame}
{% endif %}
{% endfor %}
{% endfor %}
{% if not result_presentation_mode %}
\section{ {{- translations.wishes -}} }
{% for ak, next_aks in wishes %}
%\setbeamertemplate{frame footer}{}
\begin{frame}
\frametitle{ {{- ak.name | latex_escape -}} }
\vspace{1em}
\faFilter~ {{ ak.category.name | latex_escape }}
\faUser~
\faClock~
{{ ak.description | truncatechars(280) | latex_escape }}
\vspace{2em}
\begin{scriptsize}
{% for n_ak in next_aks %}
{% if n_ak %}\hfill \faAngleDoubleRight~ {{- n_ak.name | latex_escape_utf8 -}}{% endif %}
{% endfor %}
\end{scriptsize}
\end{frame}
{% endfor %}
{% endif %}
\end{document}
...@@ -83,7 +83,9 @@ ...@@ -83,7 +83,9 @@
<a class="btn btn-success" <a class="btn btn-success"
href="{% url 'admin:ak_csv_export' event_slug=event.slug %}">{% trans "Export AKs as CSV" %}</a> href="{% url 'admin:ak_csv_export' event_slug=event.slug %}">{% trans "Export AKs as CSV" %}</a>
<a class="btn btn-success" <a class="btn btn-success"
href="{% url 'admin:ak_wiki_export' event_slug=event.slug %}">{% trans "Export AKs for Wiki" %}</a> 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 %} {% endif %}
<h3 class="block-header">{% trans "Requirements" %}</h3> <h3 class="block-header">{% trans "Requirements" %}</h3>
......
...@@ -4,11 +4,9 @@ ...@@ -4,11 +4,9 @@
{% block content %} {% block content %}
{% regroup AKs by category as ak_list %} {% for category_name, ak_list in categories_with_aks %}
<h3>{{ category_name }}</h3>
{% for category_aks in ak_list %} <textarea style="width: 100%;height:30vh;" class="mb-3">{% for ak in ak_list %}
<h3>{{ category_aks.grouper }}</h3>
<textarea style="width: 100%;height:30vh;">{% for ak in category_aks.list %}
{% verbatim %}{{{% endverbatim %} {% verbatim %}{{{% endverbatim %}
{{ ak.event.wiki_export_template_name }} {{ ak.event.wiki_export_template_name }}
| name={{ ak.name }} | name={{ ak.name }}
......
...@@ -5,7 +5,7 @@ from rest_framework.routers import DefaultRouter ...@@ -5,7 +5,7 @@ from rest_framework.routers import DefaultRouter
from AKModel import views from AKModel import views
from AKModel.views import NewEventWizardStartView, NewEventWizardSettingsView, NewEventWizardPrepareImportView, \ from AKModel.views import NewEventWizardStartView, NewEventWizardSettingsView, NewEventWizardPrepareImportView, \
NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, EventStatusView, \ NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, EventStatusView, \
AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView, export_slides
api_router = DefaultRouter() api_router = DefaultRouter()
api_router.register('akowner', views.AKOwnerViewSet, basename='AKOwner') api_router.register('akowner', views.AKOwnerViewSet, basename='AKOwner')
...@@ -22,7 +22,8 @@ if apps.is_installed("AKScheduling"): ...@@ -22,7 +22,8 @@ if apps.is_installed("AKScheduling"):
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')
api_router.register('scheduling-constraint-violations', ConstraintViolationsViewSet, basename='scheduling-constraint-violations') api_router.register('scheduling-constraint-violations', ConstraintViolationsViewSet,
basename='scheduling-constraint-violations')
extra_paths = [ extra_paths = [
path('api/scheduling-events/', EventsView.as_view(), name='scheduling-events'), path('api/scheduling-events/', EventsView.as_view(), name='scheduling-events'),
...@@ -77,4 +78,6 @@ def get_admin_urls_event(admin_site): ...@@ -77,4 +78,6 @@ def get_admin_urls_event(admin_site):
name="ak_wiki_export"), name="ak_wiki_export"),
path('<slug:slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()), path('<slug: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"),
] ]
from itertools import zip_longest
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.admin.views.decorators import staff_member_required
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, 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 TemplateView, DetailView, ListView, DeleteView, CreateView, FormView, UpdateView 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 rest_framework import viewsets, permissions, mixins
from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \ from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \
...@@ -93,7 +97,8 @@ class AKCategoryViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListMo ...@@ -93,7 +97,8 @@ class AKCategoryViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListMo
return AKCategory.objects.filter(event=self.event) return AKCategory.objects.filter(event=self.event)
class AKTrackViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): class AKTrackViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,) permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = AKTrackSerializer serializer_class = AKTrackSerializer
...@@ -101,7 +106,8 @@ class AKTrackViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.CreateMod ...@@ -101,7 +106,8 @@ class AKTrackViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.CreateMod
return AKTrack.objects.filter(event=self.event) return AKTrack.objects.filter(event=self.event)
class AKViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): class AKViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin,
viewsets.GenericViewSet):
permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,) permission_classes = (permissions.DjangoModelPermissionsOrAnonReadOnly,)
serializer_class = AKSerializer serializer_class = AKSerializer
...@@ -170,14 +176,21 @@ class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): ...@@ -170,14 +176,21 @@ class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
return context return context
class AKWikiExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): class AKWikiExportView(AdminViewMixin, DetailView):
template_name = "admin/AKModel/wiki_export.html" template_name = "admin/AKModel/wiki_export.html"
model = AK model = Event
context_object_name = "AKs" context_object_name = "event"
title = _("AK Wiki Export") title = _("AK Wiki Export")
def get_queryset(self): def get_context_data(self, **kwargs):
return super().get_queryset().order_by("category") 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): class AKMessageDeleteView(AdminViewMixin, DeleteView):
...@@ -242,11 +255,10 @@ class NewEventWizardPrepareImportView(WizardViewMixin, EventSlugMixin, FormView) ...@@ -242,11 +255,10 @@ class NewEventWizardPrepareImportView(WizardViewMixin, EventSlugMixin, FormView)
template_name = "admin/AKModel/event_wizard/created_prepare_import.html" template_name = "admin/AKModel/event_wizard/created_prepare_import.html"
wizard_step = 3 wizard_step = 3
def form_valid(self, form): def form_valid(self, form):
# Selected a valid event to import from? Use this to go to next step of wizard # 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) 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): class NewEventWizardImportView(EventSlugMixin, WizardViewMixin, FormView):
...@@ -269,7 +281,9 @@ class NewEventWizardImportView(EventSlugMixin, WizardViewMixin, FormView): ...@@ -269,7 +281,9 @@ class NewEventWizardImportView(EventSlugMixin, WizardViewMixin, FormView):
import_obj.save() import_obj.save()
messages.add_message(self.request, messages.SUCCESS, _("Copied '%(obj)s'" % {'obj': import_obj})) messages.add_message(self.request, messages.SUCCESS, _("Copied '%(obj)s'" % {'obj': import_obj}))
except BaseException as e: except BaseException as e:
messages.add_message(self.request, messages.ERROR, _("Could not copy '%(obj)s' (%(error)s)" % {'obj': import_obj, "error": str(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) return redirect("admin:new_event_wizard_activate", slug=self.event.slug)
...@@ -287,3 +301,41 @@ class NewEventWizardFinishView(WizardViewMixin, DetailView): ...@@ -287,3 +301,41 @@ class NewEventWizardFinishView(WizardViewMixin, DetailView):
model = Event model = Event
template_name = "admin/AKModel/event_wizard/finish.html" template_name = "admin/AKModel/event_wizard/finish.html"
wizard_step = 6 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)
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')
...@@ -52,6 +52,7 @@ INSTALLED_APPS = [ ...@@ -52,6 +52,7 @@ INSTALLED_APPS = [
'simple_history', 'simple_history',
'registration', 'registration',
'bootstrap_datepicker_plus', 'bootstrap_datepicker_plus',
'django_tex',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
...@@ -86,6 +87,14 @@ TEMPLATES = [ ...@@ -86,6 +87,14 @@ TEMPLATES = [
], ],
}, },
}, },
{
'NAME': 'tex',
'BACKEND': 'django_tex.engine.TeXEngine',
'APP_DIRS': True,
'OPTIONS': {
'environment': 'AKModel.environment.improved_tex_environment',
},
},
] ]
WSGI_APPLICATION = 'AKPlanning.wsgi.application' WSGI_APPLICATION = 'AKPlanning.wsgi.application'
...@@ -138,6 +147,9 @@ LANGUAGES = [ ...@@ -138,6 +147,9 @@ LANGUAGES = [
INTERNAL_IPS = ['127.0.0.1', '::1'] INTERNAL_IPS = ['127.0.0.1', '::1']
LATEX_INTERPRETER = 'lualatex'
LATEX_RUN_COUNT = 2
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/ # https://docs.djangoproject.com/en/2.2/howto/static-files/
...@@ -154,13 +166,16 @@ BOOTSTRAP4 = { ...@@ -154,13 +166,16 @@ BOOTSTRAP4 = {
"href": STATIC_URL + "common/css/bootstrap.css", "href": STATIC_URL + "common/css/bootstrap.css",
}, },
"javascript_url": { "javascript_url": {
"url": STATIC_URL + "common/vendor/bootstrap/bootstrap-4.6.0.min.js", "url": STATIC_URL + "common/vendor/bootstrap/bootstrap-4.3.1.min.js",
}, },
"jquery_url": { "jquery_url": {
"url": STATIC_URL + "common/vendor/jquery/jquery-3.5.1.min.js", "url": STATIC_URL + "common/vendor/jquery/jquery-3.3.1.min.js",
}, },
"jquery_slim_url": { "jquery_slim_url": {
"url": STATIC_URL + "common/vendor/jquery/jquery-3.5.1.slim.min.js", "url": STATIC_URL + "common/vendor/jquery/jquery-3.3.1.slim.min.js",
},
"popper_url": {
"url": STATIC_URL + "common/vendor/popper/popper-1.14.7.min.js",
}, },
} }
......
...@@ -16,6 +16,9 @@ DATABASES = { ...@@ -16,6 +16,9 @@ DATABASES = {
'PASSWORD': 'mysql', 'PASSWORD': 'mysql',
'OPTIONS': { 'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"
} },
'TEST': {
'NAME': 'test',
},
} }
} }
...@@ -184,12 +184,18 @@ ...@@ -184,12 +184,18 @@
<div id="planCalendar"></div> <div id="planCalendar"></div>
</div> </div>
<div class="col-md-2 col-lg-2" id="unscheduled-slots"> <div class="col-md-2 col-lg-2" id="unscheduled-slots">
{% for slot in slots_unscheduled %} {% regroup slots_unscheduled by ak.track as slots_unscheduled_by_track_list %}
{% for track_slots in slots_unscheduled_by_track_list %}
{% if track_slots.grouper %}
<h5 class="mt-2">{{ track_slots.grouper }}</h5>
{% endif %}
{% for slot in track_slots.list %}
<div class="unscheduled-slot badge badge-primary" style='background-color: {{ slot.ak.category.color }}' <div class="unscheduled-slot badge badge-primary" style='background-color: {{ slot.ak.category.color }}'
data-event='{ "title": "{{ slot.ak.short_name }}", "duration": {"hours": "{{ slot.duration|unlocalize }}"}, "constraint": "roomAvailable", "description": "{{ slot.ak.details | escapejs }}", "slotID": "{{ slot.pk }}", "backgroundColor": "{{ slot.ak.category.color }}"}' data-details="{{ slot.ak.details }}">{{ slot.ak.short_name }} data-event='{ "title": "{{ slot.ak.short_name }}", "duration": {"hours": "{{ slot.duration|unlocalize }}"}, "constraint": "roomAvailable", "description": "{{ slot.ak.details | escapejs }}", "slotID": "{{ slot.pk }}", "backgroundColor": "{{ slot.ak.category.color }}"}' data-details="{{ slot.ak.details }}">{{ slot.ak.short_name }}
({{ slot.duration }} h)<br>{{ slot.ak.owners_list }} ({{ slot.duration }} h)<br>{{ slot.ak.owners_list }}
</div> </div>
{% endfor %} {% endfor %}
{% endfor %}
</div> </div>
</div> </div>
......
from django.views.generic import ListView, DetailView
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView, DetailView
from AKModel.models import AKSlot, AKTrack, Event from AKModel.models import AKSlot, AKTrack, Event
from AKModel.views import AdminViewMixin, FilterByEventSlugMixin from AKModel.views import AdminViewMixin, FilterByEventSlugMixin
...@@ -25,7 +25,7 @@ class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): ...@@ -25,7 +25,7 @@ class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
context_object_name = "slots_unscheduled" context_object_name = "slots_unscheduled"
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(start__isnull=True) return super().get_queryset().filter(start__isnull=True).order_by('ak__track')
def get_context_data(self, *, object_list=None, **kwargs): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
......
...@@ -2,16 +2,18 @@ ...@@ -2,16 +2,18 @@
This repository contains a Django project with several apps. This repository contains a Django project with several apps.
## Requirements ## Requirements
AKPlanning has two types of requirements: System requirements are dependent on operating system and need to be installed manually beforehand. Python requirements will be installed inside a virtual environment (strongly recommended) during setup. AKPlanning has two types of requirements: System requirements are dependent on operating system and need to be installed
manually beforehand. Python requirements will be installed inside a virtual environment (strongly recommended) during
setup.
### System Requirements ### System Requirements
* Python 3.7 incl. development tools * Python 3.7 incl. development tools
* Virtualenv * Virtualenv
* pdflatex & beamer
class (`texlive-latex-base texlive-latex-recommended texlive-latex-extra texlive-fonts-extra texlive-luatex`)
* for production using uwsgi: * for production using uwsgi:
* C compiler e.g. gcc * C compiler e.g. gcc
* uwsgi * uwsgi
...@@ -19,24 +21,20 @@ AKPlanning has two types of requirements: System requirements are dependent on o ...@@ -19,24 +21,20 @@ AKPlanning has two types of requirements: System requirements are dependent on o
* for production using Apache (in addition to uwsgi) * for production using Apache (in addition to uwsgi)
* the mod proxy uwsgi plugin for apache2 * the mod proxy uwsgi plugin for apache2
### Python Requirements ### Python Requirements
Python requirements are listed in ``requirements.txt``. They can be installed with pip using ``-r requirements.txt``. Python requirements are listed in ``requirements.txt``. They can be installed with pip using ``-r requirements.txt``.
## Development Setup ## Development Setup
* create a new directory that should contain the files in future, e.g. ``mkdir AKPlanning`` * create a new directory that should contain the files in future, e.g. ``mkdir AKPlanning``
* change into that directory ``cd AKPlanning`` * change into that directory ``cd AKPlanning``
* clone this repository ``git clone URL .`` * clone this repository ``git clone URL .``
### Automatic Setup ### Automatic Setup
1. execute the setup bash script ``Utils/setup.sh`` 1. execute the setup bash script ``Utils/setup.sh``
### Manual Setup ### Manual Setup
1. setup a virtual environment using the proper python version ``virtualenv venv -p python3.7`` 1. setup a virtual environment using the proper python version ``virtualenv venv -p python3.7``
...@@ -48,7 +46,6 @@ Python requirements are listed in ``requirements.txt``. They can be installed wi ...@@ -48,7 +46,6 @@ Python requirements are listed in ``requirements.txt``. They can be installed wi
1. create a priviledged user, credentials are entered interactively on CLI ``python manage.py createsuperuser`` 1. create a priviledged user, credentials are entered interactively on CLI ``python manage.py createsuperuser``
1. deactivate virtualenv ``deactivate`` 1. deactivate virtualenv ``deactivate``
### Development Server ### Development Server
**Do not use this for deployment!** **Do not use this for deployment!**
...@@ -59,11 +56,10 @@ To start the application for development, in the root directory, ...@@ -59,11 +56,10 @@ To start the application for development, in the root directory,
1. start development server ``python manage.py runserver 0:8000`` 1. start development server ``python manage.py runserver 0:8000``
1. In your browser, access ``http://127.0.0.1:8000/admin/`` and continue from there. 1. In your browser, access ``http://127.0.0.1:8000/admin/`` and continue from there.
## Deployment Setup ## Deployment Setup
This application can be deployed using a web server as any other Django application. This application can be deployed using a web server as any other Django application. Remember to use a secret key that
Remember to use a secret key that is not stored in any repository or similar, and disable DEBUG mode (``settings.py``). is not stored in any repository or similar, and disable DEBUG mode (``settings.py``).
**Step-by-Step Instructions** **Step-by-Step Instructions**
...@@ -76,9 +72,12 @@ Remember to use a secret key that is not stored in any repository or similar, an ...@@ -76,9 +72,12 @@ Remember to use a secret key that is not stored in any repository or similar, an
1. activate virtualenv ``source venv/bin/activate`` 1. activate virtualenv ``source venv/bin/activate``
1. update tools ``pip install --upgrade setuptools pip wheel`` 1. update tools ``pip install --upgrade setuptools pip wheel``
1. install python requirements ``pip install -r requirements.txt`` 1. install python requirements ``pip install -r requirements.txt``
1. create the file ``AKPlanning/settings_secrets.py`` (copy from ``settings_secrets.py.sample``) and fill it with the necessary secrets (e.g. generated by ``tr -dc 'a-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c50``) (it is a good idea to restrict read permissions from others) 1. create the file ``AKPlanning/settings_secrets.py`` (copy from ``settings_secrets.py.sample``) and fill it with the
necessary secrets (e.g. generated by ``tr -dc 'a-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c50``) (it is a good idea
to restrict read permissions from others)
1. if necessary enable uwsgi proxy plugin for Apache e.g.``a2enmod proxy_uwsgi`` 1. if necessary enable uwsgi proxy plugin for Apache e.g.``a2enmod proxy_uwsgi``
1. edit the apache config to serve the application and the static files, e.g. on a dedicated system in ``/etc/apache2/sites-enabled/000-default.conf`` within the ``VirtualHost`` tag add: 1. edit the apache config to serve the application and the static files, e.g. on a dedicated system
in ``/etc/apache2/sites-enabled/000-default.conf`` within the ``VirtualHost`` tag add:
``` ```
Alias /static /srv/AKPlanning/static Alias /static /srv/AKPlanning/static
...@@ -90,19 +89,25 @@ Remember to use a secret key that is not stored in any repository or similar, an ...@@ -90,19 +89,25 @@ Remember to use a secret key that is not stored in any repository or similar, an
ProxyPass / uwsgi://127.0.0.1:3035/ ProxyPass / uwsgi://127.0.0.1:3035/
``` ```
or create a new config (.conf) file (similar to ``apache-akplanning.conf``) replacing $SUBDOMAIN with the subdomain the system should be available under, and $MAILADDRESS with the e-mail address of your administrator and $PATHTO with the appropriate paths. Copy or symlink it to ``/etc/apache2/sites-available``. Then symlink it to ``sites-enabled`` e.g. by using ``ln -s /etc/apache2/sites-available/akplanning.conf /etc/apache2/sites-enabled/akplanning.conf``. or create a new config (.conf) file (similar to ``apache-akplanning.conf``) replacing $SUBDOMAIN with the subdomain
the system should be available under, and $MAILADDRESS with the e-mail address of your administrator and $PATHTO with
the appropriate paths. Copy or symlink it to ``/etc/apache2/sites-available``. Then symlink it to ``sites-enabled``
e.g. by using ``ln -s /etc/apache2/sites-available/akplanning.conf /etc/apache2/sites-enabled/akplanning.conf``.
1. restart Apache ``sudo systemctl restart apache2.service`` 1. restart Apache ``sudo systemctl restart apache2.service``
1. create a dedicated user, e.g. ``adduser django`` 1. create a dedicated user, e.g. ``adduser django``
1. transfer ownership of the folder to the new user ``chown -R django:django /srv/AKPlanning`` 1. transfer ownership of the folder to the new user ``chown -R django:django /srv/AKPlanning``
1. Copy or symlink the uwsgi config in ``uwsgi-akplanning.ini`` to ``/etc/uwsgi/apps-available/`` and then symlink it to ``/etc/uwsgi/apps-enabled/`` using e.g., ``ln -s /srv/AKPlanning/uwsgi-akplanning.ini /etc/uwsgi/apps-available/akplanning.ini`` and ``ln -s /etc/uwsgi/apps-available/akplanning.ini /etc/uwsgi/apps-enabled/akplanning.ini`` 1. Copy or symlink the uwsgi config in ``uwsgi-akplanning.ini`` to ``/etc/uwsgi/apps-available/`` and then symlink it
to ``/etc/uwsgi/apps-enabled/`` using
e.g., ``ln -s /srv/AKPlanning/uwsgi-akplanning.ini /etc/uwsgi/apps-available/akplanning.ini``
and ``ln -s /etc/uwsgi/apps-available/akplanning.ini /etc/uwsgi/apps-enabled/akplanning.ini``
start uwsgi using the configuration file ``uwsgi --ini uwsgi-akplanning.ini`` start uwsgi using the configuration file ``uwsgi --ini uwsgi-akplanning.ini``
1. restart uwsgi ``sudo systemctl restart uwsgi`` 1. restart uwsgi ``sudo systemctl restart uwsgi``
1. execute the update script ``./Utils/update.sh --prod`` 1. execute the update script ``./Utils/update.sh --prod``
## Updates ## Updates
To update the setup to the current version on the main branch of the repository use the update script ``Utils/update.sh`` or ``Utils/update.sh --prod`` in production. To update the setup to the current version on the main branch of the repository use the update
script ``Utils/update.sh`` or ``Utils/update.sh --prod`` in production.
Afterwards, you may check your setup by executing ``Utils/check.sh`` or ``Utils/check.sh --prod`` in production. Afterwards, you may check your setup by executing ``Utils/check.sh`` or ``Utils/check.sh --prod`` in production.
Django==3.1.8 Django==3.1.8
django-bootstrap4==3.0.1 django-bootstrap4==2.3.1
django-fontawesome-5==1.0.18 django-fontawesome-5==1.0.18
django-split-settings==1.0.1 django-split-settings==1.0.1
django-timezone-field==4.1.2 django-timezone-field==4.1.2
...@@ -8,6 +8,7 @@ django-simple-history==3.0.0 ...@@ -8,6 +8,7 @@ django-simple-history==3.0.0
django-registration-redux==2.9 django-registration-redux==2.9
django-debug-toolbar==3.2.1 django-debug-toolbar==3.2.1
django-bootstrap-datepicker-plus==3.0.5 django-bootstrap-datepicker-plus==3.0.5
django-tex @ git+https://github.com/bhaettasch/django-tex.git@66cc6567acde4db2ac971b7707652067e664392c
django-csp==3.7 django-csp==3.7
mysqlclient==2.0.3 # for production deployment mysqlclient==2.0.3 # for production deployment
pytz==2021.1 pytz==2021.1
static_common/common/css/chosen-sprite.png

538 B

static_common/common/css/chosen-sprite@2x.png

738 B

This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment