diff --git a/plexapi/collection.py b/plexapi/collection.py index 0eb20924d..e438a4086 100644 --- a/plexapi/collection.py +++ b/plexapi/collection.py @@ -277,6 +277,28 @@ def removeItems(self, items): key = '%s/items/%s' % (self.key, item.ratingKey) self._server.query(key, method=self._server._session.delete) + def moveItem(self, item, after=None): + """ Move an item to a new position in the collection. + + Parameters: + items (obj): :class:`~plexapi.audio.Audio`, :class:`~plexapi.video.Video`, + or :class:`~plexapi.photo.Photo` objects to be moved in the collection. + after (obj): :class:`~plexapi.audio.Audio`, :class:`~plexapi.video.Video`, + or :class:`~plexapi.photo.Photo` objects to move the item after in the collection. + + Raises: + :class:`plexapi.exceptions.BadRequest`: When trying to move items in a smart collection. + """ + if self.smart: + raise BadRequest('Cannot move items in a smart collection.') + + key = '%s/items/%s/move' % (self.key, item.ratingKey) + + if after: + key += '?after=%s' % after.ratingKey + + self._server.query(key, method=self._server._session.put) + def updateFilters(self, libtype=None, limit=None, sort=None, filters=None, **kwargs): """ Update the filters for a smart collection. diff --git a/plexapi/playlist.py b/plexapi/playlist.py index c99626d07..280a57a24 100644 --- a/plexapi/playlist.py +++ b/plexapi/playlist.py @@ -225,7 +225,7 @@ def removeItems(self, items): self._server.query(key, method=self._server._session.delete) def moveItem(self, item, after=None): - """ Move an item to a new position in playlist. + """ Move an item to a new position in the playlist. Parameters: items (obj): :class:`~plexapi.audio.Audio`, :class:`~plexapi.video.Video`, diff --git a/tests/test_collection.py b/tests/test_collection.py index 67aeff0e9..2f3ee6ff0 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -85,17 +85,36 @@ def test_Collection_sortUpdate(collection): collection.sortUpdate("release") -def test_Collection_add_remove(collection, movies, show): +@pytest.mark.authenticated +def test_Collection_sortUpdate_custom(collection): + collection.sortUpdate(sort="custom") + collection.reload() + assert collection.collectionSort == 2 + collection.sortUpdate("release") + + +def test_Collection_add_move_remove(collection, movies): movie = movies.get("Big Buck Bunny") assert movie not in collection collection.addItems(movie) collection.reload() assert movie in collection + items = collection.items() + collection.moveItem(items[1]) + items_moved = collection.reload().items() + assert items_moved[0] == items[1] + assert items_moved[1] == items[0] + collection.moveItem(items[1], after=items[0]) + items_moved = collection.reload().items() + assert items_moved[0] == items[0] + assert items_moved[1] == items[1] collection.removeItems(movie) collection.reload() assert movie not in collection - with pytest.raises(BadRequest): - collection.addItems(show) + # Reset collection sort due to bug with corrupted XML response + # for movies that have been moved in a collection and have + # progress (updateProgress) or marked as played (markWatched) + collection.sortUpdate("release") def test_Collection_edit(collection, movies): @@ -206,6 +225,8 @@ def test_Collection_exceptions(plex, movies, movie, artist): collection.addItems(movie) with pytest.raises(BadRequest): collection.removeItems(movie) + with pytest.raises(BadRequest): + collection.moveItem(movie) finally: collection.delete() diff --git a/tests/test_library.py b/tests/test_library.py index dde3d78ed..65ae54669 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -82,8 +82,8 @@ def test_library_fetchItem(plex, movie): def test_library_onDeck(plex, movie): - movie.updateProgress(movie.duration * 1000 / 10) # set progress to 10% - assert len(list(plex.library.onDeck())) + movie.updateProgress(movie.duration // 4) # set progress to 25% + assert movie in plex.library.onDeck() movie.markUnwatched() @@ -174,11 +174,11 @@ def test_librarty_deleteMediaPreviews(movies): def test_library_MovieSection_onDeck(movie, movies, tvshows, episode): - movie.updateProgress(movie.duration * 1000 / 10) # set progress to 10% - assert movies.onDeck() + movie.updateProgress(movie.duration // 4) # set progress to 25% + assert movie in movies.onDeck() movie.markUnwatched() - episode.updateProgress(episode.duration * 1000 / 10) - assert tvshows.onDeck() + episode.updateProgress(episode.duration // 4) + assert episode in tvshows.onDeck() episode.markUnwatched() diff --git a/tests/test_video.py b/tests/test_video.py index 692c47779..3d7d925e0 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -913,13 +913,13 @@ def test_video_Season_mixins_tags(show): def test_video_Episode_updateProgress(episode, patched_http_call): - episode.updateProgress(10 * 60 * 1000) # 10 minutes. + episode.updateProgress(2 * 60 * 1000) # 2 minutes. def test_video_Episode_updateTimeline(episode, patched_http_call): episode.updateTimeline( - 10 * 60 * 1000, state="playing", duration=episode.duration - ) # 10 minutes. + 2 * 60 * 1000, state="playing", duration=episode.duration + ) # 2 minutes. def test_video_Episode_stop(episode, mocker, patched_http_call):