mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-24 20:53:09 +00:00
2142514955
* Add support for editing audience ratings and critic ratings * Note: Not all types of ratings are supported/shown in the Plex UIs. However they can be edited/updated and stored in the Plex Media Server database. * Add tests for editing audience rating and critic rating
397 lines
10 KiB
Python
397 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
from plexapi.exceptions import BadRequest, NotFound
|
|
|
|
from . import conftest as utils
|
|
|
|
TEST_MIXIN_FIELD = "Test Field"
|
|
TEST_MIXIN_DATE = utils.MIN_DATETIME
|
|
TEST_MIXIN_TAG = "Test Tag !@#$%^&*()-_=+[];:'\"/?,."
|
|
CUTE_CAT_SHA1 = "9f7003fc401761d8e0b0364d428b2dab2f789dbb"
|
|
AUDIO_STUB_SHA1 = "1abc20d5fdc904201bf8988ca6ef30f96bb73617"
|
|
|
|
|
|
def _test_mixins_field(obj, attr, field_method, default=None, value=None):
|
|
edit_field_method = getattr(obj, "edit" + field_method)
|
|
_value = lambda: getattr(obj, attr)
|
|
_fields = lambda: [f for f in obj.fields if f.name == attr]
|
|
|
|
# Check field does not match to begin with
|
|
default_value = default or _value()
|
|
if value:
|
|
test_value = value
|
|
elif isinstance(default_value, datetime):
|
|
test_value = TEST_MIXIN_DATE
|
|
elif isinstance(default_value, int):
|
|
test_value = default_value + 1
|
|
else:
|
|
test_value = TEST_MIXIN_FIELD
|
|
assert default_value != test_value
|
|
|
|
# Edit and lock the field
|
|
edit_field_method(test_value)
|
|
obj.reload()
|
|
value = _value()
|
|
fields = _fields()
|
|
assert value == test_value
|
|
assert fields and fields[0].locked
|
|
|
|
# Reset and unlock the field to restore the clean state
|
|
edit_field_method(default_value, locked=False)
|
|
obj.reload()
|
|
value = _value()
|
|
fields = _fields()
|
|
assert value == default_value
|
|
assert not fields
|
|
|
|
|
|
def edit_added_at(obj):
|
|
_test_mixins_field(obj, "addedAt", "AddedAt")
|
|
|
|
|
|
def edit_audience_rating(obj):
|
|
_test_mixins_field(obj, "audienceRating", "AudienceRating", default=None, value=7.7)
|
|
|
|
|
|
def edit_content_rating(obj):
|
|
_test_mixins_field(obj, "contentRating", "ContentRating")
|
|
|
|
|
|
def edit_critic_rating(obj):
|
|
_test_mixins_field(obj, "rating", "CriticRating", default=None, value=8.8)
|
|
|
|
|
|
def edit_edition_title(obj):
|
|
_test_mixins_field(obj, "editionTitle", "EditionTitle")
|
|
|
|
|
|
def edit_originally_available(obj):
|
|
_test_mixins_field(obj, "originallyAvailableAt", "OriginallyAvailable")
|
|
|
|
|
|
def edit_original_title(obj):
|
|
_test_mixins_field(obj, "originalTitle", "OriginalTitle")
|
|
|
|
|
|
def edit_sort_title(obj):
|
|
_test_mixins_field(obj, "titleSort", "SortTitle")
|
|
|
|
|
|
def edit_studio(obj):
|
|
_test_mixins_field(obj, "studio", "Studio")
|
|
|
|
|
|
def edit_summary(obj):
|
|
_test_mixins_field(obj, "summary", "Summary")
|
|
|
|
|
|
def edit_tagline(obj):
|
|
_test_mixins_field(obj, "tagline", "Tagline")
|
|
|
|
|
|
def edit_title(obj):
|
|
_test_mixins_field(obj, "title", "Title")
|
|
|
|
|
|
def edit_track_artist(obj):
|
|
_test_mixins_field(obj, "originalTitle", "TrackArtist")
|
|
|
|
|
|
def edit_track_number(obj):
|
|
_test_mixins_field(obj, "index", "TrackNumber")
|
|
|
|
|
|
def edit_track_disc_number(obj):
|
|
_test_mixins_field(obj, "parentIndex", "DiscNumber")
|
|
|
|
|
|
def edit_photo_captured_time(obj):
|
|
_test_mixins_field(obj, "originallyAvailableAt", "CapturedTime")
|
|
|
|
|
|
def edit_user_rating(obj):
|
|
_test_mixins_field(obj, "userRating", "UserRating", default=None, value=10.0)
|
|
|
|
|
|
def _test_mixins_tag(obj, attr, tag_method):
|
|
add_tag_method = getattr(obj, "add" + tag_method)
|
|
remove_tag_method = getattr(obj, "remove" + tag_method)
|
|
field_name = obj._tagSingular(attr)
|
|
_tags = lambda: [t.tag for t in getattr(obj, attr)]
|
|
_fields = lambda: [f for f in obj.fields if f.name == field_name]
|
|
|
|
# Check tag is not present to begin with
|
|
tags = _tags()
|
|
assert TEST_MIXIN_TAG not in tags
|
|
|
|
# Add tag string and lock the field
|
|
add_tag_method(TEST_MIXIN_TAG)
|
|
obj.reload()
|
|
tags = _tags()
|
|
fields = _fields()
|
|
assert TEST_MIXIN_TAG in tags
|
|
assert fields and fields[0].locked
|
|
|
|
# Remove MediaTag object
|
|
mediaTag = next(t for t in getattr(obj, attr) if t.tag == TEST_MIXIN_TAG)
|
|
remove_tag_method(mediaTag)
|
|
obj.reload()
|
|
tags = _tags()
|
|
assert TEST_MIXIN_TAG not in tags
|
|
|
|
# Add MediaTag object
|
|
add_tag_method(mediaTag)
|
|
obj.reload()
|
|
tags = _tags()
|
|
assert TEST_MIXIN_TAG in tags
|
|
|
|
# Remove tag string and unlock to field to restore the clean state
|
|
remove_tag_method(TEST_MIXIN_TAG, locked=False)
|
|
obj.reload()
|
|
tags = _tags()
|
|
fields = _fields()
|
|
assert TEST_MIXIN_TAG not in tags
|
|
assert not fields
|
|
|
|
|
|
def edit_collection(obj):
|
|
_test_mixins_tag(obj, "collections", "Collection")
|
|
|
|
|
|
def edit_country(obj):
|
|
_test_mixins_tag(obj, "countries", "Country")
|
|
|
|
|
|
def edit_director(obj):
|
|
_test_mixins_tag(obj, "directors", "Director")
|
|
|
|
|
|
def edit_genre(obj):
|
|
_test_mixins_tag(obj, "genres", "Genre")
|
|
|
|
|
|
def edit_label(obj):
|
|
_test_mixins_tag(obj, "labels", "Label")
|
|
|
|
|
|
def edit_mood(obj):
|
|
_test_mixins_tag(obj, "moods", "Mood")
|
|
|
|
|
|
def edit_producer(obj):
|
|
_test_mixins_tag(obj, "producers", "Producer")
|
|
|
|
|
|
def edit_similar_artist(obj):
|
|
_test_mixins_tag(obj, "similar", "SimilarArtist")
|
|
|
|
|
|
def edit_style(obj):
|
|
_test_mixins_tag(obj, "styles", "Style")
|
|
|
|
|
|
def edit_tag(obj):
|
|
_test_mixins_tag(obj, "tags", "Tag")
|
|
|
|
|
|
def edit_writer(obj):
|
|
_test_mixins_tag(obj, "writers", "Writer")
|
|
|
|
|
|
def _test_mixins_lock_image(obj, attr):
|
|
cap_attr = attr[:-1].capitalize()
|
|
lock_img_method = getattr(obj, "lock" + cap_attr)
|
|
unlock_img_method = getattr(obj, "unlock" + cap_attr)
|
|
field = "thumb" if attr == 'posters' else attr[:-1]
|
|
_fields = lambda: [f.name for f in obj.fields]
|
|
assert field not in _fields()
|
|
lock_img_method()
|
|
obj.reload()
|
|
assert field in _fields()
|
|
unlock_img_method()
|
|
obj.reload()
|
|
assert field not in _fields()
|
|
|
|
|
|
def lock_art(obj):
|
|
_test_mixins_lock_image(obj, "arts")
|
|
|
|
|
|
def lock_poster(obj):
|
|
_test_mixins_lock_image(obj, "posters")
|
|
|
|
|
|
def _test_mixins_edit_image(obj, attr):
|
|
cap_attr = attr[:-1].capitalize()
|
|
get_img_method = getattr(obj, attr)
|
|
set_img_method = getattr(obj, "set" + cap_attr)
|
|
upload_img_method = getattr(obj, "upload" + cap_attr)
|
|
images = get_img_method()
|
|
if images:
|
|
default_image = images[0]
|
|
image = images[0]
|
|
assert len(image.key) >= 10
|
|
if not image.ratingKey.startswith(("default://", "id://", "media://", "upload://")):
|
|
assert image.provider
|
|
assert len(image.ratingKey) >= 10
|
|
assert utils.is_bool(image.selected)
|
|
assert len(image.thumb) >= 10
|
|
if len(images) >= 2:
|
|
# Select a different image
|
|
set_img_method(images[1])
|
|
images = get_img_method()
|
|
assert images[0].selected is False
|
|
assert images[1].selected is True
|
|
else:
|
|
default_image = None
|
|
|
|
# Test upload image from file
|
|
upload_img_method(filepath=utils.STUB_IMAGE_PATH)
|
|
images = get_img_method()
|
|
file_image = [
|
|
i for i in images
|
|
if i.ratingKey.startswith("upload://") and i.ratingKey.endswith(CUTE_CAT_SHA1)
|
|
]
|
|
assert file_image
|
|
|
|
# Reset to default image
|
|
if default_image:
|
|
set_img_method(default_image)
|
|
|
|
# Test upload image from file-like ojbect
|
|
with open(utils.STUB_IMAGE_PATH, "rb") as f:
|
|
upload_img_method(filepath=f)
|
|
images = get_img_method()
|
|
file_image = [
|
|
i for i in images
|
|
if i.ratingKey.startswith("upload://") and i.ratingKey.endswith(CUTE_CAT_SHA1)
|
|
]
|
|
assert file_image
|
|
|
|
# Reset to default image
|
|
if default_image:
|
|
set_img_method(default_image)
|
|
|
|
# Unlock the image
|
|
unlock_img_method = getattr(obj, "unlock" + cap_attr)
|
|
unlock_img_method()
|
|
|
|
|
|
def edit_art(obj):
|
|
_test_mixins_edit_image(obj, "arts")
|
|
|
|
|
|
def edit_poster(obj):
|
|
_test_mixins_edit_image(obj, "posters")
|
|
|
|
|
|
def _test_mixins_imageUrl(obj, attr):
|
|
url = getattr(obj, attr + "Url")
|
|
if getattr(obj, attr):
|
|
assert url.startswith(utils.SERVER_BASEURL)
|
|
assert "/library/metadata/" in url or "/library/collections/" in url
|
|
assert attr in url or "composite" in url
|
|
if attr == "thumb":
|
|
assert getattr(obj, "posterUrl") == url
|
|
else:
|
|
assert url is None
|
|
|
|
|
|
def attr_artUrl(obj):
|
|
_test_mixins_imageUrl(obj, "art")
|
|
|
|
|
|
def attr_posterUrl(obj):
|
|
_test_mixins_imageUrl(obj, "thumb")
|
|
|
|
|
|
def _test_mixins_edit_theme(obj):
|
|
_fields = lambda: [f.name for f in obj.fields]
|
|
|
|
# Test upload theme from file
|
|
obj.uploadTheme(filepath=utils.STUB_MP3_PATH)
|
|
themes = obj.themes()
|
|
file_theme = [
|
|
t for t in themes
|
|
if t.ratingKey.startswith("upload://") and t.ratingKey.endswith(AUDIO_STUB_SHA1)
|
|
]
|
|
assert file_theme
|
|
obj.reload()
|
|
assert "theme" in _fields()
|
|
|
|
# Unlock the theme
|
|
obj.unlockTheme()
|
|
obj.reload()
|
|
assert "theme" not in _fields()
|
|
|
|
# Lock the theme
|
|
obj.lockTheme()
|
|
obj.reload()
|
|
assert "theme" in _fields()
|
|
with pytest.raises(NotImplementedError):
|
|
obj.setTheme(themes[0])
|
|
|
|
|
|
def edit_theme(obj):
|
|
_test_mixins_edit_theme(obj)
|
|
|
|
|
|
def _test_mixins_themeUrl(obj):
|
|
url = obj.themeUrl
|
|
if url:
|
|
assert url.startswith(utils.SERVER_BASEURL)
|
|
assert "/library/metadata/" in url
|
|
assert "theme" in url
|
|
else:
|
|
assert url is None
|
|
|
|
|
|
def attr_themeUrl(obj):
|
|
_test_mixins_themeUrl(obj)
|
|
|
|
|
|
def _test_mixins_editAdvanced(obj):
|
|
for pref in obj.preferences():
|
|
currentPref = obj.preference(pref.id)
|
|
currentValue = currentPref.value
|
|
newValue = next(v for v in pref.enumValues if v != currentValue)
|
|
obj.editAdvanced(**{pref.id: newValue})
|
|
obj.reload()
|
|
newPref = obj.preference(pref.id)
|
|
assert newPref.value == newValue
|
|
|
|
|
|
def _test_mixins_editAdvanced_bad_pref(obj):
|
|
with pytest.raises(NotFound):
|
|
assert obj.preference("bad-pref")
|
|
|
|
|
|
def _test_mixins_defaultAdvanced(obj):
|
|
obj.defaultAdvanced()
|
|
obj.reload()
|
|
for pref in obj.preferences():
|
|
assert pref.value == pref.default
|
|
|
|
|
|
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()
|
|
obj.reload()
|
|
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)
|