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
  • koma/feature/preference-polling-form
  • main
  • renovate/beautifulsoup4-4.x
  • renovate/django-5.x
  • renovate/django-bootstrap5-25.x
  • renovate/django-debug-toolbar-6.x
  • renovate/django-tex-1.x
  • renovate/djangorestframework-3.x
  • renovate/jsonschema-4.x
9 results

Target

Select target project
No results found
Select Git revision
  • 520-akowner
  • 520-fix-event-wizard-datepicker
  • 520-fix-scheduling
  • 520-improve-scheduling
  • 520-improve-scheduling-2
  • 520-improve-submission
  • 520-improve-trackmanager
  • 520-improve-wall
  • 520-message-resolved
  • 520-status
  • 520-upgrades
  • add_express_interest_to_ak_overview
  • admin-production-color
  • bugfixes
  • csp
  • featire-ical-export
  • feature-ak-requirement-lists
  • feature-akslide-export-better-filename
  • feature-akslides
  • feature-better-admin
  • feature-better-cv-list
  • feature-colors
  • feature-constraint-checking
  • feature-constraint-checking-wip
  • feature-dashboard-history-button
  • feature-event-status
  • feature-event-wizard
  • feature-export-flag
  • feature-improve-admin
  • feature-improve-filters
  • feature-improved-user-creation-workflow
  • feature-interest-view
  • feature-mails
  • feature-modular-status
  • feature-plan-autoreload
  • feature-present-default
  • feature-register-link
  • feature-remaining-constraint-validation
  • feature-room-import
  • feature-scheduler-improve
  • feature-scheduling-2.0
  • feature-special-attention
  • feature-time-input
  • feature-tracker
  • feature-wiki-wishes
  • feature-wish-slots
  • feature-wizard-buttons
  • features-availabilities
  • fix-ak-times-above-folg
  • fix-api
  • fix-constraint-violation-string
  • fix-cv-checking
  • fix-default-slot-length
  • fix-default-slot-localization
  • fix-doc-minor
  • fix-duration-display
  • fix-event-tz-pytz-update
  • fix-history-interest
  • fix-interest-view
  • fix-js
  • fix-pipeline
  • fix-plan-timezone-now
  • fix-room-add
  • fix-scheduling-drag
  • fix-slot-defaultlength
  • fix-timezone
  • fix-translation-scheduling
  • fix-virtual-room-admin
  • fix-wizard-csp
  • font-locally
  • improve-admin
  • improve-online
  • improve-slides
  • improve-submission-coupling
  • interest_restriction
  • main
  • master
  • meta-debug-toolbar
  • meta-export
  • meta-makemessages
  • meta-performance
  • meta-tests
  • meta-tests-gitlab-test
  • meta-upgrades
  • mollux-master-patch-02906
  • port-availabilites-fullcalendar
  • qs
  • remove-tags
  • renovate/configure
  • renovate/django-4.x
  • renovate/django-5.x
  • renovate/django-bootstrap-datepicker-plus-5.x
  • renovate/django-bootstrap5-23.x
  • renovate/django-bootstrap5-24.x
  • renovate/django-compressor-4.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-registration-redux-2.x
  • renovate/django-simple-history-3.x
  • renovate/django-split-settings-1.x
  • renovate/django-timezone-field-5.x
100 results
Show changes
364 files
+ 79483
15153
Compare changes
  • Side-by-side
  • Inline

Files

+58 −15
Original line number Original line Diff line number Diff line
image: python:3.9
image: python:3.11


services:
services:
  - mysql:5.7
  - mysql


variables:
variables:
  MYSQL_DATABASE: "test"
  MYSQL_DATABASE: "test"
@@ -14,28 +14,30 @@ cache:
  paths:
  paths:
    - ~/.cache/pip/
    - ~/.cache/pip/


.before_script_template:
  before_script:
  before_script:
    - python -V  # Print out python version for debugging
    - python -V  # Print out python version for debugging
    - apt-get -qq update
    - apt-get -qq update
    - apt-get -qq install -y python3-virtualenv python3 python3-dev python3-pip gettext default-mysql-client default-libmysqlclient-dev
    - apt-get -qq install -y python3-virtualenv python3 python3-dev python3-pip gettext default-mysql-client default-libmysqlclient-dev
  - export DJANGO_SETTINGS_MODULE=AKPlanning.settings_ci
    - ./Utils/setup.sh --ci
  - ./Utils/setup.sh --prod
    - mkdir -p public/badges public/lint
    - echo undefined > public/badges/$CI_JOB_NAME.score
    - source venv/bin/activate
    - pip install pylint-gitlab pylint-django
    - mysql --version
    - mysql --version


check:
migrations:
  script:
  extends: .before_script_template
    - ./Utils/check.sh --all

check-migrations:
  script:
  script:
    - source venv/bin/activate
    - source venv/bin/activate
    - ./manage.py makemigrations --dry-run --check
    - ./manage.py makemigrations --dry-run --check


test:
test:
  extends: .before_script_template
  script:
  script:
    - source venv/bin/activate
    - source venv/bin/activate
    - echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql
    - echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql
    - pip install pytest-cov unittest-xml-reporting
    - pip install pytest-cov
    - coverage run --source='.' manage.py test --settings AKPlanning.settings_ci
    - coverage run --source='.' manage.py test --settings AKPlanning.settings_ci
  after_script:
  after_script:
    - source venv/bin/activate
    - source venv/bin/activate
