usermanagement/translation/calendar
This commit is contained in:
		| @@ -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'})) | ||||
|   | ||||
| @@ -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  | ||||
|   | ||||
| @@ -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 %} | ||||
| @@ -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 %} | ||||
| @@ -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 %} | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 %} | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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}) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 jschaufuss@leitwerk.de
					jschaufuss@leitwerk.de