feat(startpage): add link model for external links on admin startpage #138

Merged
christian.merten merged 4 commits from MK/startpage_gallery into main 10 months ago

@ -52,6 +52,7 @@ JET_SIDE_MENU_ITEMS = [
{'app_label': 'startpage', 'permissions': ['startpage'], 'items': [ {'app_label': 'startpage', 'permissions': ['startpage'], 'items': [
{'name': 'section', 'permissions': ['startpage.view_section']}, {'name': 'section', 'permissions': ['startpage.view_section']},
{'name': 'post', 'permissions': ['startpage.view_post']}, {'name': 'post', 'permissions': ['startpage.view_post']},
{'name': 'link', 'permissions': ['startpage.view_link']},
]}, ]},
{'label': 'Externe Links', 'items' : [ {'label': 'Externe Links', 'items' : [
{ 'label': 'Nextcloud', 'url': CLOUD_LINK, 'url_blank': True }, { 'label': 'Nextcloud', 'url': CLOUD_LINK, 'url_blank': True },

@ -2,7 +2,9 @@ from django.http import HttpResponse
from django.views.static import serve from django.views.static import serve
from django.conf import settings from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.contrib import admin
from django.shortcuts import render
from startpage.models import Link
import re import re
@ -28,3 +30,19 @@ def media_access(request, path):
return media_unprotected(request, path) return media_unprotected(request, path)
else: else:
return media_protected(request, path) return media_protected(request, path)
def custom_admin_view(request):
"""
this methods provides access to models in order to render a custom admin page index site.
"""
app_list = admin.site.get_app_list(request)
context = {
'app_list': app_list,
'site_header': admin.site.site_header,
'site_title': admin.site.site_title,
'external_links': Link.objects.all()
}
return render(request, 'admin/index.html', context)
admin.site.index = custom_admin_view

@ -2,8 +2,10 @@ from django.contrib import admin
from django.conf import settings from django.conf import settings
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.db.models import TextField
from django.forms import Textarea
from .models import Post, Image, Section, MemberOnPost from .models import Post, Image, Section, MemberOnPost, Link
class ImageInline(admin.TabularInline): class ImageInline(admin.TabularInline):
@ -40,3 +42,14 @@ class SectionForm(forms.ModelForm):
class SectionAdmin(admin.ModelAdmin): class SectionAdmin(admin.ModelAdmin):
list_display = ['title', 'absolute_urlname'] list_display = ['title', 'absolute_urlname']
form = SectionForm form = SectionForm
@admin.register(Link)
class LinkAdmin(admin.ModelAdmin):
list_display = ['title', 'url', 'visible']
formfield_overrides = {
TextField: {'widget': Textarea(attrs={'rows': 2, 'cols': 40})}
}

@ -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: 2025-02-01 21:11+0100\n" "POT-Creation-Date: 2025-02-25 13:22+0100\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"
@ -108,6 +108,22 @@ msgstr "Person"
msgid "Persons" msgid "Persons"
msgstr "Personen" msgstr "Personen"
#: startpage/models.py
msgid "Link Icon"
msgstr "Link Logo"
#: startpage/models.py
msgid "Visible"
msgstr "Sichtbar"
#: startpage/models.py
msgid "Link"
msgstr "Link"
#: startpage/models.py
msgid "Links"
msgstr "Links"
#: startpage/templates/startpage/faq_content.html #: startpage/templates/startpage/faq_content.html
#: startpage/templates/startpage/group_introduction.html #: startpage/templates/startpage/group_introduction.html
#: startpage/templates/startpage/impressum_content.html #: startpage/templates/startpage/impressum_content.html

@ -0,0 +1,29 @@
# Generated by Django 4.0.1 on 2025-02-25 12:20
from django.db import migrations, models
import utils
class Migration(migrations.Migration):
dependencies = [
('startpage', '0003_alter_post_section'),
]
operations = [
migrations.CreateModel(
name='Link',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('icon', utils.RestrictedFileField(blank=True, upload_to='icons', verbose_name='Link Icon')),
('title', models.CharField(blank=True, default='', max_length=100, verbose_name='Title')),
('description', models.TextField(blank=True, default='', verbose_name='Description')),
('url', models.URLField(max_length=250)),
('visible', models.BooleanField(default=True, verbose_name='Visible')),
],
options={
'verbose_name': 'Link',
'verbose_name_plural': 'Links',
},
),
]

@ -101,3 +101,30 @@ class MemberOnPost(models.Model):
class Meta: class Meta:
verbose_name = _("Person") verbose_name = _("Person")
verbose_name_plural = _("Persons") verbose_name_plural = _("Persons")
class Link(models.Model):
"""
Link to external resources that should be shown on the internal startpage.
"""
title = models.CharField(_('Title'), max_length=100, default='', blank=True)
description = models.TextField(_('Description'), default='', blank=True)
url = models.URLField(max_length=250)
icon = RestrictedFileField(verbose_name=_('Link Icon'),
upload_to='icons',
blank=True,
max_upload_size=5,
content_types=['image/jpeg',
'image/png',
'image/gif'])
visible = models.BooleanField(verbose_name=_('Visible'), default=True)
class Meta:
verbose_name = _('Link')
verbose_name_plural = _('Links')
def __str__(self):
return self.title

@ -0,0 +1,20 @@
{% load common %}
<h2>Ansprechpersonen</h2>
{% comment %}
Nicht alles im Leben ist ein automatisierter Prozess. Falls du trotz KOMPASS nicht mehr
weiterweißt oder sonst der Schuh drückt, schreibe eine E-Mail an eine der folgenden Personen:
{% endcomment %}
<div>
<table>
<tr>
<td>
Jugendreferat
</td>
<td>
<a href="mailto:{% settings_value 'RESPONSIBLE_MAIL' %}">jugendreferat@jdav-hd.de</a>
</td>
</tr>
</table>
</div>

@ -98,67 +98,103 @@ Hier kannst du E-Mails an deine Gruppe oder an andere Menschen in der JDAV {% se
{% block sidebar %} {% block sidebar %}
<div id="content-related"> <div id="content-related">
<div class="module" id="recent-actions-module"> <div class="module" id="recent-actions-module">
<div>
<h2>Links</h2> <h2>Links</h2>
<table>
<tr>
<td>
<a href="{% settings_value 'CLOUD_LINK' %}">Nextcloud</a>
</td>
<td>
Hier liegen Vorlagen für Formulare und nützliche Handbücher.
</td>
</tr>
<tr>
<td>
<a href="{% settings_value 'DAV_360_LINK' %}">DAV 360</a>
</td>
<td>
Zugriff zu Online Office, Teams und deinem DAV Mailaccount.
</td>
</tr>
<tr>
<td>
<a href="{% settings_value 'WIKI_LINK' %}">Julei-Wiki</a>
</td>
<td>
Informationen zum Jugendleiter*in-sein.
</td>
</tr>
<tr>
<td>
<a href="{% settings_value 'DOCS_LINK' %}">Dokumentation</a>
</td>
<td>
Kompass Dokumentation
</td>
</tr>
</table>
<h2>Ansprechpersonen</h2>
{% comment %} <div class="icon-gallery">
Nicht alles im Leben ist ein automatisierter Prozess. Falls du trotz KOMPASS nicht mehr {% for link in external_links %}
weiterweißt oder sonst der Schuh drückt, schreibe eine E-Mail an eine der folgenden Personen: {% if link.visible %}
{% endcomment %}
<div> <a href="{{ link.url }}" class="icon-item" target="_blank">
<table> {% if link.icon %}
<tr> <img src="{{ link.icon.url }}" alt="">
<td> {% else %}
Jugendreferat <img src="{% static 'admin/img/favicon.png' %}" style="transform: scale(0.8);" alt="">
</td> {% endif %}
<td> <div class="icon-text">
<a href="mailto:{% settings_value 'RESPONSIBLE_MAIL' %}">jugendreferat@jdav-hd.de</a> <span class="icon-title">{{ link.title }}</span>
</td> <span class="icon-subtext">{{ link.description }}</span>
</tr> </div>
<tr> </a>
<td> {% endif %}
Fragen zum Kompass {% endfor %}
</td>
<td> <a href="{% settings_value 'DOCS_LINK' %}" class="icon-item" target="_blank">
<a href="mailto:{% settings_value 'DIGITAL_MAIL' %}">digitales@jdav-hd.de</a> <img src="{% static 'admin/img/favicon.png' %}" style="transform: scale(0.8);" alt="">
</td> <div class="icon-text">
</tr> <span class="icon-title">Kompass-Dokumentation</span>
</table> <span class="icon-subtext">Anleitung zur Benutzung des Kompasses</span>
</div>
</a>
</div>
<style>
.icon-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); /* Responsive columns */
grid-auto-rows: minmax(150px, auto); /* Prevents overlap by enforcing row height */
gap: 15px;
justify-content: center;
padding: 0 0 20px 0;
min-width: 484px;
/* max-width: 800px; Optional: Adjust based on design */
margin: auto; /* Centers the grid */
}
.icon-item {
text-decoration: none;
text-align: center;
background: white;
padding: 10px;
border-radius: 4px;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between; /* Ensures even spacing */
/*height: 100%; Forces uniform height */
/*min-height: 150px; Ensures all boxes have at least this height */
}
.icon-item img {
width: 60px;
height: 60px;
object-fit: contain;
margin-bottom: 8px;
}
.icon-text {
display: flex;
flex-direction: column;
align-items: center;
flex-grow: 1; /* Ensures text takes up available space */
}
.icon-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 5px;
}
.icon-subtext {
font-size: 12px;
color: #666;
}
.icon-item:hover {
transform: translateY(-3px);
}
</style>
</div> </div>
{% include "startpage/contact.html" %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

Loading…
Cancel
Save