@@ -48,3 +50,44 @@ test:
        coverage_format: cobertura
        coverage_format: cobertura
        path: coverage.xml
        path: coverage.xml
      junit: unit.xml
      junit: unit.xml

lint:
  extends: .before_script_template
  stage: test
  script:
    - pylint --load-plugins pylint_django --django-settings-module=AKPlanning.settings_ci --rcfile pylintrc --exit-zero --output-format=text AK* | tee /tmp/pylint.txt
    - sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' /tmp/pylint.txt > public/badges/$CI_JOB_NAME.score
    - pylint --load-plugins pylint_django --django-settings-module=AKPlanning.settings_ci --rcfile pylintrc --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter AK* > codeclimate.json
    - pylint --load-plugins pylint_django --django-settings-module=AKPlanning.settings_ci --rcfile pylintrc --exit-zero --output-format=pylint_gitlab.GitlabPagesHtmlReporter AK* > public/lint/index.html
  after_script:
   - |
      echo "Linting score: $(cat public/badges/$CI_JOB_NAME.score)"
  artifacts:
    paths:
      - public
    reports:
      codequality: codeclimate.json
    when: always

doc:
  extends: .before_script_template
  stage: test
  script:
    - cd docs
    - make html
    - cd ..
  artifacts:
    paths:
      - docs/_build/html

pages:
  stage: deploy
  image: alpine:latest
  script:
    - echo
  artifacts:
    paths:
      - public
  only:
    refs:
      - main
Original line number Original line Diff line number Diff line
@@ -4,6 +4,9 @@ from AKDashboard.models import DashboardButton


@admin.register(DashboardButton)
@admin.register(DashboardButton)
class DashboardButtonAdmin(admin.ModelAdmin):
class DashboardButtonAdmin(admin.ModelAdmin):
    """
    Admin interface for dashboard buttons
    """
    list_display = ['text', 'url', 'event']
    list_display = ['text', 'url', 'event']
    list_filter = ['event']
    list_filter = ['event']
    search_fields = ['text', 'url']
    search_fields = ['text', 'url']
Original line number Original line Diff line number Diff line
@@ -2,4 +2,7 @@ from django.apps import AppConfig




class AkdashboardConfig(AppConfig):
class AkdashboardConfig(AppConfig):
    """
    App configuration for dashboard (default)
    """
    name = 'AKDashboard'
    name = 'AKDashboard'
Original line number Original line Diff line number Diff line
@@ -8,7 +8,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-10-29 13:36+0000\n"
"POT-Creation-Date: 2025-06-21 18:09+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"
@@ -17,106 +17,122 @@ 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"


#: AKDashboard/models.py:10
#: AKDashboard/models.py:21
msgid "Dashboard Button"
msgid "Dashboard Button"
msgstr "Dashboard-Button"
msgstr "Dashboard-Button"


#: AKDashboard/models.py:11
#: AKDashboard/models.py:22
msgid "Dashboard Buttons"
msgid "Dashboard Buttons"
msgstr "Dashboard-Buttons"
msgstr "Dashboard-Buttons"


#: AKDashboard/models.py:21
#: AKDashboard/models.py:32
msgid "Text"
msgid "Text"
msgstr "Text"
msgstr "Text"


#: AKDashboard/models.py:22
#: AKDashboard/models.py:33
msgid "Text that will be shown on the button"
msgid "Text that will be shown on the button"
msgstr "Text, der auf dem Button angezeigt wird"
msgstr "Text, der auf dem Button angezeigt wird"


#: AKDashboard/models.py:23
#: AKDashboard/models.py:34
msgid "Link URL"
msgid "Link URL"
msgstr "Link-URL"
msgstr "Link-URL"


#: AKDashboard/models.py:23
#: AKDashboard/models.py:34
msgid "URL this button links to"
msgid "URL this button links to"
msgstr "URL auf die der Button verweist"
msgstr "URL auf die der Button verweist"


#: AKDashboard/models.py:24
#: AKDashboard/models.py:35
msgid "Icon"
msgid "Icon"
msgstr "Symbol"
msgstr "Symbol"


#: AKDashboard/models.py:26
#: AKDashboard/models.py:37
msgid "Button Style"
msgid "Button Style"
msgstr "Stil des Buttons"
msgstr "Stil des Buttons"


#: AKDashboard/models.py:26
#: AKDashboard/models.py:37
msgid "Style (Color) of this button (bootstrap class)"
msgid "Style (Color) of this button (bootstrap class)"
msgstr "Stiel (Farbe) des Buttons (Bootstrap-Klasse)"
msgstr "Stiel (Farbe) des Buttons (Bootstrap-Klasse)"


#: AKDashboard/models.py:28
#: AKDashboard/models.py:39
msgid "Event"
msgid "Event"
msgstr "Veranstaltung"
msgstr "Veranstaltung"


#: AKDashboard/models.py:28
#: AKDashboard/models.py:39
msgid "Event this button belongs to"
msgid "Event this button belongs to"
msgstr "Veranstaltung, zu der dieser Button gehört"
msgstr "Veranstaltung, zu der dieser Button gehört"


