multiuser/subscriptions/notifications
This commit is contained in:
0
accounts/__init__.py
Normal file
0
accounts/__init__.py
Normal file
3
accounts/admin.py
Normal file
3
accounts/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
accounts/apps.py
Normal file
6
accounts/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'accounts'
|
24
accounts/forms.py
Normal file
24
accounts/forms.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
from .models import User
|
||||
|
||||
class CustomUserCreationForm(UserCreationForm):
|
||||
email = forms.EmailField(required=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email', 'password1', 'password2')
|
||||
|
||||
class CustomUserChangeForm(UserChangeForm):
|
||||
password = None # Passwort-Änderung über extra Formular
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('email',)
|
||||
widgets = {
|
||||
'email': forms.EmailInput(attrs={'class': 'text-input', 'placeholder': 'E-Mail-Adresse'}),
|
||||
}
|
||||
|
||||
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'}))
|
45
accounts/migrations/0001_initial.py
Normal file
45
accounts/migrations/0001_initial.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Generated by Django 5.2.5 on 2025-08-10 11:59
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
|
||||
('bio', models.TextField(blank=True, max_length=500)),
|
||||
('is_admin', models.BooleanField(default=False)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.5 on 2025-08-10 12:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='jellyfin_server',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='jellyfin_token',
|
||||
field=models.CharField(blank=True, max_length=500, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='jellyfin_user_id',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
0
accounts/migrations/__init__.py
Normal file
0
accounts/migrations/__init__.py
Normal file
39
accounts/models.py
Normal file
39
accounts/models.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
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.
|
||||
"""
|
||||
email = models.EmailField(_("email address"), unique=True)
|
||||
bio = models.TextField(max_length=500, blank=True)
|
||||
is_admin = models.BooleanField(default=False)
|
||||
|
||||
# Jellyfin fields
|
||||
jellyfin_user_id = models.CharField(max_length=100, blank=True, null=True)
|
||||
jellyfin_token = models.CharField(max_length=500, blank=True, null=True)
|
||||
jellyfin_server = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
def check_jellyfin_admin(self):
|
||||
"""Check if user is Jellyfin admin on the server"""
|
||||
from accounts.utils import JellyfinClient
|
||||
if not self.jellyfin_user_id or not self.jellyfin_token:
|
||||
return False
|
||||
try:
|
||||
client = JellyfinClient()
|
||||
return client.is_admin(self.jellyfin_user_id, self.jellyfin_token)
|
||||
except:
|
||||
# Im Fehlerfall den lokalen Status verwenden
|
||||
return self.is_admin
|
||||
|
||||
@property
|
||||
def is_jellyfin_admin(self):
|
||||
"""Check if user is admin either locally or on Jellyfin server"""
|
||||
return self.is_admin or self.check_jellyfin_admin()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("user")
|
||||
verbose_name_plural = _("users")
|
15
accounts/templates/accounts/login.html
Normal file
15
accounts/templates/accounts/login.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="auth-container">
|
||||
<h2>Anmelden</h2>
|
||||
<form method="post" class="auth-form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn-primary">Anmelden</button>
|
||||
</form>
|
||||
<div class="auth-links">
|
||||
<p>Noch kein Konto? <a href="{% url 'accounts:register' %}">Jetzt registrieren</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
12
accounts/templates/accounts/password_change.html
Normal file
12
accounts/templates/accounts/password_change.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="auth-container">
|
||||
<h2>Passwort ändern</h2>
|
||||
<form method="post" class="auth-form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn-primary">Passwort ändern</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
9
accounts/templates/accounts/password_change_done.html
Normal file
9
accounts/templates/accounts/password_change_done.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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>
|
||||
</div>
|
||||
{% endblock %}
|
97
accounts/templates/accounts/profile.html
Normal file
97
accounts/templates/accounts/profile.html
Normal file
@@ -0,0 +1,97 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_style %}
|
||||
<link rel="stylesheet" href="{% static 'css/profile.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="profile-container">
|
||||
<h2>Hallo, {{ user.username }}</h2>
|
||||
|
||||
{% if messages %}
|
||||
<div class="messages">
|
||||
{% for message in messages %}
|
||||
<div class="message {{ message.tags }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="profile-section">
|
||||
<h3>E-Mail-Adresse</h3>
|
||||
<form method="post" class="profile-form compact-form">
|
||||
{% csrf_token %}
|
||||
<div class="form-row">
|
||||
<label for="id_email">E-Mail</label>
|
||||
{{ form.email }}
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Speichern</button>
|
||||
</form>
|
||||
|
||||
{% if user.jellyfin_server %}
|
||||
<div class="jellyfin-info">
|
||||
<h4>Jellyfin-Verbindung</h4>
|
||||
<p>
|
||||
Server: {{ user.jellyfin_server }}<br>
|
||||
Status: {% if user.jellyfin_token %}Verbunden{% else %}Nicht verbunden{% endif %}<br>
|
||||
{% if user.is_jellyfin_admin %}
|
||||
<span class="badge badge-admin">Jellyfin Administrator</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="profile-section">
|
||||
<h3>Meine Abonnements</h3>
|
||||
|
||||
<h4>Serien</h4>
|
||||
{% if series_subs %}
|
||||
<div class="subscription-list">
|
||||
{% for sub in series_subs %}
|
||||
<div class="subscription-item">
|
||||
{% 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">
|
||||
{% 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>
|
||||
{% if sub.series_overview %}
|
||||
<div class="subscription-overview">{{ sub.series_overview|truncatechars:100 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="muted">Keine Serien abonniert.</p>
|
||||
{% endif %}
|
||||
|
||||
<h4>Filme</h4>
|
||||
{% if movie_subs %}
|
||||
<div class="subscription-list">
|
||||
{% for sub in movie_subs %}
|
||||
<div class="subscription-item">
|
||||
{% 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">
|
||||
{% 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>
|
||||
{% if sub.overview %}
|
||||
<div class="subscription-overview">{{ sub.overview|truncatechars:100 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="muted">Keine Filme abonniert.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
15
accounts/templates/accounts/register.html
Normal file
15
accounts/templates/accounts/register.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="auth-container">
|
||||
<h2>Registrieren</h2>
|
||||
<form method="post" class="auth-form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn-primary">Registrieren</button>
|
||||
</form>
|
||||
<div class="auth-links">
|
||||
<p>Bereits ein Konto? <a href="{% url 'accounts:login' %}">Jetzt anmelden</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
3
accounts/tests.py
Normal file
3
accounts/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
19
accounts/urls.py
Normal file
19
accounts/urls.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.urls import path
|
||||
from django.contrib.auth import views as auth_views
|
||||
from . import views
|
||||
|
||||
app_name = 'accounts'
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', views.jellyfin_login, name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||
path('register/', views.RegisterView.as_view(), name='register'),
|
||||
path('profile/', views.profile, name='profile'),
|
||||
path('password_change/', auth_views.PasswordChangeView.as_view(
|
||||
template_name='accounts/password_change.html',
|
||||
success_url='done/'
|
||||
), name='password_change'),
|
||||
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(
|
||||
template_name='accounts/password_change_done.html'
|
||||
), name='password_change_done'),
|
||||
]
|
117
accounts/utils.py
Normal file
117
accounts/utils.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from functools import wraps
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
|
||||
class JellyfinClient:
|
||||
def __init__(self):
|
||||
# Basis-Einstellungen aus den Django-Settings
|
||||
self.client = settings.JELLYFIN_CLIENT
|
||||
self.version = settings.JELLYFIN_VERSION
|
||||
self.device = settings.JELLYFIN_DEVICE
|
||||
self.device_id = settings.JELLYFIN_DEVICE_ID
|
||||
self.server_url = None # Wird später gesetzt
|
||||
self.api_key = None # Optional, wird aus den AppSettings geholt wenn nötig
|
||||
|
||||
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")
|
||||
|
||||
# Stelle sicher, dass die URL ein Protokoll hat
|
||||
if not self.server_url.startswith(('http://', 'https://')):
|
||||
self.server_url = f'http://{self.server_url}'
|
||||
|
||||
# Entferne trailing slashes
|
||||
self.server_url = self.server_url.rstrip('/')
|
||||
|
||||
headers = {
|
||||
'X-Emby-Authorization': (
|
||||
f'MediaBrowser Client="{self.client}", '
|
||||
f'Device="{self.device}", '
|
||||
f'DeviceId="{self.device_id}", '
|
||||
f'Version="{self.version}"'
|
||||
)
|
||||
}
|
||||
|
||||
auth_data = {
|
||||
'Username': username,
|
||||
'Pw': password
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f'{self.server_url}/Users/AuthenticateByName',
|
||||
json=auth_data,
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
return {
|
||||
'user_id': data['User']['Id'],
|
||||
'access_token': data['AccessToken'],
|
||||
'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.")
|
||||
except requests.exceptions.Timeout:
|
||||
raise ValueError("Zeitüberschreitung bei der Verbindung zum Server.")
|
||||
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}")
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def is_admin(self, user_id, token):
|
||||
"""Check if user is admin in Jellyfin"""
|
||||
cache_key = f'jellyfin_admin_{user_id}'
|
||||
|
||||
# Check cache first
|
||||
cached = cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
headers = {
|
||||
'X-Emby-Authorization': (
|
||||
f'MediaBrowser Client="{self.client}", '
|
||||
f'Device="{self.device}", '
|
||||
f'DeviceId="{self.device_id}", '
|
||||
f'Version="{self.version}", '
|
||||
f'Token="{token}"'
|
||||
)
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
f'{self.server_url}/Users/{user_id}',
|
||||
headers=headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
is_admin = data.get('Policy', {}).get('IsAdministrator', False)
|
||||
|
||||
# Cache result for 5 minutes
|
||||
cache.set(cache_key, is_admin, 300)
|
||||
|
||||
return is_admin
|
||||
except:
|
||||
return False
|
||||
|
||||
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.')
|
||||
return redirect('accounts:login')
|
||||
|
||||
if not request.user.is_jellyfin_admin:
|
||||
messages.error(request, 'Sie benötigen Admin-Rechte, um diese Seite zu sehen.')
|
||||
return redirect('index')
|
||||
|
||||
return view_func(request, *args, **kwargs)
|
||||
return _wrapped_view
|
132
accounts/views.py
Normal file
132
accounts/views.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.edit import CreateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth import login
|
||||
from django.conf import settings
|
||||
from .forms import CustomUserCreationForm, CustomUserChangeForm, JellyfinLoginForm
|
||||
from .models import User
|
||||
from .utils import JellyfinClient
|
||||
|
||||
class RegisterView(CreateView):
|
||||
form_class = CustomUserCreationForm
|
||||
template_name = 'accounts/register.html'
|
||||
success_url = reverse_lazy('accounts:login')
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
messages.success(self.request, 'Registrierung erfolgreich! Sie können sich jetzt anmelden.')
|
||||
return response
|
||||
|
||||
@login_required
|
||||
def profile(request):
|
||||
if request.method == 'POST':
|
||||
form = CustomUserChangeForm(request.POST, instance=request.user)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'E-Mail gespeichert.')
|
||||
return redirect('accounts:profile')
|
||||
else:
|
||||
form = CustomUserChangeForm(instance=request.user)
|
||||
|
||||
# Lade Abonnements
|
||||
series_subs = request.user.series_subscriptions.all()
|
||||
movie_subs = request.user.movie_subscriptions.all()
|
||||
|
||||
# Best-effort Backfill fehlender Poster, damit die Profilseite Bilder zeigt
|
||||
try:
|
||||
from settingspanel.models import AppSettings
|
||||
from arr_api.services import sonarr_get_series, radarr_lookup_movie_by_title
|
||||
cfg = AppSettings.current()
|
||||
# Serien
|
||||
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)
|
||||
if details and details.get('series_poster'):
|
||||
sub.series_poster = details['series_poster']
|
||||
if not sub.series_overview:
|
||||
sub.series_overview = details.get('series_overview') or ''
|
||||
if not sub.series_genres:
|
||||
sub.series_genres = details.get('series_genres') or []
|
||||
sub.save(update_fields=['series_poster', 'series_overview', 'series_genres'])
|
||||
# Filme
|
||||
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)
|
||||
if details and details.get('poster'):
|
||||
sub.poster = details['poster']
|
||||
if not sub.overview:
|
||||
sub.overview = details.get('overview') or ''
|
||||
if not sub.genres:
|
||||
sub.genres = details.get('genres') or []
|
||||
sub.save(update_fields=['poster', 'overview', 'genres'])
|
||||
except Exception:
|
||||
# still show page even if lookups fail
|
||||
pass
|
||||
|
||||
return render(request, 'accounts/profile.html', {
|
||||
'form': form,
|
||||
'series_subs': series_subs,
|
||||
'movie_subs': movie_subs,
|
||||
})
|
||||
|
||||
def jellyfin_login(request):
|
||||
if request.method == 'POST':
|
||||
form = JellyfinLoginForm(request.POST)
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data['username']
|
||||
password = form.cleaned_data['password']
|
||||
|
||||
# Jellyfin-URL aus AppSettings
|
||||
from settingspanel.models import AppSettings
|
||||
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.')
|
||||
return render(request, 'accounts/login.html', {'form': form})
|
||||
|
||||
try:
|
||||
client = JellyfinClient()
|
||||
client.server_url = server_url
|
||||
auth_result = client.authenticate(username, password)
|
||||
|
||||
if not auth_result:
|
||||
messages.error(request, 'Anmeldung fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldedaten.')
|
||||
return render(request, 'accounts/login.html', {'form': form})
|
||||
|
||||
# Existierenden User finden oder neu erstellen
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
user = User.objects.create_user(
|
||||
username=username,
|
||||
email=f"{username}@jellyfin.local"
|
||||
)
|
||||
|
||||
# Jellyfin Daten aktualisieren
|
||||
user.jellyfin_user_id = auth_result['user_id']
|
||||
user.jellyfin_token = auth_result['access_token']
|
||||
user.jellyfin_server = server_url
|
||||
user.save()
|
||||
|
||||
if auth_result['is_admin']:
|
||||
user.is_admin = True
|
||||
user.save()
|
||||
|
||||
login(request, user)
|
||||
messages.success(request, f'Willkommen, {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)}')
|
||||
# invalid form or error path
|
||||
return render(request, 'accounts/login.html', {'form': form})
|
||||
|
||||
else:
|
||||
form = JellyfinLoginForm()
|
||||
|
||||
return render(request, 'accounts/login.html', {'form': form})
|
Reference in New Issue
Block a user