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

Add `Link` model to customize the admin startpage from the admin interface. Also add block rendering of links.

Reviewed-on: #138
Co-authored-by: marius.klein <marius.klein@alpenverein-heidelberg.de>
Co-committed-by: marius.klein <marius.klein@alpenverein-heidelberg.de>
cm-remove-jet
marius.klein 10 months ago committed by Christian Merten
parent 2a0905469d
commit c4d6581e6f

@ -52,6 +52,7 @@ JET_SIDE_MENU_ITEMS = [
{'app_label': 'startpage', 'permissions': ['startpage'], 'items': [
{'name': 'section', 'permissions': ['startpage.view_section']},
{'name': 'post', 'permissions': ['startpage.view_post']},
{'name': 'link', 'permissions': ['startpage.view_link']},
]},
{'label': 'Externe Links', 'items' : [
{ '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.conf import settings
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
@ -28,3 +30,19 @@ def media_access(request, path):
return media_unprotected(request, path)
else:
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 import forms
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):
@ -40,3 +42,14 @@ class SectionForm(forms.ModelForm):
class SectionAdmin(admin.ModelAdmin):
list_display = ['title', 'absolute_urlname']
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 ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -108,6 +108,22 @@ msgstr "Person"
msgid "Persons"
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/group_introduction.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:
verbose_name = _("Person")
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 %}
<div id="content-related">
<div class="module" id="recent-actions-module">
<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 %}
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>
<tr>
<td>
Fragen zum Kompass
</td>
<td>
<a href="mailto:{% settings_value 'DIGITAL_MAIL' %}">digitales@jdav-hd.de</a>
</td>
</tr>
</table>
<h2>Links</h2>
<div class="icon-gallery">
{% for link in external_links %}
{% if link.visible %}
<a href="{{ link.url }}" class="icon-item" target="_blank">
{% if link.icon %}
<img src="{{ link.icon.url }}" alt="">
{% else %}
<img src="{% static 'admin/img/favicon.png' %}" style="transform: scale(0.8);" alt="">
{% endif %}
<div class="icon-text">
<span class="icon-title">{{ link.title }}</span>
<span class="icon-subtext">{{ link.description }}</span>
</div>
</a>
{% endif %}
{% endfor %}
<a href="{% settings_value 'DOCS_LINK' %}" class="icon-item" target="_blank">
<img src="{% static 'admin/img/favicon.png' %}" style="transform: scale(0.8);" alt="">
<div class="icon-text">
<span class="icon-title">Kompass-Dokumentation</span>
<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>
{% include "startpage/contact.html" %}
</div>
</div>
{% endblock %}

Loading…
Cancel
Save