From 643504edc974a5f39cc0b39b7ee6a4bd39431b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 19 Sep 2025 12:38:04 +0200 Subject: [PATCH] fix(api): file upload with library (#3213) ### Description Uploads with bulk_import defining --library fail with error 400 in the REST API The primary key of the track_type was changed from chars to a numerical ID, and the data model expects this now in the REST endpoint. However the bulk import still populates this field with the Char Tag. ### Testing Notes **What I did:** `libretime-api bulk_import --path /home/libretime/upload/ --library POD --allowed-extensions mp3` **How you can replicate my testing:** see above --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../management/commands/bulk_import.py | 28 +++++++++++++------ api/libretime_api/storage/models/library.py | 2 ++ .../management/commands/test_bulk_import.py | 17 +++++------ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/api/libretime_api/storage/management/commands/bulk_import.py b/api/libretime_api/storage/management/commands/bulk_import.py index 52887f756..855b5a54d 100644 --- a/api/libretime_api/storage/management/commands/bulk_import.py +++ b/api/libretime_api/storage/management/commands/bulk_import.py @@ -85,7 +85,7 @@ class Importer: return File.objects.filter(md5=file_md5).exists() - def _upload_file(self, filepath: Path, library: Optional[str]) -> None: + def _upload_file(self, filepath: Path, library_id: Optional[int]) -> None: try: resp = requests.post( f"{self.url}/rest/media", @@ -94,7 +94,11 @@ class Importer: ("file", (filepath.name, filepath.open("rb"))), ], timeout=30, - cookies={"tt_upload": library} if library is not None else {}, + cookies=( + {"tt_upload": str(library_id)} + if library_id not in (None, 0) + else {} + ), ) resp.raise_for_status() @@ -105,7 +109,7 @@ class Importer: logger.info("deleting %s", filepath) filepath.unlink() - def _handle_file(self, filepath: Path, library: Optional[str]) -> None: + def _handle_file(self, filepath: Path, library_id: Optional[int]) -> None: logger.debug("handling file %s", filepath) if not filepath.is_file(): @@ -117,7 +121,7 @@ class Importer: self._delete_file(filepath) return - self._upload_file(filepath, library) + self._upload_file(filepath, library_id) if self.delete_after_upload: self._delete_file(filepath) @@ -125,7 +129,7 @@ class Importer: def _walk_dir( self, path: Path, - library: Optional[str], + library_id: Optional[int], allowed_extensions: List[str], ) -> None: if not path.is_dir(): @@ -133,13 +137,13 @@ class Importer: for sub_path in path.iterdir(): if sub_path.is_dir(): - self._walk_dir(sub_path, library, allowed_extensions) + self._walk_dir(sub_path, library_id, allowed_extensions) continue if sub_path.suffix.lower() not in allowed_extensions: continue - self._handle_file(sub_path.resolve(), library) + self._handle_file(sub_path.resolve(), library_id) def _check_library(self, library: str) -> bool: return Library.objects.filter(code=library).exists() @@ -153,8 +157,16 @@ class Importer: if library is not None and not self._check_library(library): raise ValueError(f"provided library {library} does not exist") + if library: + try: + library_id = Library.objects.get(code=library).id + except Library.DoesNotExist as exc: + raise ValueError(f"provided library {library} does not exist") from exc + else: + library_id = 0 + allowed_extensions = [ (x if x.startswith(".") else "." + x) for x in allowed_extensions ] - self._walk_dir(path, library, allowed_extensions) + self._walk_dir(path, library_id, allowed_extensions) diff --git a/api/libretime_api/storage/models/library.py b/api/libretime_api/storage/models/library.py index 36a044da3..8a5d69ce3 100644 --- a/api/libretime_api/storage/models/library.py +++ b/api/libretime_api/storage/models/library.py @@ -22,6 +22,8 @@ class Library(models.Model): db_column="analyze_cue_points", ) + id = models.AutoField(primary_key=True) + class Meta: managed = False db_table = "cc_track_types" diff --git a/api/libretime_api/storage/tests/management/commands/test_bulk_import.py b/api/libretime_api/storage/tests/management/commands/test_bulk_import.py index 5bd54a1ec..cbf38dbe0 100644 --- a/api/libretime_api/storage/tests/management/commands/test_bulk_import.py +++ b/api/libretime_api/storage/tests/management/commands/test_bulk_import.py @@ -30,6 +30,7 @@ def _import_paths(tmp_path: Path): def _library(): return baker.make( "storage.Library", + id=1, code="MUS", name="Music", description="Some music", @@ -62,8 +63,8 @@ def test_importer( ): importer.import_dir(import_paths[0], library.code, [".mp3"]) - importer._handle_file.assert_called_with(import_paths[1], library.code) - importer._upload_file.assert_called_with(import_paths[1], library.code) + importer._handle_file.assert_called_with(import_paths[1], library.id) + importer._upload_file.assert_called_with(import_paths[1], library.id) importer._delete_file.assert_not_called() @@ -76,8 +77,8 @@ def test_importer_and_delete( importer.delete_after_upload = True importer.import_dir(import_paths[0], library.code, [".mp3"]) - importer._handle_file.assert_called_with(import_paths[1], library.code) - importer._upload_file.assert_called_with(import_paths[1], library.code) + importer._handle_file.assert_called_with(import_paths[1], library.id) + importer._upload_file.assert_called_with(import_paths[1], library.id) importer._delete_file.assert_called_with(import_paths[1]) @@ -87,11 +88,11 @@ def test_importer_existing_file( importer: MockImporter, library, ): - baker.make("storage.File", md5="46305a7cf42ee53976c88d337e47e940") + baker.make("storage.File", id=1, md5="46305a7cf42ee53976c88d337e47e940") importer.import_dir(import_paths[0], library.code, [".mp3"]) - importer._handle_file.assert_called_with(import_paths[1], library.code) + importer._handle_file.assert_called_with(import_paths[1], library.id) importer._upload_file.assert_not_called() importer._delete_file.assert_not_called() @@ -102,12 +103,12 @@ def test_importer_existing_file_and_delete( importer: MockImporter, library, ): - baker.make("storage.File", md5="46305a7cf42ee53976c88d337e47e940") + baker.make("storage.File", id=1, md5="46305a7cf42ee53976c88d337e47e940") importer.delete_if_exists = True importer.import_dir(import_paths[0], library.code, [".mp3"]) - importer._handle_file.assert_called_with(import_paths[1], library.code) + importer._handle_file.assert_called_with(import_paths[1], library.id) importer._upload_file.assert_not_called() importer._delete_file.assert_called_with(import_paths[1])