usermanagement/translation/calendar

This commit is contained in:
jschaufuss@leitwerk.de
2025-08-11 12:27:30 +02:00
parent 13a5286357
commit c2bb64d961
27 changed files with 998 additions and 240 deletions

View File

@@ -10,15 +10,15 @@ class CustomUserCreationForm(UserCreationForm):
fields = ('username', 'email', 'password1', 'password2')
class CustomUserChangeForm(UserChangeForm):
password = None # Passwort-Änderung über extra Formular
password = None # Password change via separate form
class Meta:
model = User
fields = ('email',)
widgets = {
'email': forms.EmailInput(attrs={'class': 'text-input', 'placeholder': 'E-Mail-Adresse'}),
'email': forms.EmailInput(attrs={'class': 'text-input', 'placeholder': 'Email address'}),
}
class JellyfinLoginForm(forms.Form):
username = forms.CharField(label='Benutzername', widget=forms.TextInput(attrs={'class': 'form-control'}))
password = forms.CharField(label='Passwort', widget=forms.PasswordInput(attrs={'class': 'form-control'}))
username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'form-control'}))
password = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class': 'form-control'}))

View File

@@ -4,9 +4,9 @@ from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
"""
Custom User Model mit zusätzlichen Feldern und Berechtigungen.
Normale User können nur ihre eigenen Daten bearbeiten.
Admin-User können alles.
Custom User Model with additional fields and permissions.
Regular users can only edit their own data.
Admin users can edit everything.
"""
email = models.EmailField(_("email address"), unique=True)
bio = models.TextField(max_length=500, blank=True)
@@ -26,7 +26,7 @@ class User(AbstractUser):
client = JellyfinClient()
return client.is_admin(self.jellyfin_user_id, self.jellyfin_token)
except:
# Im Fehlerfall den lokalen Status verwenden
# On error, fall back to local status
return self.is_admin
@property

View File

@@ -2,14 +2,14 @@
{% block content %}
<div class="auth-container">
<h2>Anmelden</h2>
<h2>Sign in</h2>
<form method="post" class="auth-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn-primary">Anmelden</button>
<button type="submit" class="btn-primary">Sign in</button>
</form>
<div class="auth-links">
<p>Noch kein Konto? <a href="{% url 'accounts:register' %}">Jetzt registrieren</a></p>
<p>Don't have an account? <a href="{% url 'accounts:register' %}">Register now</a></p>
</div>
</div>
{% endblock %}

View File

@@ -2,11 +2,11 @@
{% block content %}
<div class="auth-container">
<h2>Passwort ändern</h2>
<h2>Change password</h2>
<form method="post" class="auth-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn-primary">Passwort ändern</button>
<button type="submit" class="btn-primary">Change password</button>
</form>
</div>
{% endblock %}

View File

@@ -2,8 +2,8 @@
{% block content %}
<div class="auth-container">
<h2>Passwort geändert</h2>
<p>Ihr Passwort wurde erfolgreich geändert.</p>
<p><a href="{% url 'accounts:profile' %}">Zurück zum Profil</a></p>
<h2>Password changed</h2>
<p>Your password has been changed successfully.</p>
<p><a href="{% url 'accounts:profile' %}">Back to profile</a></p>
</div>
{% endblock %}

View File

