diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..71e7fc7 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 291d043..f1c904d 100644 --- a/config/settings.py +++ b/config/settings.py @@ -151,7 +151,6 @@ STATIC_ROOT = BASE_DIR / 'staticfiles' STATICFILES_DIRS = [ BASE_DIR / 'static', - BASE_DIR / 'assets', BASE_DIR / 'node_modules', ] @@ -179,4 +178,4 @@ if EMAIL_USE_SSL: # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..b3c7d96 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/api_clients.cpython-311.pyc b/core/__pycache__/api_clients.cpython-311.pyc new file mode 100644 index 0000000..f72f89e Binary files /dev/null and b/core/__pycache__/api_clients.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..cc337ee 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..9bdcb74 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..366ff46 Binary files /dev/null and b/core/__pycache__/utils.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..b436cab 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..80e2ba7 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,7 @@ from django.contrib import admin +from .models import ServiceSetting -# Register your models here. +@admin.register(ServiceSetting) +class ServiceSettingAdmin(admin.ModelAdmin): + list_display = ('name', 'url', 'is_active') + list_editable = ('is_active',) \ No newline at end of file diff --git a/core/api_clients.py b/core/api_clients.py new file mode 100644 index 0000000..67ee1dc --- /dev/null +++ b/core/api_clients.py @@ -0,0 +1,121 @@ +import requests +from .models import ServiceSetting + +class BaseClient: + def __init__(self, name): + try: + self.setting = ServiceSetting.objects.get(name=name, is_active=True) + self.url = self.setting.url.rstrip('/') + self.api_key = self.setting.api_key + except ServiceSetting.DoesNotExist: + self.setting = None + self.url = None + self.api_key = None + + def is_configured(self): + return self.url and self.api_key + +class JellyfinClient(BaseClient): + def __init__(self): + super().__init__('jellyfin') + + def get_info(self): + if not self.is_configured(): return None + try: + headers = {'X-Emby-Token': self.api_key} + resp = requests.get(f"{self.url}/System/Info", headers=headers, timeout=5) + if resp.status_code == 200: + return resp.json() + except: + pass + return None + + def get_recent_items(self, limit=5): + if not self.is_configured(): return [] + try: + headers = {'X-Emby-Token': self.api_key} + # Get latest media + resp = requests.get(f"{self.url}/Users/Public/Items/Latest?Limit={limit}", headers=headers, timeout=5) + if resp.status_code == 200: + return resp.json() + except: + pass + return [] + +class RadarrClient(BaseClient): + def __init__(self): + super().__init__('radarr') + + def get_status(self): + if not self.is_configured(): return None + try: + params = {'apikey': self.api_key} + resp = requests.get(f"{self.url}/api/v3/system/status", params=params, timeout=5) + if resp.status_code == 200: + return resp.json() + except: + pass + return None + + def get_queue(self): + if not self.is_configured(): return [] + try: + params = {'apikey': self.api_key} + resp = requests.get(f"{self.url}/api/v3/queue", params=params, timeout=5) + if resp.status_code == 200: + return resp.json().get('records', []) + except: + pass + return [] + +class SonarrClient(BaseClient): + def __init__(self): + super().__init__('sonarr') + + def get_status(self): + if not self.is_configured(): return None + try: + params = {'apikey': self.api_key} + resp = requests.get(f"{self.url}/api/v3/system/status", params=params, timeout=5) + if resp.status_code == 200: + return resp.json() + except: + pass + return None + + def get_queue(self): + if not self.is_configured(): return [] + try: + params = {'apikey': self.api_key} + resp = requests.get(f"{self.url}/api/v3/queue", params=params, timeout=5) + if resp.status_code == 200: + return resp.json().get('records', []) + except: + pass + return [] + +class JellyseerrClient(BaseClient): + def __init__(self): + super().__init__('jellyseerr') + + def get_status(self): + if not self.is_configured(): return None + try: + headers = {'X-Api-Key': self.api_key} + resp = requests.get(f"{self.url}/api/v1/status", headers=headers, timeout=5) + if resp.status_code == 200: + return resp.json() + except: + pass + return None + + def get_requests(self, count=5): + if not self.is_configured(): return [] + try: + headers = {'X-Api-Key': self.api_key} + resp = requests.get(f"{self.url}/api/v1/request?take={count}&skip=0&filter=all", headers=headers, timeout=5) + if resp.status_code == 200: + return resp.json().get('results', []) + except: + pass + return [] \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..82ecd10 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.7 on 2026-02-06 12:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ServiceSetting', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(choices=[('jellyfin', 'Jellyfin'), ('radarr', 'Radarr'), ('sonarr', 'Sonarr'), ('jellyseerr', 'Jellyseerr')], max_length=50, unique=True)), + ('url', models.URLField(help_text='Base URL of the service (e.g., http://192.168.1.100:8096)')), + ('api_key', models.CharField(blank=True, help_text='API Key or Token for the service', max_length=255, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..7a97750 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..e5867a0 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,17 @@ from django.db import models -# Create your models here. +class ServiceSetting(models.Model): + SERVICE_CHOICES = [ + ('jellyfin', 'Jellyfin'), + ('radarr', 'Radarr'), + ('sonarr', 'Sonarr'), + ('jellyseerr', 'Jellyseerr'), + ] + + name = models.CharField(max_length=50, choices=SERVICE_CHOICES, unique=True) + url = models.URLField(help_text="Base URL of the service (e.g., http://192.168.1.100:8096)") + api_key = models.CharField(max_length=255, blank=True, null=True, help_text="API Key or Token for the service") + is_active = models.BooleanField(default=True) + + def __str__(self): + return self.get_name_display() \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..c3be64e 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,130 @@ - +
-Welcome to your Matzeflix control center.
AppWizzy AI is collecting your requirements and applying the first changes.
-This page will refresh automatically as the plan is implemented.
-
- Runtime: Django {{ django_version }} · Python {{ python_version }}
- — UTC {{ current_time|date:"Y-m-d H:i:s" }}
-
{{ data.details|default:"No additional info" }}
+{{ item.Name }}
+{{ item.ProductionYear|default:"" }}
+Requested by {{ req.requestedBy.displayName }}
+No pending requests
+Use the cast button in the header or your mobile app to send media directly to your TV.
+Configure your service connections and API keys.
+