Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ A Django application to track and rate the media I consume: movies, TV shows, bo
- `SECRET_KEY`: Generate with `python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"`
- `ALLOWED_HOSTS`: Add your domain and IP (e.g., `datakult.example.com,192.168.1.100,localhost`)
- `DJANGO_SUPERUSER_PASSWORD`: Use a secure password
- `TMDB_API_KEY`: If you want to be able to import metadata from TMDB

4. Start the application:
```bash
Expand Down
4 changes: 4 additions & 0 deletions docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ ALLOWED_HOSTS=localhost,127.0.0.1
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@example.com
DJANGO_SUPERUSER_PASSWORD=change-this-password

# API keys (optional - for importing metadata from TMDB)
# Leave empty to disable TMDB integration
TMDB_API_KEY=
2 changes: 2 additions & 0 deletions docker/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ services:
- DJANGO_SUPERUSER_USERNAME=${DJANGO_SUPERUSER_USERNAME:-admin}
- DJANGO_SUPERUSER_EMAIL=${DJANGO_SUPERUSER_EMAIL:-admin@example.com}
- DJANGO_SUPERUSER_PASSWORD=${DJANGO_SUPERUSER_PASSWORD:-admin}
# Optional - leave empty to disable TMDB integration
- TMDB_API_KEY=${TMDB_API_KEY:-}
restart: unless-stopped
6 changes: 6 additions & 0 deletions src/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@

TAILWIND_APP_NAME = "theme"

# =============================================================================
# External API Keys
# =============================================================================

TMDB_API_KEY = os.environ.get("TMDB_API_KEY", "")

# =============================================================================
# Security Settings for Production (behind reverse proxy like Cloudflare Tunnel)
# =============================================================================
Expand Down
3 changes: 2 additions & 1 deletion src/core/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.contrib import admin

from core.models import Agent, Media, SavedView
from core.models import Agent, Media, SavedView, Tag

admin.site.register(Agent)
admin.site.register(Media)
admin.site.register(SavedView)
admin.site.register(Tag)
30 changes: 21 additions & 9 deletions src/core/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.db.models import Q
from django.utils.translation import gettext as _

from .models import Agent, Media
from .models import Agent, Media, Tag


def resolve_sorting(request):
Expand All @@ -27,6 +27,7 @@ def extract_filters(request):
"""Extract filter parameters from request and return filters dict."""
filters = {
"contributor": request.GET.get("contributor", ""),
"tag": request.GET.get("tag", ""),
"type": request.GET.getlist("type"),
"status": request.GET.getlist("status"),
"score": request.GET.getlist("score"),
Expand Down Expand Up @@ -79,15 +80,25 @@ def get_field_choices():
}


def _apply_related_filter(queryset, model, pk_value, filter_field):
"""Apply a filter based on a related model lookup."""
instance = None
if pk_value:
with contextlib.suppress(ValueError, TypeError):
instance = model.objects.filter(pk=pk_value).first()
if instance:
queryset = queryset.filter(**{filter_field: instance})
return queryset, instance


def apply_contributor_filter(queryset, contributor_id):
"""Apply contributor filter to queryset and return (queryset, contributor)."""
return _apply_related_filter(queryset, Agent, contributor_id, "contributors")


contributor = None
if contributor_id:
contributor = Agent.objects.filter(pk=contributor_id).first()
if contributor:
queryset = queryset.filter(contributors=contributor)
return queryset, contributor
def apply_tag_filter(queryset, tag_id):
"""Apply tag filter to queryset and return (queryset, tag)."""
return _apply_related_filter(queryset, Tag, tag_id, "tags")


def apply_type_filter(queryset, media_types):
Expand Down Expand Up @@ -143,10 +154,11 @@ def apply_date_and_content_filters(queryset, filters):


def apply_filters(queryset, filters):
"""Apply filters to a queryset and return (queryset, contributor)."""
"""Apply filters to a queryset and return (queryset, contributor, tag)."""
queryset, contributor = apply_contributor_filter(queryset, filters["contributor"])
queryset, tag = apply_tag_filter(queryset, filters["tag"])
queryset = apply_type_filter(queryset, filters["type"])
queryset = apply_status_filter(queryset, filters["status"])
queryset = apply_score_filter(queryset, filters["score"])
queryset = apply_date_and_content_filters(queryset, filters)
return queryset, contributor
return queryset, contributor, tag
125 changes: 120 additions & 5 deletions src/core/fixtures/sample_data.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,58 @@
[
{
"model": "core.tag",
"pk": 1,
"fields": {
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z",
"name": "Science-Fiction"
}
},
{
"model": "core.tag",
"pk": 2,
"fields": {
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z",
"name": "Animation"
}
},
{
"model": "core.tag",
"pk": 3,
"fields": {
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z",
"name": "Thriller"
}
},
{
"model": "core.tag",
"pk": 4,
"fields": {
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z",
"name": "Fantasy"
}
},
{
"model": "core.tag",
"pk": 5,
"fields": {
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z",
"name": "Action-RPG"
}
},
{
"model": "core.tag",
"pk": 6,
"fields": {
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z",
"name": "Electronic"
}
},
{
"model": "core.agent",
"pk": 1,
Expand Down Expand Up @@ -140,6 +194,7 @@
"score": 9,
"review_date": "2021-10",
"contributors": [2],
"tags": [1],
"cover": "covers/cover_01.jpg"
}
},
Expand All @@ -158,6 +213,7 @@
"score": 10,
"review_date": "2020",
"contributors": [3],
"tags": [1],
"cover": "covers/cover_02.jpg"
}
},
Expand All @@ -176,6 +232,7 @@
"score": 8,
"review_date": "2014-11-15",
"contributors": [1],
"tags": [1],
"cover": "covers/cover_03.jpg"
}
},
Expand All @@ -194,6 +251,7 @@
"score": 10,
"review_date": "2015",
"contributors": [4],
"tags": [2, 4],
"cover": "covers/cover_04.jpg"
}
},
Expand All @@ -211,7 +269,8 @@
"review": "",
"score": null,
"review_date": null,
"contributors": [5]
"contributors": [5],
"tags": [1, 5]
}
},
{
Expand All @@ -229,6 +288,7 @@
"score": 9,
"review_date": "2018",
"contributors": [6],
"tags": [6],
"cover": "covers/cover_06.jpg"
}
},
Expand All @@ -247,6 +307,7 @@
"score": 10,
"review_date": "2020",
"contributors": [],
"tags": [3],
"cover": "covers/cover_07.jpg"
}
},
Expand All @@ -264,7 +325,8 @@
"review": "A sequel that's even more **epic** than the first installment. The action sequences are *breathtaking*, and the character development reaches new heights. Villeneuve has created something truly special here.",
"score": 9,
"review_date": "2024-03",
"contributors": [2]
"contributors": [2],
"tags": [1]
}
},
{
Expand All @@ -282,6 +344,7 @@
"score": 9,
"review_date": "2018",
"contributors": [7],
"tags": [5],
"cover": "covers/cover_09.jpg"
}
},
Expand All @@ -300,6 +363,7 @@
"score": 9,
"review_date": "2019",
"contributors": [8],
"tags": [1],
"cover": "covers/cover_10.jpg"
}
},
Expand All @@ -318,6 +382,7 @@
"score": 9,
"review_date": "2016",
"contributors": [9],
"tags": [3],
"cover": "covers/cover_11.jpg"
}
},
Expand All @@ -335,7 +400,8 @@
"review": "A **brilliant** social thriller that blends genres masterfully. Bong Joon-ho's commentary on class struggle is *devastatingly effective*, shifting from comedy to horror with seamless precision. The film's structure is **perfect**, with each act building tension until the explosive finale.",
"score": 10,
"review_date": "2020-02",
"contributors": [10]
"contributors": [10],
"tags": [3]
}
},
{
Expand All @@ -352,7 +418,8 @@
"review": "An **ambitious album** that celebrates analog music and live instrumentation. Daft Punk took a bold departure from their electronic roots, collaborating with legendary musicians. *Get Lucky* became an instant classic.",
"score": 8,
"review_date": "2013",
"contributors": [11]
"contributors": [11],
"tags": [6]
}
},
{
Expand All @@ -370,6 +437,7 @@
"score": null,
"review_date": null,
"contributors": [12],
"tags": [4, 5],
"cover": "covers/cover_14.jpg"
}
},
Expand All @@ -388,6 +456,7 @@
"score": 10,
"review_date": "2021",
"contributors": [13],
"tags": [4],
"cover": "covers/cover_15.jpg"
}
},
Expand Down Expand Up @@ -423,6 +492,7 @@
"score": 9,
"review_date": "2017",
"contributors": [12],
"tags": [4, 5],
"cover": "covers/cover_17.jpg"
}
},
Expand All @@ -441,6 +511,7 @@
"score": 9,
"review_date": "2014",
"contributors": [4],
"tags": [2, 4],
"cover": "covers/cover_18.jpg"
}
},
Expand Down Expand Up @@ -477,6 +548,7 @@
"score": 9,
"review_date": "2019",
"contributors": [6],
"tags": [6],
"cover": "covers/cover_20.jpg"
}
},
Expand All @@ -495,6 +567,7 @@
"score": null,
"review_date": null,
"contributors": [13],
"tags": [4],
"cover": "covers/cover_21.jpg"
}
},
Expand All @@ -512,7 +585,8 @@
"review": "",
"score": null,
"review_date": null,
"contributors": [10]
"contributors": [10],
"tags": [3]
}
},
{
Expand All @@ -530,7 +604,48 @@
"score": 6,
"review_date": "2016",
"contributors": [5],
"tags": [5],
"cover": "covers/cover_23.jpg"
}
},
{
"model": "core.savedview",
"pk": 1,
"fields": {
"user": 1,
"name": "Movies to watch",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z",
"filter_types": ["FILM"],
"filter_statuses": ["PLANNED"],
"filter_scores": [],
"filter_contributor_id": null,
"filter_review_from": "",
"filter_review_to": "",
"filter_has_review": "",
"filter_has_cover": "",
"sort": "title",
"view_mode": "list"
}
},
{
"model": "core.savedview",
"pk": 2,
"fields": {
"user": 1,
"name": "Faves",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z",
"filter_types": [],
"filter_statuses": ["COMPLETED"],
"filter_scores": [9, 10],
"filter_contributor_id": null,
"filter_review_from": "",
"filter_review_to": "",
"filter_has_review": "yes",
"filter_has_cover": "",
"sort": "-score",
"view_mode": "grid"
}
}
]
Loading