@@ -7,7 +7,7 @@
{% block content %}
<div class="profile-container">
<h2>Hallo, {{ user.username }}</h2>
<h2>Hello, {{ user.username }}</h2>
{% if messages %}
<div class="messages">
@@ -18,24 +18,24 @@
{% endif %}
<div class="profile-section">
<h3>E-Mail-Adresse</h3>
<h3>Email address</h3>
<form method="post" class="profile-form compact-form">
{% csrf_token %}
<div class="form-row">
<label for="id_email">E-Mail</label>
<label for="id_email">Email</label>
{{ form.email }}
</div>
<button type="submit" class="btn-primary">Speichern</button>
<button type="submit" class="btn-primary">Save</button>
</form>
{% if user.jellyfin_server %}
<div class="jellyfin-info">
<h4>Jellyfin-Verbindung</h4>
<h4>Jellyfin connection</h4>
<p>
Server: {{ user.jellyfin_server }}<br>
Status: {% if user.jellyfin_token %}Verbunden{% else %}Nicht verbunden{% endif %}<br>
Status: {% if user.jellyfin_token %}Connected{% else %}Not connected{% endif %}<br>
{% if user.is_jellyfin_admin %}
<span class="badge badge-admin">Jellyfin Administrator</span>
<span class="badge badge-admin">Jellyfin administrator</span>
{% endif %}
</p>
</div>
@@ -43,9 +43,9 @@
</div>
<div class="profile-section">
<h3>Meine Abonnements</h3>
<h3>My subscriptions</h3>
<h4>Serien</h4>
<h4>Series</h4>
{% if series_subs %}
<div class="subscription-list">
{% for sub in series_subs %}
@@ -53,11 +53,11 @@
{% if sub.series_poster %}
<img src="{{ sub.series_poster }}" alt="{{ sub.series_title }}" class="subscription-poster">
{% else %}
<img src="https://via.placeholder.com/80x120?text=Kein+Poster" alt="" class="subscription-poster">
<img src="https://via.placeholder.com/80x120?text=No+Poster" alt="" class="subscription-poster">
{% endif %}
<div class="subscription-info">
<div class="subscription-title">{{ sub.series_title }}</div>
<div class="subscription-date">Abonniert am {{ sub.created_at|date:"d.m.Y" }}</div>
<div class="subscription-date">Subscribed on {{ sub.created_at|date:"d.m.Y" }}</div>
{% if sub.series_overview %}
<div class="subscription-overview">{{ sub.series_overview|truncatechars:100 }}</div>
{% endif %}
@@ -66,10 +66,10 @@
{% endfor %}
</div>
{% else %}
<p class="muted">Keine Serien abonniert.</p>
<p class="muted">No series subscribed.</p>
{% endif %}
<h4>Filme</h4>
<h4>Movies</h4>
{% if movie_subs %}
<div class="subscription-list">
{% for sub in movie_subs %}
@@ -77,11 +77,11 @@
{% if sub.poster %}
<img src="{{ sub.poster }}" alt="{{ sub.title }}" class="subscription-poster">
{% else %}
<img src="https://via.placeholder.com/80x120?text=Kein+Poster" alt="" class="subscription-poster">
<img src="https://via.placeholder.com/80x120?text=No+Poster" alt="" class="subscription-poster">
{% endif %}
<div class="subscription-info">
<div class="subscription-title">{{ sub.title }}</div>
<div class="subscription-date">Abonniert am {{ sub.created_at|date:"d.m.Y" }}</div>
<div class="subscription-date">Subscribed on {{ sub.created_at|date:"d.m.Y" }}</div>
{% if sub.overview %}
<div class="subscription-overview">{{ sub.overview|truncatechars:100 }}</div>
{% endif %}
@@ -90,7 +90,7 @@
{% endfor %}
</div>
{% else %}
<p class="muted">Keine Filme abonniert.</p>
<p class="muted">No movies subscribed.</p>
{% endif %}
</div>
</div>

View File

@@ -2,14 +2,14 @@
{% block content %}
<div class="auth-container">
<h2>Registrieren</h2>
<h2>Register</h2>
<form method="post" class="auth-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn-primary">Registrieren</button>
<button type="submit" class="btn-primary">Register</button>
</form>
<div class="auth-links">
<p>Bereits ein Konto? <a href="{% url 'accounts:login' %}">Jetzt anmelden</a></p>
<p>Already have an account? <a href="{% url 'accounts:login' %}">Sign in</a></p>
</div>
</div>
{% endblock %}

View File

