diff --git a/plexapi/audio.py b/plexapi/audio.py index 25ac659b1..8f81d9066 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -5,7 +5,7 @@ from plexapi.base import Playable, PlexPartialObject from plexapi.exceptions import BadRequest from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin -from plexapi.mixins import SplitMergeMixin, UnmatchMatchMixin +from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin @@ -21,6 +21,7 @@ class Audio(PlexPartialObject): guid (str): Plex GUID for the artist, album, or track (plex://artist/5d07bcb0403c64029053ac4c). index (int): Plex index number (often the track number). key (str): API URL (/library/metadata/). + lastRatedAt (datetime): Datetime the item was last rated. lastViewedAt (datetime): Datetime the item was last played. librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID. librarySectionKey (str): :class:`~plexapi.library.LibrarySection` key. @@ -35,7 +36,7 @@ class Audio(PlexPartialObject): titleSort (str): Title to use when sorting (defaults to title). type (str): 'artist', 'album', or 'track'. updatedAt (datatime): Datetime the item was updated. - userRating (float): Rating of the track (0.0 - 10.0) equaling (0 stars - 5 stars). + userRating (float): Rating of the item (0.0 - 10.0) equaling (0 stars - 5 stars). viewCount (int): Count of times the item was played. """ @@ -66,7 +67,7 @@ def _loadData(self, data): self.titleSort = data.attrib.get('titleSort', self.title) self.type = data.attrib.get('type') self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt')) - self.userRating = utils.cast(float, data.attrib.get('userRating', 0)) + self.userRating = utils.cast(float, data.attrib.get('userRating')) self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0)) def url(self, part): @@ -115,7 +116,7 @@ def sync(self, bitrate, client=None, clientId=None, limit=None, title=None): @utils.registerPlexObject -class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin, +class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, SplitMergeMixin, UnmatchMatchMixin, CollectionMixin, CountryMixin, GenreMixin, MoodMixin, SimilarArtistMixin, StyleMixin): """ Represents a single Artist. @@ -222,7 +223,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): @utils.registerPlexObject -class Album(Audio, ArtMixin, PosterMixin, UnmatchMatchMixin, +class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin, CollectionMixin, GenreMixin, LabelMixin, MoodMixin, StyleMixin): """ Represents a single Album. @@ -329,7 +330,8 @@ def _defaultSyncTitle(self): @utils.registerPlexObject -class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, CollectionMixin, MoodMixin): +class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, + CollectionMixin, MoodMixin): """ Represents a single Track. Attributes: diff --git a/plexapi/base.py b/plexapi/base.py index 060c7d906..fc445e32f 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -48,7 +48,7 @@ def __init__(self, server, data, initpath=None, parent=None): self._server = server self._data = data self._initpath = initpath or self.key - self._parent = weakref.ref(parent) if parent else None + self._parent = weakref.ref(parent) if parent is not None else None self._details_key = None if data is not None: self._loadData(data) diff --git a/plexapi/collection.py b/plexapi/collection.py index b157e2e3a..97e1d3daa 100644 --- a/plexapi/collection.py +++ b/plexapi/collection.py @@ -5,14 +5,14 @@ from plexapi.base import PlexPartialObject from plexapi.exceptions import BadRequest, NotFound, Unsupported from plexapi.library import LibrarySection -from plexapi.mixins import AdvancedSettingsMixin, ArtMixin, PosterMixin +from plexapi.mixins import AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin from plexapi.mixins import LabelMixin from plexapi.playqueue import PlayQueue from plexapi.utils import deprecated @utils.registerPlexObject -class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin, LabelMixin): +class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, LabelMixin): """ Represents a single Collection. Attributes: @@ -32,6 +32,7 @@ class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin index (int): Plex index number for the collection. key (str): API URL (/library/metadata/). labels (List<:class:`~plexapi.media.Label`>): List of label objects. + lastRatedAt (datetime): Datetime the collection was last rated. librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID. librarySectionKey (str): :class:`~plexapi.library.LibrarySection` key. librarySectionTitle (str): :class:`~plexapi.library.LibrarySection` title. @@ -48,6 +49,7 @@ class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin titleSort (str): Title to use when sorting (defaults to title). type (str): 'collection' updatedAt (datatime): Datetime the collection was updated. + userRating (float): Rating of the collection (0.0 - 10.0) equaling (0 stars - 5 stars). """ TAG = 'Directory' @@ -69,6 +71,7 @@ def _loadData(self, data): self.index = utils.cast(int, data.attrib.get('index')) self.key = data.attrib.get('key', '').replace('/children', '') # FIX_BUG_50 self.labels = self.findItems(data, media.Label) + self.lastRatedAt = utils.toDatetime(data.attrib.get('lastRatedAt')) self.librarySectionID = utils.cast(int, data.attrib.get('librarySectionID')) self.librarySectionKey = data.attrib.get('librarySectionKey') self.librarySectionTitle = data.attrib.get('librarySectionTitle') @@ -85,6 +88,7 @@ def _loadData(self, data): self.titleSort = data.attrib.get('titleSort', self.title) self.type = data.attrib.get('type') self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt')) + self.userRating = utils.cast(float, data.attrib.get('userRating')) self._items = None # cache for self.items self._section = None # cache for self.section diff --git a/plexapi/mixins.py b/plexapi/mixins.py index 49d8332a1..4e982dfe4 100644 --- a/plexapi/mixins.py +++ b/plexapi/mixins.py @@ -2,7 +2,7 @@ from urllib.parse import quote_plus, urlencode from plexapi import media, settings, utils -from plexapi.exceptions import NotFound +from plexapi.exceptions import BadRequest, NotFound class AdvancedSettingsMixin(object): @@ -192,6 +192,26 @@ def setPoster(self, poster): poster.select() +class RatingMixin(object): + """ Mixin for Plex objects that can have user star ratings. """ + + def rate(self, rating=None): + """ Rate the Plex object. Note: Plex ratings are displayed out of 5 stars (e.g. rating 7.0 = 3.5 stars). + + Parameters: + rating (float, optional): Rating from 0 to 10. Exclude to reset the rating. + + Raises: + :exc:`~plexapi.exceptions.BadRequest`: If the rating is invalid. + """ + if rating is None: + rating = -1 + elif not isinstance(rating, (int, float)) or rating < 0 or rating > 10: + raise BadRequest('Rating must be between 0 to 10.') + key = '/:/rate?key=%s&identifier=com.plexapp.plugins.library&rating=%s' % (self.ratingKey, rating) + self._server.query(key, method=self._server._session.put) + + class SplitMergeMixin(object): """ Mixin for Plex objects that can be split and merged. """ diff --git a/plexapi/photo.py b/plexapi/photo.py index a83073339..ad8e0706b 100644 --- a/plexapi/photo.py +++ b/plexapi/photo.py @@ -4,11 +4,11 @@ from plexapi import media, utils, video from plexapi.base import Playable, PlexPartialObject from plexapi.exceptions import BadRequest -from plexapi.mixins import ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, TagMixin +from plexapi.mixins import ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, RatingMixin, TagMixin @utils.registerPlexObject -class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin): +class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin, RatingMixin): """ Represents a single Photoalbum (collection of photos). Attributes: @@ -21,6 +21,7 @@ class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin): guid (str): Plex GUID for the photo album (local://229674). index (sting): Plex index number for the photo album. key (str): API URL (/library/metadata/). + lastRatedAt (datetime): Datetime the photo album was last rated. librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID. librarySectionKey (str): :class:`~plexapi.library.LibrarySection` key. librarySectionTitle (str): :class:`~plexapi.library.LibrarySection` title. @@ -32,7 +33,7 @@ class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin): titleSort (str): Title to use when sorting (defaults to title). type (str): 'photo' updatedAt (datatime): Datetime the photo album was updated. - userRating (float): Rating of the photoalbum (0.0 - 10.0) equaling (0 stars - 5 stars). + userRating (float): Rating of the photo album (0.0 - 10.0) equaling (0 stars - 5 stars). """ TAG = 'Directory' TYPE = 'photo' @@ -46,6 +47,7 @@ def _loadData(self, data): self.guid = data.attrib.get('guid') self.index = utils.cast(int, data.attrib.get('index')) self.key = data.attrib.get('key', '').replace('/children', '') # FIX_BUG_50 + self.lastRatedAt = utils.toDatetime(data.attrib.get('lastRatedAt')) self.librarySectionID = utils.cast(int, data.attrib.get('librarySectionID')) self.librarySectionKey = data.attrib.get('librarySectionKey') self.librarySectionTitle = data.attrib.get('librarySectionTitle') @@ -57,7 +59,7 @@ def _loadData(self, data): self.titleSort = data.attrib.get('titleSort', self.title) self.type = data.attrib.get('type') self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt')) - self.userRating = utils.cast(float, data.attrib.get('userRating', 0)) + self.userRating = utils.cast(float, data.attrib.get('userRating')) def album(self, title): """ Returns the :class:`~plexapi.photo.Photoalbum` that matches the specified title. @@ -137,7 +139,7 @@ def download(self, savepath=None, keep_original_name=False, showstatus=False): @utils.registerPlexObject -class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, TagMixin): +class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, TagMixin): """ Represents a single Photo. Attributes: @@ -150,6 +152,7 @@ class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, TagMixin): guid (str): Plex GUID for the photo (com.plexapp.agents.none://231714?lang=xn). index (sting): Plex index number for the photo. key (str): API URL (/library/metadata/). + lastRatedAt (datetime): Datetime the photo was last rated. librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID. librarySectionKey (str): :class:`~plexapi.library.LibrarySection` key. librarySectionTitle (str): :class:`~plexapi.library.LibrarySection` title. @@ -170,6 +173,7 @@ class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, TagMixin): titleSort (str): Title to use when sorting (defaults to title). type (str): 'photo' updatedAt (datatime): Datetime the photo was updated. + userRating (float): Rating of the photo (0.0 - 10.0) equaling (0 stars - 5 stars). year (int): Year the photo was taken. """ TAG = 'Photo' @@ -186,6 +190,7 @@ def _loadData(self, data): self.guid = data.attrib.get('guid') self.index = utils.cast(int, data.attrib.get('index')) self.key = data.attrib.get('key', '') + self.lastRatedAt = utils.toDatetime(data.attrib.get('lastRatedAt')) self.librarySectionID = utils.cast(int, data.attrib.get('librarySectionID')) self.librarySectionKey = data.attrib.get('librarySectionKey') self.librarySectionTitle = data.attrib.get('librarySectionTitle') @@ -206,6 +211,7 @@ def _loadData(self, data): self.titleSort = data.attrib.get('titleSort', self.title) self.type = data.attrib.get('type') self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt')) + self.userRating = utils.cast(float, data.attrib.get('userRating')) self.year = utils.cast(int, data.attrib.get('year')) def photoalbum(self): diff --git a/plexapi/video.py b/plexapi/video.py index 141e29150..999e5adfe 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -6,7 +6,7 @@ from plexapi.base import Playable, PlexPartialObject from plexapi.exceptions import BadRequest from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, BannerMixin, PosterUrlMixin, PosterMixin -from plexapi.mixins import SplitMergeMixin, UnmatchMatchMixin +from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin from plexapi.mixins import CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, WriterMixin @@ -22,6 +22,7 @@ class Video(PlexPartialObject): fields (List<:class:`~plexapi.media.Field`>): List of field objects. guid (str): Plex GUID for the movie, show, season, episode, or clip (plex://movie/5d776b59ad5437001f79c6f8). key (str): API URL (/library/metadata/). + lastRatedAt (datetime): Datetime the item was last rated. lastViewedAt (datetime): Datetime the item was last played. librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID. librarySectionKey (str): :class:`~plexapi.library.LibrarySection` key. @@ -35,6 +36,7 @@ class Video(PlexPartialObject): titleSort (str): Title to use when sorting (defaults to title). type (str): 'movie', 'show', 'season', 'episode', or 'clip'. updatedAt (datatime): Datetime the item was updated. + userRating (float): Rating of the item (0.0 - 10.0) equaling (0 stars - 5 stars). viewCount (int): Count of times the item was played. """ @@ -61,6 +63,7 @@ def _loadData(self, data): self.titleSort = data.attrib.get('titleSort', self.title) self.type = data.attrib.get('type') self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt')) + self.userRating = utils.cast(float, data.attrib.get('userRating')) self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0)) @property @@ -73,23 +76,14 @@ def url(self, part): return self._server.url(part, includeToken=True) if part else None def markWatched(self): - """ Mark video as watched. """ + """ Mark the video as palyed. """ key = '/:/scrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey self._server.query(key) - self.reload() def markUnwatched(self): - """ Mark video unwatched. """ + """ Mark the video as unplayed. """ key = '/:/unscrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey self._server.query(key) - self.reload() - - def rate(self, rate): - """ Rate video. """ - key = '/:/rate?key=%s&identifier=com.plexapp.plugins.library&rating=%s' % (self.ratingKey, rate) - - self._server.query(key) - self.reload() def _defaultSyncTitle(self): """ Returns str, default title for a new syncItem. """ @@ -249,7 +243,7 @@ def sync(self, videoQuality, client=None, clientId=None, limit=None, unwatched=F @utils.registerPlexObject -class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin, +class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, SplitMergeMixin, UnmatchMatchMixin, CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, WriterMixin): """ Represents a single Movie. @@ -283,7 +277,6 @@ class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, Split tagline (str): Movie tag line (Back 2 Work; Who says men can't change?). useOriginalTitle (int): Setting that indicates if the original title is used for the movie (-1 = Library default, 0 = No, 1 = Yes). - userRating (float): User rating (2.0; 8.0). viewOffset (int): View offset in milliseconds. writers (List<:class:`~plexapi.media.Writer`>): List of writers objects. year (int): Year movie was released. @@ -321,7 +314,6 @@ def _loadData(self, data): self.studio = data.attrib.get('studio') self.tagline = data.attrib.get('tagline') self.useOriginalTitle = utils.cast(int, data.attrib.get('useOriginalTitle', '-1')) - self.userRating = utils.cast(float, data.attrib.get('userRating')) self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) self.writers = self.findItems(data, media.Writer) self.year = utils.cast(int, data.attrib.get('year')) @@ -387,7 +379,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): @utils.registerPlexObject -class Show(Video, AdvancedSettingsMixin, ArtMixin, BannerMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin, +class Show(Video, AdvancedSettingsMixin, ArtMixin, BannerMixin, PosterMixin, RatingMixin, SplitMergeMixin, UnmatchMatchMixin, CollectionMixin, GenreMixin, LabelMixin): """ Represents a single Show (including all seasons and episodes). @@ -434,7 +426,6 @@ class Show(Video, AdvancedSettingsMixin, ArtMixin, BannerMixin, PosterMixin, Spl theme (str): URL to theme resource (/library/metadata//theme/). useOriginalTitle (int): Setting that indicates if the original title is used for the show (-1 = Library default, 0 = No, 1 = Yes). - userRating (float): User rating (2.0; 8.0). viewedLeafCount (int): Number of items marked as played in the show view. year (int): Year the show was released. """ @@ -477,7 +468,6 @@ def _loadData(self, data): self.tagline = data.attrib.get('tagline') self.theme = data.attrib.get('theme') self.useOriginalTitle = utils.cast(int, data.attrib.get('useOriginalTitle', '-1')) - self.userRating = utils.cast(float, data.attrib.get('userRating')) self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount')) self.year = utils.cast(int, data.attrib.get('year')) @@ -591,7 +581,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): @utils.registerPlexObject -class Season(Video, ArtMixin, PosterMixin, CollectionMixin): +class Season(Video, ArtMixin, PosterMixin, RatingMixin, CollectionMixin): """ Represents a single Show Season (including all episodes). Attributes: @@ -729,7 +719,8 @@ def _defaultSyncTitle(self): @utils.registerPlexObject -class Episode(Video, Playable, ArtMixin, PosterMixin, CollectionMixin, DirectorMixin, WriterMixin): +class Episode(Video, Playable, ArtMixin, PosterMixin, RatingMixin, + CollectionMixin, DirectorMixin, WriterMixin): """ Represents a single Shows Episode. Attributes: @@ -764,7 +755,6 @@ class Episode(Video, Playable, ArtMixin, PosterMixin, CollectionMixin, DirectorM parentYear (int): Year the season was released. rating (float): Episode rating (7.9; 9.8; 8.1). skipParent (bool): True if the show's seasons are set to hidden. - userRating (float): User rating (2.0; 8.0). viewOffset (int): View offset in milliseconds. writers (List<:class:`~plexapi.media.Writer`>): List of writers objects. year (int): Year the episode was released. @@ -807,7 +797,6 @@ def _loadData(self, data): self.parentYear = utils.cast(int, data.attrib.get('parentYear')) self.rating = utils.cast(float, data.attrib.get('rating')) self.skipParent = utils.cast(bool, data.attrib.get('skipParent', '0')) - self.userRating = utils.cast(float, data.attrib.get('userRating')) self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) self.writers = self.findItems(data, media.Writer) self.year = utils.cast(int, data.attrib.get('year')) @@ -865,8 +854,6 @@ def seasonEpisode(self): @property def hasIntroMarker(self): """ Returns True if the episode has an intro marker in the xml. """ - if not self.isFullObject(): - self.reload() return any(marker.type == 'intro' for marker in self.markers) @property diff --git a/tests/test_actions.py b/tests/test_actions.py index 994068d8d..39afbcde1 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -6,9 +6,11 @@ def test_mark_movie_watched(movie): print('Marking movie watched: %s' % movie) print('View count: %s' % movie.viewCount) movie.markWatched() + movie.reload() print('View count: %s' % movie.viewCount) assert movie.viewCount == 1, 'View count 0 after watched.' movie.markUnwatched() + movie.reload() print('View count: %s' % movie.viewCount) assert movie.viewCount == 0, 'View count 1 after unwatched.' @@ -19,12 +21,3 @@ def test_refresh_section(tvshows): def test_refresh_video(movie): movie.refresh() - - -def test_rate_movie(movie): - oldrate = movie.userRating - if oldrate is None: - oldrate = 1 - movie.rate(10.0) - assert movie.userRating == 10.0, 'User rating 10.0 after rating five stars.' - movie.rate(oldrate) diff --git a/tests/test_audio.py b/tests/test_audio.py index 288c3162b..33325a4c5 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -80,6 +80,10 @@ def test_audio_Artist_mixins_images(artist): test_mixins.attr_posterUrl(artist) +def test_audio_Artist_mixins_rating(artist): + test_mixins.edit_rating(artist) + + def test_audio_Artist_mixins_tags(artist): test_mixins.edit_collection(artist) test_mixins.edit_country(artist) @@ -171,6 +175,10 @@ def test_audio_Album_mixins_images(album): test_mixins.attr_posterUrl(album) +def test_audio_Album_mixins_rating(album): + test_mixins.edit_rating(album) + + def test_audio_Album_mixins_tags(album): test_mixins.edit_collection(album) test_mixins.edit_genre(album) @@ -314,6 +322,10 @@ def test_audio_Track_mixins_images(track): test_mixins.attr_posterUrl(track) +def test_audio_Track_mixins_rating(track): + test_mixins.edit_rating(track) + + def test_audio_Track_mixins_tags(track): test_mixins.edit_collection(track) test_mixins.edit_mood(track) diff --git a/tests/test_collection.py b/tests/test_collection.py index 202060a09..48128c64d 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -229,5 +229,9 @@ def test_Collection_mixins_images(collection): test_mixins.attr_posterUrl(collection) +def test_Collection_mixins_rating(collection): + test_mixins.edit_rating(collection) + + def test_Collection_mixins_tags(collection): test_mixins.edit_label(collection) diff --git a/tests/test_mixins.py b/tests/test_mixins.py index e5c86c080..94d494a22 100644 --- a/tests/test_mixins.py +++ b/tests/test_mixins.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from plexapi.exceptions import NotFound +from plexapi.exceptions import BadRequest, NotFound from plexapi.utils import tag_singular import pytest @@ -177,3 +177,21 @@ def edit_advanced_settings(obj): _test_mixins_editAdvanced(obj) _test_mixins_editAdvanced_bad_pref(obj) _test_mixins_defaultAdvanced(obj) + + +def edit_rating(obj): + obj.rate(10.0) + obj.reload() + assert utils.is_datetime(obj.lastRatedAt) + assert obj.userRating == 10.0 + obj.rate() + # Cannot use obj.reload() since PlexObject.__setattr__() + # will not overwrite userRating with None + obj = obj.fetchItem(obj._details_key) + assert obj.userRating is None + with pytest.raises(BadRequest): + assert obj.rate('bad-rating') + with pytest.raises(BadRequest): + assert obj.rate(-1) + with pytest.raises(BadRequest): + assert obj.rate(100) diff --git a/tests/test_photo.py b/tests/test_photo.py index 85e4c9acc..8d282f81d 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -18,6 +18,14 @@ def test_photo_Photoalbum_mixins_images(photoalbum): test_mixins.attr_posterUrl(photoalbum) +def test_photo_Photoalbum_mixins_rating(photoalbum): + test_mixins.edit_rating(photoalbum) + + +def test_photo_Photo_mixins_rating(photo): + test_mixins.edit_rating(photo) + + def test_photo_Photo_mixins_tags(photo): test_mixins.edit_tag(photo) diff --git a/tests/test_video.py b/tests/test_video.py index 6df422f0e..b9486079c 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -49,6 +49,10 @@ def test_video_Movie_mixins_images(movie): test_mixins.edit_poster(movie) +def test_video_Movie_mixins_rating(movie): + test_mixins.edit_rating(movie) + + def test_video_Movie_mixins_tags(movie): test_mixins.edit_collection(movie) test_mixins.edit_country(movie) @@ -693,11 +697,13 @@ def test_video_Show_analyze(show): def test_video_Show_markWatched(show): show.markWatched() + show.reload() assert show.isWatched def test_video_Show_markUnwatched(show): show.markUnwatched() + show.reload() assert not show.isWatched @@ -729,6 +735,10 @@ def test_video_Show_mixins_images(show): test_mixins.attr_posterUrl(show) +def test_video_Show_mixins_rating(show): + test_mixins.edit_rating(show) + + def test_video_Show_mixins_tags(show): test_mixins.edit_collection(show) test_mixins.edit_genre(show) @@ -806,12 +816,14 @@ def test_video_Season_show(show): def test_video_Season_watched(show): season = show.season("Season 1") season.markWatched() + season.reload() assert season.isWatched def test_video_Season_unwatched(show): season = show.season("Season 1") season.markUnwatched() + season.reload() assert not season.isWatched @@ -843,6 +855,11 @@ def test_video_Season_mixins_images(show): test_mixins.attr_posterUrl(season) +def test_video_Season_mixins_rating(show): + season = show.season(season=1) + test_mixins.edit_rating(season) + + def test_video_Season_mixins_tags(show): season = show.season(season=1) test_mixins.edit_collection(season) @@ -1033,6 +1050,10 @@ def test_video_Episode_mixins_images(episode): test_mixins.attr_posterUrl(episode) +def test_video_Episode_mixins_rating(episode): + test_mixins.edit_rating(episode) + + def test_video_Episode_mixins_tags(episode): test_mixins.edit_collection(episode) test_mixins.edit_director(episode)