Add IMAP configuration UI and sidebar navigation for email inbox
- Email settings page at /administration/email/ with IMAP config form - Connection test button to verify IMAP connectivity - Sidebar link "E-Mail Eingang" for quick access - AppConfiguration model extended with email category and password type - init_config command includes IMAP default settings - DB-based IMAP config with env var fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -91,10 +91,76 @@ class Command(BaseCommand):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# E-Mail / IMAP Settings
|
||||||
|
email_settings = [
|
||||||
|
{
|
||||||
|
"key": "imap_host",
|
||||||
|
"display_name": "IMAP Server",
|
||||||
|
"description": "Hostname oder IP-Adresse des IMAP-Servers (z.B. mail.example.com)",
|
||||||
|
"value": "",
|
||||||
|
"default_value": "",
|
||||||
|
"setting_type": "text",
|
||||||
|
"category": "email",
|
||||||
|
"order": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "imap_port",
|
||||||
|
"display_name": "IMAP Port",
|
||||||
|
"description": "Port des IMAP-Servers (Standard: 993 für SSL, 143 für unverschlüsselt)",
|
||||||
|
"value": "993",
|
||||||
|
"default_value": "993",
|
||||||
|
"setting_type": "number",
|
||||||
|
"category": "email",
|
||||||
|
"order": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "imap_user",
|
||||||
|
"display_name": "IMAP Benutzername",
|
||||||
|
"description": "Benutzername / E-Mail-Adresse für die IMAP-Anmeldung",
|
||||||
|
"value": "",
|
||||||
|
"default_value": "",
|
||||||
|
"setting_type": "text",
|
||||||
|
"category": "email",
|
||||||
|
"order": 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "imap_password",
|
||||||
|
"display_name": "IMAP Passwort",
|
||||||
|
"description": "Passwort für die IMAP-Anmeldung",
|
||||||
|
"value": "",
|
||||||
|
"default_value": "",
|
||||||
|
"setting_type": "password",
|
||||||
|
"category": "email",
|
||||||
|
"order": 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "imap_folder",
|
||||||
|
"display_name": "IMAP Ordner",
|
||||||
|
"description": "Name des zu überwachenden Postfach-Ordners (Standard: INBOX)",
|
||||||
|
"value": "INBOX",
|
||||||
|
"default_value": "INBOX",
|
||||||
|
"setting_type": "text",
|
||||||
|
"category": "email",
|
||||||
|
"order": 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "imap_use_ssl",
|
||||||
|
"display_name": "SSL/TLS verwenden",
|
||||||
|
"description": "Sichere Verbindung zum IMAP-Server (empfohlen)",
|
||||||
|
"value": "True",
|
||||||
|
"default_value": "True",
|
||||||
|
"setting_type": "boolean",
|
||||||
|
"category": "email",
|
||||||
|
"order": 6,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
all_settings = paperless_settings + email_settings
|
||||||
|
|
||||||
created_count = 0
|
created_count = 0
|
||||||
updated_count = 0
|
updated_count = 0
|
||||||
|
|
||||||
for setting_data in paperless_settings:
|
for setting_data in all_settings:
|
||||||
setting, created = AppConfiguration.objects.get_or_create(
|
setting, created = AppConfiguration.objects.get_or_create(
|
||||||
key=setting_data["key"], defaults=setting_data
|
key=setting_data["key"], defaults=setting_data
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ class AppConfiguration(models.Model):
|
|||||||
|
|
||||||
SETTING_TYPE_CHOICES = [
|
SETTING_TYPE_CHOICES = [
|
||||||
("text", "Text"),
|
("text", "Text"),
|
||||||
|
("password", "Password"),
|
||||||
("number", "Number"),
|
("number", "Number"),
|
||||||
("boolean", "Boolean"),
|
("boolean", "Boolean"),
|
||||||
("url", "URL"),
|
("url", "URL"),
|
||||||
@@ -322,6 +323,7 @@ class AppConfiguration(models.Model):
|
|||||||
|
|
||||||
CATEGORY_CHOICES = [
|
CATEGORY_CHOICES = [
|
||||||
("paperless", "Paperless Integration"),
|
("paperless", "Paperless Integration"),
|
||||||
|
("email", "E-Mail / IMAP"),
|
||||||
("general", "General Settings"),
|
("general", "General Settings"),
|
||||||
("corporate", "Corporate Identity"),
|
("corporate", "Corporate Identity"),
|
||||||
("notifications", "Notifications"),
|
("notifications", "Notifications"),
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ urlpatterns = [
|
|||||||
# Administration URLs
|
# Administration URLs
|
||||||
path("administration/", views.administration, name="administration"),
|
path("administration/", views.administration, name="administration"),
|
||||||
path("administration/settings/", views.app_settings, name="app_settings"),
|
path("administration/settings/", views.app_settings, name="app_settings"),
|
||||||
|
path("administration/email/", views.email_settings, name="email_settings"),
|
||||||
path("administration/audit-log/", views.audit_log_list, name="audit_log_list"),
|
path("administration/audit-log/", views.audit_log_list, name="audit_log_list"),
|
||||||
path("administration/backup/", views.backup_management, name="backup_management"),
|
path("administration/backup/", views.backup_management, name="backup_management"),
|
||||||
path(
|
path(
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ from .system import ( # noqa: F401
|
|||||||
user_login,
|
user_login,
|
||||||
user_logout,
|
user_logout,
|
||||||
app_settings,
|
app_settings,
|
||||||
|
email_settings,
|
||||||
edit_help_box,
|
edit_help_box,
|
||||||
two_factor_setup,
|
two_factor_setup,
|
||||||
two_factor_qr,
|
two_factor_qr,
|
||||||
|
|||||||
@@ -596,6 +596,10 @@
|
|||||||
<i class="fas fa-gift"></i>
|
<i class="fas fa-gift"></i>
|
||||||
<span>Foerderungen</span>
|
<span>Foerderungen</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="sidebar-link" href="{% url 'stiftung:email_eingang_list' %}">
|
||||||
|
<i class="fas fa-envelope-open-text"></i>
|
||||||
|
<span>E-Mail Eingang</span>
|
||||||
|
</a>
|
||||||
<a class="sidebar-link" href="{% url 'stiftung:paechter_list' %}">
|
<a class="sidebar-link" href="{% url 'stiftung:paechter_list' %}">
|
||||||
<i class="fas fa-user-tie"></i>
|
<i class="fas fa-user-tie"></i>
|
||||||
<span>Paechter</span>
|
<span>Paechter</span>
|
||||||
|
|||||||
@@ -217,6 +217,12 @@
|
|||||||
<span>App Settings</span>
|
<span>App Settings</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<a href="{% url 'stiftung:email_settings' %}" class="btn btn-outline-primary w-100">
|
||||||
|
<i class="fas fa-envelope d-block mb-2 fa-2x"></i>
|
||||||
|
<span>E-Mail / IMAP</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<a href="{% url 'stiftung:audit_log_list' %}" class="btn btn-outline-primary w-100">
|
<a href="{% url 'stiftung:audit_log_list' %}" class="btn btn-outline-primary w-100">
|
||||||
<i class="fas fa-history d-block mb-2 fa-2x"></i>
|
<i class="fas fa-history d-block mb-2 fa-2x"></i>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h4 class="mb-0">
|
<h4 class="mb-0">
|
||||||
<i class="fas fa-{% if category_name == 'Paperless Integration' %}file-alt{% elif category_name == 'System' %}cog{% elif category_name == 'Database' %}database{% else %}folder{% endif %}"></i>
|
<i class="fas fa-{% if category_name == 'Paperless Integration' %}file-alt{% elif category_name == 'E-Mail / IMAP' %}envelope{% elif category_name == 'System' %}cog{% elif category_name == 'Database' %}database{% else %}folder{% endif %}"></i>
|
||||||
{{ category_name }}
|
{{ category_name }}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,9 +49,9 @@
|
|||||||
|
|
||||||
{% if setting.setting_type == 'boolean' %}
|
{% if setting.setting_type == 'boolean' %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
id="setting_{{ setting.key }}"
|
id="setting_{{ setting.key }}"
|
||||||
name="setting_{{ setting.key }}"
|
name="setting_{{ setting.key }}"
|
||||||
value="True"
|
value="True"
|
||||||
{% if setting.get_typed_value %}checked{% endif %}
|
{% if setting.get_typed_value %}checked{% endif %}
|
||||||
@@ -63,17 +63,24 @@
|
|||||||
{% if not setting.get_typed_value %}
|
{% if not setting.get_typed_value %}
|
||||||
<input type="hidden" name="setting_{{ setting.key }}" value="False">
|
<input type="hidden" name="setting_{{ setting.key }}" value="False">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif setting.setting_type == 'integer' %}
|
{% elif setting.setting_type == 'password' %}
|
||||||
<input type="number"
|
<input type="password"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="setting_{{ setting.key }}"
|
id="setting_{{ setting.key }}"
|
||||||
|
name="setting_{{ setting.key }}"
|
||||||
|
value="{{ setting.value }}"
|
||||||
|
{% if setting.is_system %}readonly{% endif %}>
|
||||||
|
{% elif setting.setting_type == 'integer' or setting.setting_type == 'number' %}
|
||||||
|
<input type="number"
|
||||||
|
class="form-control"
|
||||||
|
id="setting_{{ setting.key }}"
|
||||||
name="setting_{{ setting.key }}"
|
name="setting_{{ setting.key }}"
|
||||||
value="{{ setting.get_typed_value }}"
|
value="{{ setting.get_typed_value }}"
|
||||||
{% if setting.is_system %}readonly{% endif %}>
|
{% if setting.is_system %}readonly{% endif %}>
|
||||||
{% elif setting.setting_type == 'text' or setting.setting_type == 'url' %}
|
{% elif setting.setting_type == 'text' or setting.setting_type == 'url' %}
|
||||||
<input type="{% if setting.setting_type == 'url' %}url{% else %}text{% endif %}"
|
<input type="{% if setting.setting_type == 'url' %}url{% else %}text{% endif %}"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="setting_{{ setting.key }}"
|
id="setting_{{ setting.key }}"
|
||||||
name="setting_{{ setting.key }}"
|
name="setting_{{ setting.key }}"
|
||||||
value="{{ setting.value }}"
|
value="{{ setting.value }}"
|
||||||
{% if setting.is_system %}readonly{% endif %}>
|
{% if setting.is_system %}readonly{% endif %}>
|
||||||
|
|||||||
142
app/templates/stiftung/email_settings.html
Normal file
142
app/templates/stiftung/email_settings.html
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }} - Stiftung{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumb %}
|
||||||
|
<a href="{% url 'stiftung:home' %}">Stiftungsverwaltung</a>
|
||||||
|
<span class="mx-1">/</span>
|
||||||
|
<a href="{% url 'stiftung:administration' %}">Administration</a>
|
||||||
|
<span class="mx-1">/</span>
|
||||||
|
{{ title }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h3 class="card-title mb-0">
|
||||||
|
<i class="fas fa-envelope"></i>
|
||||||
|
{{ title }}
|
||||||
|
</h3>
|
||||||
|
<a href="{% url 'stiftung:administration' %}" class="btn btn-sm btn-secondary">
|
||||||
|
<i class="fas fa-arrow-left"></i> Zurück
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
{% if test_result %}
|
||||||
|
<div class="alert alert-{% if test_result.success %}success{% else %}danger{% endif %} alert-dismissible fade show">
|
||||||
|
<i class="fas fa-{% if test_result.success %}check-circle{% else %}exclamation-triangle{% endif %} me-1"></i>
|
||||||
|
{{ test_result.message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% for setting in imap_settings %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="setting_{{ setting.key }}" class="form-label">
|
||||||
|
<strong>{{ setting.display_name }}</strong>
|
||||||
|
</label>
|
||||||
|
{% if setting.description %}
|
||||||
|
<div class="form-text mb-1">{{ setting.description }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if setting.setting_type == 'boolean' %}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="form-check-input"
|
||||||
|
id="setting_{{ setting.key }}"
|
||||||
|
name="setting_{{ setting.key }}"
|
||||||
|
value="True"
|
||||||
|
{% if setting.get_typed_value %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="setting_{{ setting.key }}">Aktiviert</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% elif setting.setting_type == 'password' %}
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="setting_{{ setting.key }}"
|
||||||
|
name="setting_{{ setting.key }}"
|
||||||
|
value="{{ setting.value }}"
|
||||||
|
placeholder="{% if setting.value %}••••••••{% else %}Passwort eingeben{% endif %}">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword(this)" title="Passwort anzeigen">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% elif setting.setting_type == 'number' %}
|
||||||
|
<input type="number"
|
||||||
|
class="form-control"
|
||||||
|
id="setting_{{ setting.key }}"
|
||||||
|
name="setting_{{ setting.key }}"
|
||||||
|
value="{{ setting.value }}">
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="setting_{{ setting.key }}"
|
||||||
|
name="setting_{{ setting.key }}"
|
||||||
|
value="{{ setting.value }}">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="submit" name="action" value="save" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i> Speichern
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="action" value="test" class="btn btn-outline-primary">
|
||||||
|
<i class="fas fa-plug me-1"></i> Verbindung testen
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'stiftung:email_eingang_list' %}" class="btn btn-outline-secondary ms-auto">
|
||||||
|
<i class="fas fa-inbox me-1"></i> Zum Posteingang
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info sidebar -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-info-circle"></i> Hinweise
|
||||||
|
</div>
|
||||||
|
<div class="card-body" style="font-size: 0.85rem;">
|
||||||
|
<p>Konfigurieren Sie hier die IMAP-Verbindung zum E-Mail-Server. Eingehende E-Mails werden automatisch alle <strong>15 Minuten</strong> abgerufen und den Destinatären zugeordnet.</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-1"><strong>Typische Einstellungen:</strong></p>
|
||||||
|
<ul class="mb-0" style="font-size: 0.8rem;">
|
||||||
|
<li>SSL/TLS: Port <code>993</code></li>
|
||||||
|
<li>Unverschlüsselt: Port <code>143</code></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0"><i class="fas fa-shield-alt text-success me-1"></i> Das Passwort wird in der Datenbank gespeichert. Umgebungsvariablen (<code>IMAP_HOST</code>, etc.) werden als Fallback verwendet, wenn hier keine Werte gesetzt sind.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function togglePassword(btn) {
|
||||||
|
const input = btn.parentElement.querySelector('input');
|
||||||
|
const icon = btn.querySelector('i');
|
||||||
|
if (input.type === 'password') {
|
||||||
|
input.type = 'text';
|
||||||
|
icon.classList.replace('fa-eye', 'fa-eye-slash');
|
||||||
|
} else {
|
||||||
|
input.type = 'password';
|
||||||
|
icon.classList.replace('fa-eye-slash', 'fa-eye');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user