Skip to content

Commit 439ebff

Browse files
committed
feat: importing from an existing media opens the correct tab and pre-fills the search
1 parent b013b4f commit 439ebff

4 files changed

Lines changed: 142 additions & 13 deletions

File tree

src/core/views.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,27 @@ def _fetch_musicbrainz_data(mbid: str) -> dict | None:
341341

342342
@login_required
343343
def media_import(request):
344-
"""Display TMDB search page for importing media."""
345-
# Optional: if editing existing media, pass media_id to template
344+
"""Display search page for importing media metadata."""
346345
media_id = request.GET.get("media_id")
347-
context = {"media_id": media_id}
346+
media_type = request.GET.get("media_type", "")
347+
title = request.GET.get("title", "")
348+
349+
# Map media_type to the appropriate import source
350+
source_mapping = {
351+
"FILM": "tmdb",
352+
"TV": "tmdb",
353+
"GAME": "igdb",
354+
"BOOK": "openlibrary",
355+
"COMIC": "openlibrary",
356+
"MUSIC": "musicbrainz",
357+
}
358+
default_source = source_mapping.get(media_type, "tmdb")
359+
360+
context = {
361+
"media_id": media_id,
362+
"default_source": default_source,
363+
"default_query": title,
364+
}
348365
return render(request, "base/media_import.html", context)
349366

350367

src/templates/base/media_edit.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ <h1 class="text-4xl font-bold">
3535
</h1>
3636
</div>
3737
<div class="flex gap-2">
38-
<a href="{% url 'media_import' %}{% if media %}?media_id={{ media.pk }}{% endif %}"
38+
<a href="{% url 'media_import' %}{% if media %}?media_id={{ media.pk }}&media_type={{ media.media_type }}&title={{ media.title|urlencode }}{% endif %}"
3939
class="btn btn-outline btn-sm"
4040
title="{% translate 'Import metadata' %}">
4141
{% lucide "download" class="w-4 h-4" %}