#: AKDashboard/templates/AKDashboard/dashboard.html:25
#: AKDashboard/templates/AKDashboard/dashboard.html:19
#: AKDashboard/templates/AKDashboard/dashboard_event.html:37
#: AKDashboard/templates/AKDashboard/dashboard_event.html:30
#: AKDashboard/templates/AKDashboard/dashboard_row_old_event.html:53
msgid "Write to organizers of this event for questions and comments"
msgid "Write to organizers of this event for questions and comments"
msgstr ""
msgstr ""
"Kontaktiere die Organisator*innen des Events bei Fragen oder Kommentaren"
"Kontaktiere die Organisator*innen des Events bei Fragen oder Kommentaren"


#: AKDashboard/templates/AKDashboard/dashboard.html:32
#: AKDashboard/templates/AKDashboard/dashboard.html:25
msgid "Old events"
msgstr "Frühere Veranstaltungen"

#: AKDashboard/templates/AKDashboard/dashboard.html:35
msgid "Currently, there are no Events!"
msgid "Currently, there are no Events!"
msgstr "Aktuell gibt es keine Events!"
msgstr "Aktuell gibt es keine Events!"


#: AKDashboard/templates/AKDashboard/dashboard.html:35
#: AKDashboard/templates/AKDashboard/dashboard.html:38
msgid "Please contact an administrator if you want to use AKPlanning."
msgid "Please contact an administrator if you want to use AKPlanning."
msgstr ""
msgstr ""
"Bitte kontaktiere eine*n Administrator*in, falls du AKPlanning verwenden "
"Bitte kontaktiere eine*n Administrator*in, falls du AKPlanning verwenden "
"möchtest."
"möchtest."


#: AKDashboard/templates/AKDashboard/dashboard_event.html:27
#: AKDashboard/templates/AKDashboard/dashboard_event.html:20
msgid "Recent"
msgid "Recent"
msgstr "Kürzlich"
msgstr "Kürzlich"


#: AKDashboard/templates/AKDashboard/dashboard_row.html:12
#: AKDashboard/templates/AKDashboard/dashboard_row.html:18
#: AKDashboard/templates/AKDashboard/dashboard_row_old_event.html:20
msgid "AK List"
msgid "AK List"
msgstr "AK-Liste"
msgstr "AK-Liste"


#: AKDashboard/templates/AKDashboard/dashboard_row.html:23
#: AKDashboard/templates/AKDashboard/dashboard_row.html:29
msgid "Current AKs"
msgid "Current AKs"
msgstr "Aktuelle AKs"
msgstr "Aktuelle AKs"


#: AKDashboard/templates/AKDashboard/dashboard_row.html:30
#: AKDashboard/templates/AKDashboard/dashboard_row.html:36
msgid "AK Wall"
msgid "AK Wall"
msgstr "AK-Wall"
msgstr "AK-Wall"


#: AKDashboard/templates/AKDashboard/dashboard_row.html:38
#: AKDashboard/templates/AKDashboard/dashboard_row.html:44
#: AKDashboard/templates/AKDashboard/dashboard_row_old_event.html:30
msgid "Schedule"
msgid "Schedule"
msgstr "AK-Plan"
msgstr "AK-Plan"


#: AKDashboard/templates/AKDashboard/dashboard_row.html:49
#: AKDashboard/templates/AKDashboard/dashboard_row.html:55
msgid "AK Submission"
msgid "AK Submission"
msgstr "AK-Einreichung"
msgstr "AK-Einreichung"


#: AKDashboard/views.py:42
#: AKDashboard/templates/AKDashboard/dashboard_row.html:63
#: AKDashboard/templates/AKDashboard/dashboard_row_old_event.html:39
msgid "AK History"
msgstr "AK-Verlauf"

#: AKDashboard/templates/AKDashboard/dashboard_row.html:72
msgid "AK Preferences"
msgstr "AK-Präferenzen"

#: AKDashboard/views.py:70
#, python-format
#, python-format
msgid "New AK: %(ak)s."
msgid "New AK: %(ak)s."
msgstr "Neuer AK: %(ak)s."
msgstr "Neuer AK: %(ak)s."


#: AKDashboard/views.py:45
#: AKDashboard/views.py:73
#, python-format
#, python-format
msgid "AK \"%(ak)s\" edited."
msgid "AK \"%(ak)s\" edited."
msgstr "AK \"%(ak)s\" bearbeitet."
msgstr "AK \"%(ak)s\" bearbeitet."


#: AKDashboard/views.py:48
#: AKDashboard/views.py:76
#, python-format
#, python-format
msgid "AK \"%(ak)s\" deleted."
msgid "AK \"%(ak)s\" deleted."
msgstr "AK \"%(ak)s\" gelöscht."
msgstr "AK \"%(ak)s\" gelöscht."


#: AKDashboard/views.py:61
#: AKDashboard/views.py:91
#, python-format
#, python-format
msgid "AK \"%(ak)s\" (re-)scheduled."
msgid "AK \"%(ak)s\" (re-)scheduled."
msgstr "AK \"%(ak)s\" (um-)geplant."
msgstr "AK \"%(ak)s\" (um-)geplant."
Original line number Original line Diff line number Diff line
@@ -2,7 +2,7 @@


from django.db import migrations, models
from django.db import migrations, models
import django.db.models.deletion
import django.db.models.deletion
import fontawesome_5.fields
import fontawesome_6.fields