@@ -7,7 +7,7 @@ from django.contrib import messages
class JellyfinClient:
def __init__(self):
# Basis-Einstellungen aus den Django-Settings
# Base settings from Django settings
self.client = settings.JELLYFIN_CLIENT
self.version = settings.JELLYFIN_VERSION
self.device = settings.JELLYFIN_DEVICE
@@ -18,13 +18,13 @@ class JellyfinClient:
def authenticate(self, username, password):
"""Authenticate with Jellyfin and return user info if successful"""
if not self.server_url:
raise ValueError("Keine Server-URL angegeben")
raise ValueError("No server URL provided")
# Stelle sicher, dass die URL ein Protokoll hat
# Ensure the URL has a protocol
if not self.server_url.startswith(('http://', 'https://')):
self.server_url = f'http://{self.server_url}'
# Entferne trailing slashes
# Remove trailing slashes
self.server_url = self.server_url.rstrip('/')
headers = {
@@ -57,13 +57,13 @@ class JellyfinClient:
'is_admin': data['User'].get('Policy', {}).get('IsAdministrator', False)
}
except requests.exceptions.ConnectionError:
raise ValueError("Verbindung zum Server nicht möglich. Bitte überprüfen Sie die Server-URL.")
raise ValueError("Unable to connect to the server. Please check the server URL.")
except requests.exceptions.Timeout:
raise ValueError("Zeitüberschreitung bei der Verbindung zum Server.")
raise ValueError("Connection to the server timed out.")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
return None # Authentifizierung fehlgeschlagen
raise ValueError(f"HTTP-Fehler: {e.response.status_code}")
raise ValueError(f"HTTP error: {e.response.status_code}")
except Exception as e:
return None
@@ -71,7 +71,7 @@ class JellyfinClient:
"""Check if user is admin in Jellyfin"""
cache_key = f'jellyfin_admin_{user_id}'
# Check cache first
# Check cache first
cached = cache.get(cache_key)
if cached is not None:
return cached
@@ -106,11 +106,11 @@ def jellyfin_admin_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if not request.user.is_authenticated:
messages.error(request, 'Sie müssen angemeldet sein, um diese Seite zu sehen.')
messages.error(request, 'You must be logged in to view this page.')
return redirect('accounts:login')
if not request.user.is_jellyfin_admin:
messages.error(request, 'Sie benötigen Admin-Rechte, um diese Seite zu sehen.')
messages.error(request, 'You need admin rights to view this page.')
return redirect('index')
return view_func(request, *args, **kwargs)

View File

@@ -17,7 +17,7 @@ class RegisterView(CreateView):
def form_valid(self, form):
response = super().form_valid(form)
messages.success(self.request, 'Registrierung erfolgreich! Sie können sich jetzt anmelden.')
messages.success(self.request, 'Registration successful! You can now sign in.')
return response
@login_required
@@ -26,12 +26,12 @@ def profile(request):
form = CustomUserChangeForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, 'E-Mail gespeichert.')
messages.success(request, 'Email saved.')
return redirect('accounts:profile')
else:
form = CustomUserChangeForm(instance=request.user)
# Lade Abonnements
# Load subscriptions
series_subs = request.user.series_subscriptions.all()
movie_subs = request.user.movie_subscriptions.all()
@@ -40,7 +40,7 @@ def profile(request):
from settingspanel.models import AppSettings
from arr_api.services import sonarr_get_series, radarr_lookup_movie_by_title
cfg = AppSettings.current()
# Serien
# Series
for sub in series_subs:
if not sub.series_poster and sub.series_id:
details = sonarr_get_series(sub.series_id, base_url=cfg.sonarr_url, api_key=cfg.sonarr_api_key)
@@ -51,7 +51,7 @@ def profile(request):
if not sub.series_genres:
sub.series_genres = details.get('series_genres') or []
sub.save(update_fields=['series_poster', 'series_overview', 'series_genres'])
# Filme
# Movies
for sub in movie_subs:
if not sub.poster:
details = radarr_lookup_movie_by_title(sub.title, base_url=cfg.radarr_url, api_key=cfg.radarr_api_key)
@@ -84,7 +84,7 @@ def jellyfin_login(request):
app_settings = AppSettings.current()
server_url = app_settings.get_jellyfin_url()
if not server_url:
messages.error(request, 'Jellyfin Server ist nicht konfiguriert. Bitte Setup abschließen.')
messages.error(request, 'Jellyfin server is not configured. Please complete setup.')
return render(request, 'accounts/login.html', {'form': form})
try:
@@ -93,7 +93,7 @@ def jellyfin_login(request):
auth_result = client.authenticate(username, password)
if not auth_result:
messages.error(request, 'Anmeldung fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldedaten.')
messages.error(request, 'Sign in failed. Please check your credentials.')
return render(request, 'accounts/login.html', {'form': form})
# Existierenden User finden oder neu erstellen
@@ -116,13 +116,13 @@ def jellyfin_login(request):
user.save()
login(request, user)
messages.success(request, f'Willkommen, {username}!')
messages.success(request, f'Welcome, {username}!')
return redirect('arr_api:index')
except ValueError as e:
messages.error(request, str(e))
except Exception as e:
messages.error(request, f'Verbindungsfehler: {str(e)}')
messages.error(request, f'Connection error: {str(e)}')
# invalid form or error path
return render(request, 'accounts/login.html', {'form': form})