src/templates/base/media_import.html

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,36 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
1717
class="tab"
1818
aria-label="{% translate 'Movies & TV' %}"
1919
id="tab-tmdb"
20-
checked
20+
{% if default_source == "tmdb" or not default_source %}checked{% endif %}
2121
autocomplete="off"
2222
hx-on:click="document.getElementById('source-tmdb').classList.remove('hidden'); document.getElementById('source-igdb').classList.add('hidden'); document.getElementById('source-openlibrary').classList.add('hidden'); document.getElementById('source-musicbrainz').classList.add('hidden'); document.getElementById('import-results').innerHTML='';">
2323
<input type="radio"
2424
name="import_source"
2525
class="tab"
2626
aria-label="{% translate 'Video games' %}"
2727
id="tab-igdb"
28+
{% if default_source == "igdb" %}checked{% endif %}
2829
autocomplete="off"
2930
hx-on:click="document.getElementById('source-igdb').classList.remove('hidden'); document.getElementById('source-tmdb').classList.add('hidden'); document.getElementById('source-openlibrary').classList.add('hidden'); document.getElementById('source-musicbrainz').classList.add('hidden'); document.getElementById('import-results').innerHTML='';">
3031
<input type="radio"
3132
name="import_source"
3233
class="tab"
3334
aria-label="{% translate 'Books' %}"
3435
id="tab-openlibrary"
36+
{% if default_source == "openlibrary" %}checked{% endif %}
3537
autocomplete="off"
3638
hx-on:click="document.getElementById('source-openlibrary').classList.remove('hidden'); document.getElementById('source-tmdb').classList.add('hidden'); document.getElementById('source-igdb').classList.add('hidden'); document.getElementById('source-musicbrainz').classList.add('hidden'); document.getElementById('import-results').innerHTML='';">
3739
<input type="radio"
3840
name="import_source"
3941
class="tab"
4042
aria-label="{% translate 'Music' %}"
4143
id="tab-musicbrainz"
44+
{% if default_source == "musicbrainz" %}checked{% endif %}
4245
autocomplete="off"
4346
hx-on:click="document.getElementById('source-musicbrainz').classList.remove('hidden'); document.getElementById('source-tmdb').classList.add('hidden'); document.getElementById('source-igdb').classList.add('hidden'); document.getElementById('source-openlibrary').classList.add('hidden'); document.getElementById('import-results').innerHTML='';">
4447
</div>
4548
{# TMDB Search (Movies & TV) #}
46-
<div id="source-tmdb" class="card bg-base-200 shadow-md mb-6">
49+
<div id="source-tmdb" class="card bg-base-200 shadow-md mb-6{% if default_source and default_source != 'tmdb' %} hidden{% endif %}">
4750
<div class="card-body p-4 space-y-3">
4851
<div class="flex items-center gap-2 mb-2">
4952
{% lucide "film" class="w-5 h-5" %}
@@ -56,9 +59,10 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
5659
name="q"
5760
class="input join-item w-full"
5861
placeholder="{% translate 'Search...' %}"
62+
value="{{ default_query }}"
5963
autocomplete="off"
6064
hx-get="{% url 'tmdb_search_htmx' %}{% if media_id %}?media_id={{ media_id }}{% endif %}"
61-
hx-trigger="keyup changed delay:400ms, search"
65+
hx-trigger="keyup changed delay:400ms, search{% if default_query and default_source == 'tmdb' %}, load{% endif %}"
6266
hx-target="#import-results"
6367
hx-include="#tmdb-lang"
6468
hx-indicator="#search-spinner" />
@@ -84,7 +88,7 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
8488
</div>
8589
</div>
8690
{# IGDB Search (Video games) #}
87-
<div id="source-igdb" class="card bg-base-200 shadow-md mb-6 hidden">
91+
<div id="source-igdb" class="card bg-base-200 shadow-md mb-6{% if default_source != 'igdb' %} hidden{% endif %}">
8892
<div class="card-body p-4 space-y-3">
8993
<div class="flex items-center gap-2 mb-2">
9094
{% lucide "gamepad-2" class="w-5 h-5" %}
@@ -97,9 +101,10 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
97101
name="q"
98102
class="input join-item w-full"
99103
placeholder="{% translate 'Search...' %}"
104+
value="{{ default_query }}"
100105
autocomplete="off"
101106
hx-get="{% url 'igdb_search_htmx' %}{% if media_id %}?media_id={{ media_id }}{% endif %}"
102-
hx-trigger="keyup changed delay:400ms, search"
107+
hx-trigger="keyup changed delay:400ms, search{% if default_query and default_source == 'igdb' %}, load{% endif %}"
103108
hx-target="#import-results"
104109
hx-indicator="#search-spinner" />
105110
<button type="button"
@@ -113,7 +118,7 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
113118
</div>
114119
{# OpenLibrary Search (Books) #}
115120
<div id="source-openlibrary"
116-
class="card bg-base-200 shadow-md mb-6 hidden">
121+
class="card bg-base-200 shadow-md mb-6{% if default_source != 'openlibrary' %} hidden{% endif %}">
117122
<div class="card-body p-4 space-y-3">
118123
<div class="flex items-center gap-2 mb-2">
119124
{% lucide "book-open" class="w-5 h-5" %}
@@ -126,9 +131,10 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
126131
name="q"
127132
class="input join-item w-full"
128133
placeholder="{% translate 'Search...' %}"
134+
value="{{ default_query }}"
129135
autocomplete="off"
130136
hx-get="{% url 'openlibrary_search_htmx' %}{% if media_id %}?media_id={{ media_id }}{% endif %}"
131-
hx-trigger="keyup changed delay:400ms, search"
137+
hx-trigger="keyup changed delay:400ms, search{% if default_query and default_source == 'openlibrary' %}, load{% endif %}"
132138
hx-target="#import-results"
133139
hx-indicator="#search-spinner" />
134140
<button type="button"
@@ -142,7 +148,7 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
142148
</div>
143149
{# MusicBrainz Search (Music) #}
144150
<div id="source-musicbrainz"
145-
class="card bg-base-200 shadow-md mb-6 hidden">
151+
class="card bg-base-200 shadow-md mb-6{% if default_source != 'musicbrainz' %} hidden{% endif %}">
146152
<div class="card-body p-4 space-y-3">
147153
<div class="flex items-center gap-2 mb-2">
148154
{% lucide "disc-3" class="w-5 h-5" %}
@@ -155,9 +161,10 @@ <h1 class="text-4xl font-bold">{% translate "Import metadata" %}</h1>
155161
name="q"
156162
class="input join-item w-full"
157163
placeholder="{% translate 'Search...' %}"
164+
value="{{ default_query }}"
158165
autocomplete="off"
159166
hx-get="{% url 'musicbrainz_search_htmx' %}{% if media_id %}?media_id={{ media_id }}{% endif %}"
160-
hx-trigger="keyup changed delay:400ms, search"
167+
hx-trigger="keyup changed delay:400ms, search{% if default_query and default_source == 'musicbrainz' %}, load{% endif %}"
161168
hx-target="#import-results"
162169
hx-indicator="#search-spinner" />
163170
<button type="button"

src/tests/core/test_views.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,3 +1169,108 @@ def test_saved_view_accepts_descending_sort(self, logged_in_client, user, db):
11691169
assert SavedView.objects.filter(user=user, name="Descending Sort View").exists()
11701170
saved_view = SavedView.objects.get(user=user, name="Descending Sort View")
11711171
assert saved_view.sort == "-created_at"
1172+
1173+
1174+
class TestMediaImportView:
1175+
"""Tests for the media_import view."""
1176+
1177+
def test_media_import_accessible_when_logged_in(self, logged_in_client):
1178+
"""The import view is accessible when logged in."""
1179+
response = logged_in_client.get(reverse("media_import"))
1180+
1181+
assert response.status_code == 200
1182+
assert "base/media_import.html" in [t.name for t in response.templates]
1183+
1184+
def test_media_import_default_source_is_tmdb(self, logged_in_client):
1185+
"""Without parameters, default source is TMDB."""
1186+
response = logged_in_client.get(reverse("media_import"))
1187+
1188+
assert response.context["default_source"] == "tmdb"
1189+
assert response.context["default_query"] == ""
1190+
1191+
def test_media_import_film_maps_to_tmdb(self, logged_in_client):
1192+
"""FILM media type maps to TMDB source."""
1193+
response = logged_in_client.get(
1194+
reverse("media_import"),
1195+
{"media_type": "FILM", "title": "The Matrix"},
1196+
)
1197+
1198+
assert response.context["default_source"] == "tmdb"
1199+
assert response.context["default_query"] == "The Matrix"
1200+
1201+
def test_media_import_tv_maps_to_tmdb(self, logged_in_client):
1202+
"""TV media type maps to TMDB source."""
1203+
response = logged_in_client.get(
1204+
reverse("media_import"),
1205+
{"media_type": "TV", "title": "Breaking Bad"},
1206+
)
1207+
1208+
assert response.context["default_source"] == "tmdb"
1209+
assert response.context["default_query"] == "Breaking Bad"
1210+
1211+
def test_media_import_game_maps_to_igdb(self, logged_in_client):
1212+
"""GAME media type maps to IGDB source."""
1213+
response = logged_in_client.get(
1214+
reverse("media_import"),
1215+
{"media_type": "GAME", "title": "The Witcher 3"},
1216+
)
1217+
1218+
assert response.context["default_source"] == "igdb"
1219+
assert response.context["default_query"] == "The Witcher 3"
1220+
1221+
def test_media_import_book_maps_to_openlibrary(self, logged_in_client):
1222+
"""BOOK media type maps to OpenLibrary source."""
1223+
response = logged_in_client.get(
1224+
reverse("media_import"),
1225+
{"media_type": "BOOK", "title": "1984"},
1226+
)
1227+
1228+
assert response.context["default_source"] == "openlibrary"
1229+
assert response.context["default_query"] == "1984"
1230+
1231+
def test_media_import_comic_maps_to_openlibrary(self, logged_in_client):
1232+
"""COMIC media type maps to OpenLibrary source."""
1233+
response = logged_in_client.get(
1234+
reverse("media_import"),
1235+
{"media_type": "COMIC", "title": "Watchmen"},
1236+
)
1237+
1238+
assert response.context["default_source"] == "openlibrary"
1239+
assert response.context["default_query"] == "Watchmen"
1240+
1241+
def test_media_import_music_maps_to_musicbrainz(self, logged_in_client):
1242+
"""MUSIC media type maps to MusicBrainz source."""
1243+
response = logged_in_client.get(
1244+
reverse("media_import"),
1245+
{"media_type": "MUSIC", "title": "Abbey Road"},
1246+
)
1247+
1248+
assert response.context["default_source"] == "musicbrainz"
1249+
assert response.context["default_query"] == "Abbey Road"
1250+
1251+
def test_media_import_unknown_type_defaults_to_tmdb(self, logged_in_client):
1252+
"""Unknown media type defaults to TMDB source."""
1253+
response = logged_in_client.get(
1254+
reverse("media_import"),
1255+
{"media_type": "UNKNOWN", "title": "Something"},
1256+
)
1257+
1258+
assert response.context["default_source"] == "tmdb"
1259+
1260+
def test_media_import_passes_media_id(self, logged_in_client, media):
1261+
"""Media ID is passed to the template context."""
1262+
response = logged_in_client.get(
1263+
reverse("media_import"),
1264+
{"media_id": media.pk},
1265+
)
1266+
1267+
assert response.context["media_id"] == str(media.pk)
1268+
1269+
def test_media_import_handles_special_characters_in_title(self, logged_in_client):
1270+
"""Title with special characters is handled correctly."""
1271+
response = logged_in_client.get(
1272+
reverse("media_import"),
1273+
{"media_type": "FILM", "title": "Amélie & Co: L'histoire"},
1274+
)
1275+
1276+
assert response.context["default_query"] == "Amélie & Co: L'histoire"

0 commit comments

Comments
 (0)