class Migration(migrations.Migration):
class Migration(migrations.Migration):
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('text', models.CharField(help_text='Text that will be shown on the button', max_length=50, verbose_name='Text')),
                ('text', models.CharField(help_text='Text that will be shown on the button', max_length=50, verbose_name='Text')),
                ('url', models.URLField(help_text='URL this button links to', verbose_name='Link URL')),
                ('url', models.URLField(help_text='URL this button links to', verbose_name='Link URL')),
                ('icon', fontawesome_5.fields.IconField(blank=True, default='external-link-alt', help_text='Symbol represeting this button.', max_length=60, verbose_name='Icon')),
                ('icon', fontawesome_6.fields.IconField(blank=True, default='external-link-alt', help_text='Symbol represeting this button.', max_length=60, verbose_name='Icon')),
                ('color', models.PositiveSmallIntegerField(choices=[(0, 'primary'), (1, 'success'), (2, 'info'), (3, 'warning'), (4, 'danger')], default=0, help_text='Style (Color) of this button (bootstrap class)', verbose_name='Button Style')),
                ('color', models.PositiveSmallIntegerField(choices=[(0, 'primary'), (1, 'success'), (2, 'info'), (3, 'warning'), (4, 'danger')], default=0, help_text='Style (Color) of this button (bootstrap class)', verbose_name='Button Style')),
                ('event', models.ForeignKey(help_text='Event this button belongs to', on_delete=django.db.models.deletion.CASCADE, to='AKModel.Event', verbose_name='Event')),
                ('event', models.ForeignKey(help_text='Event this button belongs to', on_delete=django.db.models.deletion.CASCADE, to='AKModel.Event', verbose_name='Event')),
            ],
            ],
Original line number Original line Diff line number Diff line
# Generated by Django 3.2.16 on 2023-01-03 16:50

from django.db import migrations
import fontawesome_6.fields


class Migration(migrations.Migration):

    dependencies = [
        ('AKDashboard', '0001_initial'),
    ]

    operations = [
        migrations.AlterField(
            model_name='dashboardbutton',
            name='icon',
            field=fontawesome_6.fields.IconField(blank=True, default='external-link-alt', help_text='Symbol represeting this button.', max_length=60, verbose_name='Icon'),
        ),
    ]
Original line number Original line Diff line number Diff line
from django.db import models
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _
from fontawesome_5.fields import IconField
from fontawesome_6.fields import IconField


from AKModel.models import Event
from AKModel.models import Event




class DashboardButton(models.Model):
class DashboardButton(models.Model):
    """
    Model for a single dashboard button

    Allows to specify
    * a text (currently without possibility to translate),
    * a color (based on predefined design colors)
    * a url the button should point to (internal or external)
    * an icon (from the collection of fontawesome)

    Each button is associated with a single event and will be deleted when the event is deleted.
    """
    class Meta:
    class Meta:
        verbose_name = _("Dashboard Button")
        verbose_name = _("Dashboard Button")
        verbose_name_plural = _("Dashboard Buttons")
        verbose_name_plural = _("Dashboard Buttons")
Original line number Original line Diff line number Diff line
@@ -2,6 +2,10 @@
    margin-bottom: 5em;
    margin-bottom: 5em;
}
}


.dashboard-row-small {
    margin-bottom: 3em;
}

.dashboard-row > .row {
.dashboard-row > .row {
    margin-left: 0;
    margin-left: 0;
    padding-bottom: 1em;
    padding-bottom: 1em;
@@ -18,7 +22,6 @@
}
}


