Skip to content

Commit 59638dc

Browse files
authored
Add new methods to edit object fields (#876)
* Update Collection.edit() method * Add new EditFieldMixin and subclasses * Add EditFieldMixin to Plex objects * Update PlexPartialObject.edit() doc string * Cast Track parentIndex to int * Add base _searchTypes property * Add kwargs to editField * Editing album title also requires the artist ratingKey * Add tests for editing fields * Deprecate Collection.edit() * Make editing fields chainable * Add chaining example to doc string * Add batch editing mode * Add test for batchEdits * Clean up mixins imports * Reorder mixins * Fix tests for batch edit
1 parent a568819 commit 59638dc

File tree

12 files changed

+738
-213
lines changed

12 files changed

+738
-213
lines changed

plexapi/audio.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from plexapi import library, media, utils
66
from plexapi.base import Playable, PlexPartialObject
77
from plexapi.exceptions import BadRequest
8-
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, ThemeMixin, \
9-
ThemeUrlMixin
10-
from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin
11-
from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, \
12-
StyleMixin
8+
from plexapi.mixins import (
9+
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, RatingMixin,
10+
ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, ThemeMixin, ThemeUrlMixin,
11+
OriginallyAvailableMixin, SortTitleMixin, StudioMixin, SummaryMixin, TitleMixin,
12+
TrackArtistMixin, TrackDiscNumberMixin, TrackNumberMixin,
13+
CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin
14+
)
1315
from plexapi.playlist import Playlist
1416

1517

@@ -127,9 +129,13 @@ def sync(self, bitrate, client=None, clientId=None, limit=None, title=None):
127129

128130

129131
@utils.registerPlexObject
130-
class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, ThemeMixin, RatingMixin, SplitMergeMixin,
131-
UnmatchMatchMixin, CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin,
132-
SimilarArtistMixin, StyleMixin):
132+
class Artist(
133+
Audio,
134+
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, RatingMixin,
135+
ArtMixin, PosterMixin, ThemeMixin,
136+
SortTitleMixin, SummaryMixin, TitleMixin,
137+
CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin
138+
):
133139
""" Represents a single Artist.
134140
135141
Attributes:
@@ -237,8 +243,13 @@ def station(self):
237243

238244

239245
@utils.registerPlexObject
240-
class Album(Audio, ArtMixin, PosterMixin, ThemeUrlMixin, RatingMixin, UnmatchMatchMixin,
241-
CollectionMixin, GenreMixin, LabelMixin, MoodMixin, StyleMixin):
246+
class Album(
247+
Audio,
248+
UnmatchMatchMixin, RatingMixin,
249+
ArtMixin, PosterMixin, ThemeUrlMixin,
250+
OriginallyAvailableMixin, SortTitleMixin, StudioMixin, SummaryMixin, TitleMixin,
251+
CollectionMixin, GenreMixin, LabelMixin, MoodMixin, StyleMixin
252+
):
242253
""" Represents a single Album.
243254
244255
Attributes:
@@ -346,8 +357,13 @@ def _defaultSyncTitle(self):
346357

347358

348359
@utils.registerPlexObject
349-
class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, ThemeUrlMixin, RatingMixin,
350-
CollectionMixin, LabelMixin, MoodMixin):
360+
class Track(
361+
Audio, Playable,
362+
RatingMixin,
363+
ArtUrlMixin, PosterUrlMixin, ThemeUrlMixin,
364+
TitleMixin, TrackArtistMixin, TrackNumberMixin, TrackDiscNumberMixin,
365+
CollectionMixin, LabelMixin, MoodMixin
366+
):
351367
""" Represents a single Track.
352368
353369
Attributes:
@@ -369,7 +385,7 @@ class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, ThemeUrlMixin, RatingM
369385
media (List<:class:`~plexapi.media.Media`>): List of media objects.
370386
originalTitle (str): The artist for the track.
371387
parentGuid (str): Plex GUID for the album (plex://album/5d07cd8e403c640290f180f9).
372-
parentIndex (int): Album index.
388+
parentIndex (int): Disc number of the track.
373389
parentKey (str): API URL of the album (/library/metadata/<parentRatingKey>).
374390
parentRatingKey (int): Unique key identifying the album.
375391
parentThumb (str): URL to album thumbnail image (/library/metadata/<parentRatingKey>/thumb/<thumbid>).
@@ -400,7 +416,7 @@ def _loadData(self, data):
400416
self.media = self.findItems(data, media.Media)
401417
self.originalTitle = data.attrib.get('originalTitle')
402418
self.parentGuid = data.attrib.get('parentGuid')
403-
self.parentIndex = data.attrib.get('parentIndex')
419+
self.parentIndex = utils.cast(int, data.attrib.get('parentIndex'))
404420
self.parentKey = data.attrib.get('parentKey')
405421
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey'))
406422
self.parentThumb = data.attrib.get('parentThumb')

plexapi/base.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(self, server, data, initpath=None, parent=None):
5454
self._loadData(data)
5555
self._details_key = self._buildDetailsKey()
5656
self._autoReload = False
57+
self._edits = None # Save batch edits for a single API call
5758

5859
def __repr__(self):
5960
uid = self._clean(self.firstAttr('_baseurl', 'key', 'id', 'playQueueID', 'uri'))
@@ -419,6 +420,10 @@ def _castAttrValue(self, op, query, value):
419420
def _loadData(self, data):
420421
raise NotImplementedError('Abstract method not implemented.')
421422

423+
@property
424+
def _searchType(self):
425+
return self.TYPE
426+
422427

423428
class PlexPartialObject(PlexObject):
424429
""" Not all objects in the Plex listings return the complete list of elements
@@ -512,28 +517,79 @@ def isPartialObject(self):
512517