.dashboard-box {
.dashboard-box {
    display: block;
    padding: 2em;
    padding: 2em;
    margin-right: 1em;
    margin-right: 1em;
    min-width: 15em;
    min-width: 15em;
Original line number Original line Diff line number Diff line
{% extends 'base.html' %}
{% extends 'base.html' %}


{% load fontawesome_5 %}
{% load fontawesome_6 %}
{% load i18n %}
{% load i18n %}
{% load static %}
{% load static %}


{% block imports %}

    {{ block.super }}

    <link rel="stylesheet" href="{% static 'AKDashboard/style.css' %}">

{% endblock %}

{% block breadcrumbs %}
{% block breadcrumbs %}
    <li class="breadcrumb-item">AKPlanning</li>
    <li class="breadcrumb-item">AKPlanning</li>
{% endblock %}
{% endblock %}


{% block content %}
{% block content %}
    {% for event in events %}
    {% include "messages.html" %}
    {% if total_event_count > 0 %}
        {% for event in active_and_current_events %}
            <div class="dashboard-row">
            <div class="dashboard-row">
                {% include "AKDashboard/dashboard_row.html" %}
                {% include "AKDashboard/dashboard_row.html" %}
                {% if event.contact_email %}
                {% if event.contact_email %}
                    <p>
                    <p>
                    <a href="mailto:{{ event.contact_email }}">{% fa5_icon "envelope" "fas" %} {% trans "Write to organizers of this event for questions and comments" %}</a>
                        <a href="mailto:{{ event.contact_email }}">{% fa6_icon "envelope" "fas" %} {% trans "Write to organizers of this event for questions and comments" %}</a>
                    </p>
                    </p>
                {% endif %}
                {% endif %}
            </div>
            </div>
    {% empty %}
        {% endfor %}
        {% if old_event_count > 0 %}
            <h2 class="mb-3">{% trans "Old events" %}</h2>
            {% for event in old_events %}
                <div class="dashboard-row-small">
                    {% include "AKDashboard/dashboard_row_old_event.html" %}
                </div>
            {% endfor %}
        {% endif %}
    {% else %}
        <div class="jumbotron">
        <div class="jumbotron">
            <h2 class="display-4">
            <h2 class="display-4">
                {% trans 'Currently, there are no Events!' %}
                {% trans 'Currently, there are no Events!' %}
@@ -35,5 +38,5 @@
                {% trans 'Please contact an administrator if you want to use AKPlanning.' %}
                {% trans 'Please contact an administrator if you want to use AKPlanning.' %}
            </p>
            </p>
        </div>
        </div>
    {% endfor %}
    {% endif %}
{% endblock %}
{% endblock %}
Original line number Original line Diff line number Diff line
{% extends 'base.html' %}
{% extends 'base.html' %}


{% load fontawesome_5 %}
{% load fontawesome_6 %}
{% load i18n %}
{% load i18n %}
{% load static %}
{% load static %}
{% load tags_AKModel %}
{% load tags_AKModel %}
{% load tz %}
{% load tz %}


{% block imports %}

    {{ block.super }}

    <link rel="stylesheet" href="{% static 'AKDashboard/style.css' %}">

{% endblock %}

{% block breadcrumbs %}
{% block breadcrumbs %}
    <li class="breadcrumb-item"><a href="{% url 'dashboard:dashboard' %}">AKPlanning</a></li>
    <li class="breadcrumb-item"><a href="{% url 'dashboard:dashboard' %}">AKPlanning</a></li>
    <li class="breadcrumb-item active">{{ event }}</li>
    <li class="breadcrumb-item active">{{ event }}</li>
{% endblock %}
{% endblock %}


{% block content %}
{% block content %}
    {% include "messages.html" %}
    <div class="dashboard-row">
    <div class="dashboard-row">
        {% include "AKDashboard/dashboard_row.html" %}
        {% include "AKDashboard/dashboard_row.html" %}


        {% if recent_changes|length > 0 %}
        {% if recent_changes|length > 0 %}
            <h3 class="mt-1">{% trans "Recent" %}:</h3>
            <h3 class="mt-1" id="history">{% trans "Recent" %}:</h3>
            <ul id="recent-changes-list">
            <ul id="recent-changes-list">
                {% for recent in recent_changes %}
                {% for recent in recent_changes %}
                    <li><a href="{{ recent.link }}">{% fa5_icon recent.icon.0 recent.icon.1 %} {{ recent.text }}</a> <span style="color: #999999;">{{ recent.timestamp | timezone:event.timezone | date:"d.m. H:i" }}</span></li>
                    <li><a href="{{ recent.link }}">{% fa6_icon recent.icon.0 recent.icon.1 %} {{ recent.text }}</a> <span style="color: #999999;">{{ recent.timestamp | timezone:event.timezone | date:"d.m. H:i" }}</span></li>
                {% endfor %}
                {% endfor %}
            </ul>
            </ul>
        {% endif %}
        {% endif %}


        {% if event.contact_email %}
        {% if event.contact_email %}
            <p>
            <p>
                <a href="mailto:{{ event.contact_email }}">{% fa5_icon "envelope" "fas" %} {% trans "Write to organizers of this event for questions and comments" %}</a>
                <a href="mailto:{{ event.contact_email }}">{% fa6_icon "envelope" "fas" %} {% trans "Write to organizers of this event for questions and comments" %}</a>
            </p>
            </p>
        {% endif %}
        {% endif %}
    </div>
    </div>
Original line number Original line Diff line number Diff line
{% load i18n %}
{% load i18n %}
{% load tags_AKModel %}
{% load tags_AKModel %}
{% load fontawesome_5 %}
{% load fontawesome_6 %}


<h2><a href="{% url 'dashboard:dashboard_event' slug=event.slug %}">{{ event.name }}</a></h2>
<h2><a href="{% url 'dashboard:dashboard_event' slug=event.slug %}">{{ event.name }}</a></h2>
<div class="row">
<h4 class="text-muted">
    {% if event.place %}
        <b>{{ event.place }} &middot;</b>
    {% endif %}
    {{ event | event_month_year }}
</h4>
<div class="mt-2">
    {% if 'AKSubmission'|check_app_installed %}
    {% if 'AKSubmission'|check_app_installed %}
        <a class="dashboard-box btn btn-primary"
        <a class="dashboard-box btn btn-primary"
           href="{% url 'submit:ak_list' event_slug=event.slug %}">
           href="{% url 'submit:ak_list' event_slug=event.slug %}">
@@ -50,6 +56,24 @@
            </div>
            </div>
        </a>
        </a>
    {% endif %}
    {% endif %}
    <a class="dashboard-box btn btn-primary"
       href="{% url 'dashboard:dashboard_event' slug=event.slug %}#history">
        <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
            <span class="fa fa-history"></span>
            <span class='text'>{% trans 'AK History' %}</span>
        </div>
    </a>
    {% if 'AKPreference'|check_app_installed and event.active %}
        {% if not event.poll_hidden or user.is_staff %}
            <a class="dashboard-box btn btn-primary"
            href="{% url 'poll:poll' event_slug=event.slug %}">
                <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
                    <span class="fa fa-poll"></span>
                    <span class='text'>{% trans 'AK Preferences' %}</span>
                </div>
            </a>
        {% endif %}
    {% endif %}
    {% for button in event.dashboardbutton_set.all %}
    {% for button in event.dashboardbutton_set.all %}
        <a class="dashboard-box btn btn-{{ button.get_color_display }}"
        <a class="dashboard-box btn btn-{{ button.get_color_display }}"
           href="{{ button.url }}">
           href="{{ button.url }}">
@@ -60,4 +84,3 @@
        </a>
        </a>
    {% endfor %}
    {% endfor %}
</div>
</div>
Original line number Original line Diff line number Diff line
{% load i18n %}
{% load tags_AKModel %}
{% load fontawesome_6 %}

<h3><a href="{% url 'dashboard:dashboard_event' slug=event.slug %}">{{ event.name }}</a>
    <span class="text-muted">
        &middot;
        {% if event.place %}
            {{ event.place }} &middot;
        {% endif %}
        {{ event | event_month_year }}
    </span>
</h3>
<div class="mt-2">
    {% if 'AKSubmission'|check_app_installed %}
        <a class="btn btn-primary"
           href="{% url 'submit:ak_list' event_slug=event.slug %}">
            <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
                <span class="fa fa-list-ul"></span>
                <span class='text'>{% trans 'AK List' %}</span>
            </div>
        </a>
    {% endif %}
    {% if 'AKPlan'|check_app_installed %}
        {% if not event.plan_hidden or user.is_staff %}
            <a class="btn btn-primary"
               href="{% url 'plan:plan_overview' event_slug=event.slug %}">
                <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
                    <span class="fa fa-calendar-alt"></span>
                    <span class='text'>{% trans 'Schedule' %}</span>
                </div>
            </a>
        {% endif %}
    {% endif %}
    <a class="btn btn-primary"
       href="{% url 'dashboard:dashboard_event' slug=event.slug %}#history">
        <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
            <span class="fa fa-history"></span>
            <span class='text'>{% trans 'AK History' %}</span>
        </div>
    </a>
    {% for button in event.dashboardbutton_set.all %}
        <a class="btn btn-{{ button.get_color_display }}"
           href="{{ button.url }}">
            <div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
                {% if button.icon %}<span class="fa">{{ button.icon.as_html }}</span>{% endif %}
                <span class='text'>{{ button.text }}</span>
            </div>
        </a>
    {% endfor %}
    <a class="btn btn-info"
       href=mailto:{{ event.contact_email }}"
       title="{% trans 'Write to organizers of this event for questions and comments' %}">
            {% fa6_icon "envelope" "fas" %}
    </a>
</div>
Original line number Original line Diff line number Diff line
import pytz
import zoneinfo

from django.apps import apps
from django.apps import apps
from django.test import TestCase, override_settings
from django.test import override_settings, TestCase
from django.urls import reverse
from django.urls import reverse
from django.utils.timezone import now
from django.utils.timezone import now


from AKDashboard.models import DashboardButton
from AKDashboard.models import DashboardButton
from AKModel.models import Event, AK, AKCategory
from AKModel.models import AK, AKCategory, Event
from AKModel.tests import BasicViewTests
from AKModel.tests.test_views import BasicViewTests




class DashboardTests(TestCase):
class DashboardTests(TestCase):
    """
    Specific Dashboard Tests
    """

    @classmethod
    @classmethod
    def setUpTestData(cls):
    def setUpTestData(cls):
        """
        Initialize Test database
        """
        super().setUpTestData()
        super().setUpTestData()
        cls.event = Event.objects.create(
        cls.event = Event.objects.create(
                name="Dashboard Test Event",
                name="Dashboard Test Event",
                slug="dashboardtest",
                slug="dashboardtest",
            timezone=pytz.utc,
                timezone=zoneinfo.ZoneInfo("Europe/Berlin"),
                start=now(),
                start=now(),
                end=now(),
                end=now(),
                active=True,
                active=True,
                plan_hidden=False,
                plan_hidden=False,
                poll_hidden=False,
        )
        )
        cls.default_category = AKCategory.objects.create(
        cls.default_category = AKCategory.objects.create(
                name="Test Category",
                name="Test Category",
@@ -28,22 +37,35 @@ class DashboardTests(TestCase):
        )
        )


    def test_dashboard_view(self):
    def test_dashboard_view(self):
        """
        Check that the main dashboard is reachable
        (would also be covered by generic view testcase below)
        """
        url = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
        url = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
        response = self.client.get(url)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.status_code, 200)


    def test_nonexistent_dashboard_view(self):
    def test_nonexistent_dashboard_view(self):
        """
        Make sure there is no dashboard for an non-existing event
        """
        url = reverse('dashboard:dashboard_event', kwargs={"slug": "nonexistent-event"})
        url = reverse('dashboard:dashboard_event', kwargs={"slug": "nonexistent-event"})
        response = self.client.get(url)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)
        self.assertEqual(response.status_code, 404)


    @override_settings(DASHBOARD_SHOW_RECENT=True)
    @override_settings(DASHBOARD_SHOW_RECENT=True)
    def test_history(self):
    def test_history(self):
        """
        Test displaying of history

        For the sake of that test, the setting to show recent events in dashboard is enforced to be true
        regardless of the default configuration currently in place
        """
        url = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
        url = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})


        # History should be empty
        # History should be empty
        response = self.client.get(url)
        response = self.client.get(url)
        self.assertQuerysetEqual(response.context["recent_changes"], [])
        self.assertQuerySetEqual(response.context["recent_changes"], [])


        AK.objects.create(
        AK.objects.create(
                name="Test AK",
                name="Test AK",
@@ -57,6 +79,11 @@ class DashboardTests(TestCase):
        self.assertEqual(response.context["recent_changes"][0]['text'], "New AK: Test AK.")
        self.assertEqual(response.context["recent_changes"][0]['text'], "New AK: Test AK.")


    def test_public(self):
    def test_public(self):
        """
        Test handling of public and private events
        (only public events should be part of the standard dashboard,
        but there should be an individual dashboard for both public and private events)
        """
        url_dashboard_index = reverse('dashboard:dashboard')
        url_dashboard_index = reverse('dashboard:dashboard')
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})


@@ -66,7 +93,8 @@ class DashboardTests(TestCase):
        self.event.save()
        self.event.save()
        response = self.client.get(url_dashboard_index)
        response = self.client.get(url_dashboard_index)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.status_code, 200)
        self.assertFalse(self.event in response.context["events"])
        self.assertFalse(self.event in response.context["active_and_current_events"])
        self.assertFalse(self.event in response.context["old_events"])
        response = self.client.get(url_event_dashboard)
        response = self.client.get(url_event_dashboard)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context["event"], self.event)
        self.assertEqual(response.context["event"], self.event)
@@ -76,9 +104,12 @@ class DashboardTests(TestCase):
        self.event.save()
        self.event.save()
        response = self.client.get(url_dashboard_index)
        response = self.client.get(url_dashboard_index)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.status_code, 200)
        self.assertTrue(self.event in response.context["events"])
        self.assertTrue(self.event in response.context["active_and_current_events"])


    def test_active(self):
    def test_active(self):
        """
        Test existence of buttons with regard to activity status of the given event
        """
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})


        if apps.is_installed('AKSubmission'):
        if apps.is_installed('AKSubmission'):
@@ -95,6 +126,9 @@ class DashboardTests(TestCase):
            self.assertContains(response, "AK Submission")
            self.assertContains(response, "AK Submission")


    def test_plan_hidden(self):
    def test_plan_hidden(self):
        """
        Test visibility of plan buttons with regard to plan visibility status for a given event
        """
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})


        if apps.is_installed('AKPlan'):
        if apps.is_installed('AKPlan'):