513518
def _edit(self, **kwargs):
514519
""" Actually edit an object. """
520+
if isinstance(self._edits, dict):
521+
self._edits.update(kwargs)
522+
return self
523+
515524
if 'id' not in kwargs:
516525
kwargs['id'] = self.ratingKey
517526
if 'type' not in kwargs:
518-
kwargs['type'] = utils.searchType(self.type)
527+
kwargs['type'] = utils.searchType(self._searchType)
519528

520-
part = '/library/sections/%s/all?%s' % (self.librarySectionID,
521-
urlencode(kwargs))
529+
part = '/library/sections/%s/all%s' % (self.librarySectionID,
530+
utils.joinArgs(kwargs))
522531
self._server.query(part, method=self._server._session.put)
532+
return self
523533

524534
def edit(self, **kwargs):
525535
""" Edit an object.
536+
Note: This is a low level method and you need to know all the field/tag keys.
537+
See :class:`~plexapi.mixins.EditFieldMixin` and :class:`~plexapi.mixins.EditTagsMixin`
538+
for individual field and tag editing methods.
526539
527540
Parameters:
528541
kwargs (dict): Dict of settings to edit.
529542
530543
Example:
531-
{'type': 1,
532-
'id': movie.ratingKey,
533-
'collection[0].tag.tag': 'Super',
534-
'collection.locked': 0}
544+
545+
.. code-block:: python
546+
547+
edits = {
548+
'type': 1,
549+
'id': movie.ratingKey,
550+
'title.value': 'A new title',
551+
'title.locked': 1,
552+
'summary.value': 'This is a summary.',
553+
'summary.locked': 1,
554+
'collection[0].tag.tag': 'A tag',
555+
'collection.locked': 1}
556+
}
557+
movie.edit(**edits)
558+
535559
"""
536-
self._edit(**kwargs)
560+
return self._edit(**kwargs)
561+
562+
def batchEdits(self):
563+
""" Enable batch editing mode to save API calls.
564+
Must call :func:`~plexapi.base.PlexPartialObject.saveEdits` at the end to save all the edits.
565+
See :class:`~plexapi.mixins.EditFieldMixin` and :class:`~plexapi.mixins.EditTagsMixin`
566+
for individual field and tag editing methods.
567+
568+
Example:
569+
570+
.. code-block:: python
571+
572+
# Batch editing multiple fields and tags in a single API call
573+
Movie.batchEdits()
574+
Movie.editTitle('A New Title').editSummary('A new summary').editTagline('A new tagline') \\
575+
.addCollection('New Collection').removeGenre('Action').addLabel('Favorite')
576+
Movie.saveEdits()
577+
578+
"""
579+
self._edits = {}
580+
return self
581+
582+
def saveEdits(self):
583+
""" Save all the batch edits and automatically reload the object.
584+
See :func:`~plexapi.base.PlexPartialObject.batchEdits` for details.
585+
"""
586+
if not isinstance(self._edits, dict):
587+
raise BadRequest('Batch editing mode not enabled. Must call `batchEdits()` first.')
588+
589+
edits = self._edits
590+
self._edits = None
591+
self._edit(**edits)
592+
return self.reload()
537593

538594
def _edit_tags(self, tag, items, locked=True, remove=False):
539595
""" Helper to edit tags.

plexapi/collection.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@
55
from plexapi.base import PlexPartialObject
66
from plexapi.exceptions import BadRequest, NotFound, Unsupported
77
from plexapi.library import LibrarySection
8-
from plexapi.mixins import AdvancedSettingsMixin, ArtMixin, PosterMixin, ThemeMixin, RatingMixin
9-
from plexapi.mixins import LabelMixin, SmartFilterMixin
8+
from plexapi.mixins import (
9+
AdvancedSettingsMixin, SmartFilterMixin, RatingMixin,
10+
ArtMixin, PosterMixin, ThemeMixin,
11+
ContentRatingMixin, SortTitleMixin, SummaryMixin, TitleMixin,
12+
LabelMixin
13+
)
1014
from plexapi.playqueue import PlayQueue
1115
from plexapi.utils import deprecated
1216

1317

1418
@utils.registerPlexObject
15-
class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin, ThemeMixin, RatingMixin,
16-
LabelMixin, SmartFilterMixin):
19+
class Collection(
20+
PlexPartialObject,
21+
AdvancedSettingsMixin, SmartFilterMixin, RatingMixin,
22+
ArtMixin, PosterMixin, ThemeMixin,
23+
ContentRatingMixin, SortTitleMixin, SummaryMixin, TitleMixin,
24+
LabelMixin
25+
):
1726
""" Represents a single Collection.
1827
1928
Attributes:
@@ -343,6 +352,7 @@ def updateFilters(self, libtype=None, limit=None, sort=None, filters=None, **kwa
343352
}))
344353
self._server.query(key, method=self._server._session.put)
345354

355+
@deprecated('use editTitle, editSortTitle, editContentRating, and editSummary instead')
346356
def edit(self, title=None, titleSort=None, contentRating=None, summary=None, **kwargs):
347357
""" Edit the collection.
348358
@@ -367,7 +377,7 @@ def edit(self, title=None, titleSort=None, contentRating=None, summary=None, **k
367377
args['summary.locked'] = 1
368378

369379
args.update(kwargs)
370-
super(Collection, self).edit(**args)
380+
self._edit(**args)
371381

372382
def delete(self):
373383
""" Delete the collection. """

0 commit comments

Comments
 (0)