@@ -113,7 +147,30 @@ class DashboardTests(TestCase):
            self.assertContains(response, "Current AKs")
            self.assertContains(response, "Current AKs")
            self.assertContains(response, "AK Wall")
            self.assertContains(response, "AK Wall")


    def test_poll_hidden(self):
        """
        Test visibility of poll buttons with regard to poll visibility status for a given event
        """
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})

        if apps.is_installed('AKPreference'):
            # Poll hidden? No buttons should show up
            self.event.poll_hidden = True
            self.event.save()
            response = self.client.get(url_event_dashboard)
            self.assertNotContains(response, "AK Preferences")

            # Poll not hidden?
            # Buttons to preference poll should be on the page
            self.event.poll_hidden = False
            self.event.save()
            response = self.client.get(url_event_dashboard)
            self.assertContains(response, "AK Preferences")

    def test_dashboard_buttons(self):
    def test_dashboard_buttons(self):
        """
        Make sure manually added buttons are displayed correctly
        """
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
        url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})


        response = self.client.get(url_event_dashboard)
        response = self.client.get(url_event_dashboard)
@@ -129,6 +186,9 @@ class DashboardTests(TestCase):




class DashboardViewTests(BasicViewTests, TestCase):
class DashboardViewTests(BasicViewTests, TestCase):
    """
    Generic view tests, based on :class:`AKModel.BasicViewTests` as specified in this class in VIEWS
    """
    fixtures = ['model.json', 'dashboard.json']
    fixtures = ['model.json', 'dashboard.json']


    APP_NAME = 'dashboard'
    APP_NAME = 'dashboard'
Original line number Original line Diff line number Diff line
from django.apps import apps
from django.apps import apps
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import TemplateView, DetailView
from django.views.generic import TemplateView, DetailView
@@ -10,6 +9,11 @@ from AKPlanning import settings




class DashboardView(TemplateView):
class DashboardView(TemplateView):
    """
    Index view of dashboard and therefore the main entry point for AKPlanning

    Displays information and buttons for all public events
    """
    template_name = 'AKDashboard/dashboard.html'
    template_name = 'AKDashboard/dashboard.html'


    @method_decorator(ensure_csrf_cookie)
    @method_decorator(ensure_csrf_cookie)
@@ -18,11 +22,30 @@ class DashboardView(TemplateView):


    def get_context_data(self, **kwargs):
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context = super().get_context_data(**kwargs)
        context['events'] = Event.objects.filter(public=True)
        # Load events and split between active and current/featured events and those that should show smaller below
        context["active_and_current_events"] = []
        context["old_events"] = []
        events = Event.objects.filter(public=True).order_by("-active", "-pk").prefetch_related('dashboardbutton_set')
        for event in events:
            if event.active or len(context["active_and_current_events"]) < settings.DASHBOARD_MAX_FEATURED_EVENTS:
                context["active_and_current_events"].append(event)
            else:
                context["old_events"].append(event)
        context["active_event_count"] = len(context["active_and_current_events"])
        context["old_event_count"] = len(context["old_events"])
        context["total_event_count"] = context["active_event_count"] + context["old_event_count"]
        return context
        return context




class DashboardEventView(DetailView):
class DashboardEventView(DetailView):
    """
    Dashboard view for a single event

    In addition to the basic information and the buttons,
    an overview over recent events (new and changed AKs, moved AKSlots) for the given event is shown.

    The event dashboard also exists for non-public events (one only needs to know the URL/slug of the event).
    """
    template_name = 'AKDashboard/dashboard_event.html'
    template_name = 'AKDashboard/dashboard_event.html'
    context_object_name = 'event'
    context_object_name = 'event'
    model = Event
    model = Event
@@ -32,11 +55,16 @@ class DashboardEventView(DetailView):


        # Show feed of recent changes (if activated)
        # Show feed of recent changes (if activated)
        if settings.DASHBOARD_SHOW_RECENT:
        if settings.DASHBOARD_SHOW_RECENT:
            # Create a list of chronically sorted events (both AK and plan changes):
            recent_changes = []
            recent_changes = []


            # Newest AKs
            # Newest AKs (if AKSubmission is used)
            if apps.is_installed("AKSubmission"):
            if apps.is_installed("AKSubmission"):
                submission_changes = AK.history.filter(event=context['event'])[:int(settings.DASHBOARD_RECENT_MAX)]
                # Get the latest x changes (if there are that many),
                # where x corresponds to the entry threshold configured in the settings
                # (such that the list will be completely filled even if there are no (newer) plan changes)
                submission_changes = AK.history.filter(event=context['event'])[:int(settings.DASHBOARD_RECENT_MAX)] # pylint: disable=no-member, line-too-long
                # Create textual representation including icons
                for s in submission_changes:
                for s in submission_changes:
                    if s.history_type == '+':
                    if s.history_type == '+':
                        text = _('New AK: %(ak)s.') % {'ak': s.name}
                        text = _('New AK: %(ak)s.') % {'ak': s.name}
@@ -48,19 +76,20 @@ class DashboardEventView(DetailView):
                        text = _('AK "%(ak)s" deleted.') % {'ak': s.name}
                        text = _('AK "%(ak)s" deleted.') % {'ak': s.name}
                        icon = ('times', 'fas')
                        icon = ('times', 'fas')


                    recent_changes.append({'icon': icon, 'text': text, 'link': reverse_lazy('submit:ak_detail', kwargs={
                    # Store representation in change list (still unsorted)
                        'event_slug': context['event'].slug, 'pk': s.id}), 'timestamp': s.history_date})
                    recent_changes.append(
                        {'icon': icon, 'text': text, 'link': s.instance.detail_url, 'timestamp': s.history_date}
                    )


            # Changes in plan
            # Changes in plan (if AKPlan is used and plan is publicly visible)
            if apps.is_installed("AKPlan"):
            if apps.is_installed("AKPlan") and not context['event'].plan_hidden:
                if not context['event'].plan_hidden:
                # Get the latest plan changes (again using a threshold, see above)
                    last_changed_slots = AKSlot.objects.filter(event=context['event'], start__isnull=False).order_by('-updated')[
                last_changed_slots = AKSlot.objects.select_related('ak').filter(event=context['event'], start__isnull=False).order_by('-updated')[:int(settings.DASHBOARD_RECENT_MAX)] #pylint: disable=line-too-long
                                         :int(settings.DASHBOARD_RECENT_MAX)]
                for changed_slot in last_changed_slots:
                for changed_slot in last_changed_slots:
                    # Create textual representation including icons and links and store in list (still unsorted)
                    recent_changes.append({'icon': ('clock', 'far'),
                    recent_changes.append({'icon': ('clock', 'far'),
                                           'text': _('AK "%(ak)s" (re-)scheduled.') % {'ak': changed_slot.ak.name},
                                           'text': _('AK "%(ak)s" (re-)scheduled.') % {'ak': changed_slot.ak.name},
                                               'link': reverse_lazy('submit:ak_detail', kwargs={
                                           'link': changed_slot.ak.detail_url,
                                                   'event_slug': context['event'].slug, 'pk': changed_slot.ak.id}),
                                           'timestamp': changed_slot.updated})
                                           'timestamp': changed_slot.updated})


            # Sort by change date...
            # Sort by change date...
+398 −129

File changed.

Preview size limit exceeded, changes collapsed.

+7 −0
Original line number Original line Diff line number Diff line
@@ -3,8 +3,15 @@ from django.contrib.admin.apps import AdminConfig




class AkmodelConfig(AppConfig):
class AkmodelConfig(AppConfig):
    """
    App configuration (default, only specifies name of the app)
    """
    name = 'AKModel'
    name = 'AKModel'




class AKAdminConfig(AdminConfig):
class AKAdminConfig(AdminConfig):
    """
    App configuration for admin
    Loading a custom version here allows to add additional contex and further adapt the behavior of the admin interface
    """
    default_site = 'AKModel.site.AKAdminSite'
    default_site = 'AKModel.site.AKAdminSite'