mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-10 06:54:21 +00:00
match plex_search with smart_filter
This commit is contained in:
parent
f9659c62e3
commit
4de9e3603d
3 changed files with 186 additions and 188 deletions
|
@ -14,7 +14,9 @@ advance_new_agent = ["item_metadata_language", "item_use_original_title"]
|
|||
advance_show = ["item_episode_sorting", "item_keep_episodes", "item_delete_episodes", "item_season_display", "item_episode_sorting"]
|
||||
method_alias = {
|
||||
"actors": "actor", "role": "actor", "roles": "actor",
|
||||
"collections": "collecion", "plex_collection": "collection",
|
||||
"show_actor": "actor", "show_actors": "actor", "show_role": "actor", "show_roles": "actor",
|
||||
"collections": "collection", "plex_collection": "collection",
|
||||
"show_collections": "collection", "show_collection": "collection",
|
||||
"content_ratings": "content_rating", "contentRating": "content_rating", "contentRatings": "content_rating",
|
||||
"countries": "country",
|
||||
"decades": "decade",
|
||||
|
@ -22,11 +24,18 @@ method_alias = {
|
|||
"genres": "genre",
|
||||
"labels": "label",
|
||||
"rating": "critic_rating",
|
||||
"show_user_rating": "user_rating",
|
||||
"play": "plays", "show_plays": "plays", "show_play": "plays", "episode_play": "episode_plays",
|
||||
"originally_available": "release", "episode_originally_available": "episode_air_date",
|
||||
"episode_release": "episode_air_date", "episode_released": "episode_air_date",
|
||||
"show_originally_available": "release", "show_release": "release", "show_air_date": "release",
|
||||
"released": "release", "show_released": "release",
|
||||
"studios": "studio",
|
||||
"networks": "network",
|
||||
"producers": "producer",
|
||||
"writers": "writer",
|
||||
"years": "year"
|
||||
"years": "year", "show_year": "year", "show_years": "year",
|
||||
"show_title": "title"
|
||||
}
|
||||
filter_translation = {
|
||||
"actor": "actors",
|
||||
|
@ -37,7 +46,9 @@ filter_translation = {
|
|||
"critic_rating": "rating",
|
||||
"director": "directors",
|
||||
"genre": "genres",
|
||||
"originally_available": "originallyAvailableAt",
|
||||
"label": "labels",
|
||||
"producer": "producers",
|
||||
"release": "originallyAvailableAt",
|
||||
"tmdb_vote_count": "vote_count",
|
||||
"user_rating": "userRating",
|
||||
"writer": "writers"
|
||||
|
@ -152,7 +163,8 @@ all_filters = [
|
|||
"filepath", "filepath.not",
|
||||
"genre", "genre.not",
|
||||
"max_age",
|
||||
"originally_available.gte", "originally_available.lte",
|
||||
"release.gte", "release.lte",
|
||||
"title", "title.not",
|
||||
"tmdb_vote_count.gte", "tmdb_vote_count.lte",
|
||||
"duration.gte", "duration.lte",
|
||||
"original_language", "original_language.not",
|
||||
|
@ -177,15 +189,6 @@ movie_only_filters = [
|
|||
"writer", "writer.not"
|
||||
]
|
||||
|
||||
def _split(text):
|
||||
attribute, modifier = os.path.splitext(str(text).lower())
|
||||
attribute = method_alias[attribute] if attribute in method_alias else attribute
|
||||
modifier = modifier_alias[modifier] if modifier in modifier_alias else modifier
|
||||
final = f"{attribute}{modifier}"
|
||||
if text != final:
|
||||
logger.warning(f"Collection Warning: {text} plex search attribute will run as {final}")
|
||||
return attribute, modifier, final
|
||||
|
||||
class CollectionBuilder:
|
||||
def __init__(self, config, library, metadata, name, data):
|
||||
self.config = config
|
||||
|
@ -214,8 +217,8 @@ class CollectionBuilder:
|
|||
self.schedule = ""
|
||||
self.add_to_radarr = None
|
||||
self.add_to_sonarr = None
|
||||
current_time = datetime.now()
|
||||
current_year = current_time.year
|
||||
self.current_time = datetime.now()
|
||||
self.current_year = self.current_time.year
|
||||
|
||||
methods = {m.lower(): m for m in self.data}
|
||||
|
||||
|
@ -341,7 +344,7 @@ class CollectionBuilder:
|
|||
logger.debug(f"Value: {self.data[methods['schedule']]}")
|
||||
skip_collection = True
|
||||
schedule_list = util.get_list(self.data[methods["schedule"]])
|
||||
next_month = current_time.replace(day=28) + timedelta(days=4)
|
||||
next_month = self.current_time.replace(day=28) + timedelta(days=4)
|
||||
last_day = next_month - timedelta(days=next_month.day)
|
||||
for schedule in schedule_list:
|
||||
run_time = str(schedule).lower()
|
||||
|
@ -369,13 +372,13 @@ class CollectionBuilder:
|
|||
continue
|
||||
weekday = util.days_alias[param.lower()]
|
||||
self.schedule += f"\nScheduled weekly on {util.pretty_days[weekday]}"
|
||||
if weekday == current_time.weekday():
|
||||
if weekday == self.current_time.weekday():
|
||||
skip_collection = False
|
||||
elif run_time.startswith("month"):
|
||||
try:
|
||||
if 1 <= int(param) <= 31:
|
||||
self.schedule += f"\nScheduled monthly on the {util.make_ordinal(param)}"
|
||||
if current_time.day == int(param) or (current_time.day == last_day.day and int(param) > last_day.day):
|
||||
if self.current_time.day == int(param) or (self.current_time.day == last_day.day and int(param) > last_day.day):
|
||||
skip_collection = False
|
||||
else:
|
||||
raise ValueError
|
||||
|
@ -389,7 +392,7 @@ class CollectionBuilder:
|
|||
month = int(match.group(1))
|
||||
day = int(match.group(2))
|
||||
self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}"
|
||||
if current_time.month == month and (current_time.day == day or (current_time.day == last_day.day and day > last_day.day)):
|
||||
if self.current_time.month == month and (self.current_time.day == day or (self.current_time.day == last_day.day and day > last_day.day)):
|
||||
skip_collection = False
|
||||
else:
|
||||
logger.error(f"Collection Error: schedule attribute {schedule} invalid")
|
||||
|
@ -539,14 +542,14 @@ class CollectionBuilder:
|
|||
validate = smart_filter[smart_methods["validate"]]
|
||||
filter_details += f"Validate: {validate}\n"
|
||||
|
||||
def _filter(filter_dict, fail, is_all=True, level=1):
|
||||
def _filter(filter_dict, is_all=True, level=1):
|
||||
output = ""
|
||||
display = f"\n{' ' * level}Match {'all' if is_all else 'any'} of the following:"
|
||||
level += 1
|
||||
indent = f"\n{' ' * level}"
|
||||
conjunction = f"{'and' if is_all else 'or'}=1&"
|
||||
for smart_key, smart_data in filter_dict.items():
|
||||
smart, smart_mod, smart_final = _split(smart_key)
|
||||
smart, smart_mod, smart_final = self._split(smart_key)
|
||||
|
||||
def build_url_arg(arg, mod=None, arg_s=None, mod_s=None, param_s=None):
|
||||
arg_key = plex.search_translation[smart] if smart in plex.search_translation else smart
|
||||
|
@ -578,63 +581,33 @@ class CollectionBuilder:
|
|||
for dict_data in dicts:
|
||||
if not isinstance(dict_data, dict):
|
||||
raise Failed(f"Collection Error: {smart} must be either a dictionary or list of dictionaries")
|
||||
inside_filter, inside_display = _filter(dict_data, fail, is_all=smart == "all", level=level)
|
||||
display_add += inside_display
|
||||
results += f"{conjunction if len(results) > 0 else ''}push=1&{inside_filter}pop=1&"
|
||||
elif smart in ["year", "episode_year"] and smart_mod in [".gt", ".gte", ".lt", ".lte"]:
|
||||
results, display_add = build_url_arg(util.check_year(smart_data, current_year, smart_final))
|
||||
elif smart in ["added", "episode_added", "originally_available", "episode_originally_available"] and smart_mod in [".before", ".after"]:
|
||||
results, display_add = build_url_arg(util.check_date(smart_data, smart_final, return_string=True, plex_date=True))
|
||||
elif smart in ["added", "episode_added", "originally_available", "episode_originally_available"] and smart_mod in ["", ".not"]:
|
||||
in_the_last = util.check_number(smart_data, smart_final, minimum=1)
|
||||
last_text = "is not in the last" if smart_mod == ".not" else "is in the last"
|
||||
last_mod = "%3E%3E" if smart_mod == "" else "%3C%3C"
|
||||
results, display_add = build_url_arg(f"-{in_the_last}d", mod=last_mod, arg_s=f"{in_the_last} Days", mod_s=last_text)
|
||||
elif smart in ["duration"] and smart_mod in [".gt", ".gte", ".lt", ".lte"]:
|
||||
results, display_add = build_url_arg(util.check_number(smart_data, smart_final, minimum=1) * 60000)
|
||||
elif smart in ["plays", "episode_plays"] and smart_mod in [".gt", ".gte", ".lt", ".lte"]:
|
||||
results, display_add = build_url_arg(util.check_number(smart_data, smart_final, minimum=1))
|
||||
elif smart in ["user_rating", "episode_user_rating", "critic_rating", "audience_rating"] and smart_mod in [".gt", ".gte", ".lt", ".lte"]:
|
||||
results, display_add = build_url_arg(util.check_number(smart_data, smart_final, number_type="float", minimum=0, maximum=10))
|
||||
elif smart == "hdr":
|
||||
if isinstance(smart_data, bool):
|
||||
hdr_mod = "" if smart_data else "!"
|
||||
hdr_arg = "true" if smart_data else "false"
|
||||
results, display_add = build_url_arg(1, mod=hdr_mod, arg_s=hdr_arg, mod_s="is", param_s="HDR")
|
||||
else:
|
||||
raise Failed("Collection Error: HDR must be true or false")
|
||||
inside_filter, inside_display = _filter(dict_data, is_all=smart == "all", level=level)
|
||||
if len(inside_filter) > 0:
|
||||
display_add += inside_display
|
||||
results += f"{conjunction if len(results) > 0 else ''}push=1&{inside_filter}pop=1&"
|
||||
else:
|
||||
if smart in ["title", "episode_title", "studio"] and smart_mod in ["", ".not", ".begins", ".ends"]:
|
||||
results_list = [(t, t) for t in util.get_list(smart_data, split=False)]
|
||||
elif smart in plex.tags and smart_mod in ["", ".not", ".begins", ".ends"]:
|
||||
if smart_final in plex.tmdb_searches:
|
||||
smart_values = []
|
||||
for tmdb_value in util.get_list(smart_data):
|
||||
if tmdb_value.lower() == "tmdb" and "tmdb_person" in self.details:
|
||||
for tmdb_name in self.details["tmdb_person"]:
|
||||
smart_values.append(tmdb_name)
|
||||
else:
|
||||
smart_values.append(tmdb_value)
|
||||
else:
|
||||
smart_values = util.get_list(smart_data)
|
||||
results_list = []
|
||||
try:
|
||||
results_list = self.library.validate_search_list(smart_values, smart, title=False, pairs=True)
|
||||
except Failed as e:
|
||||
if fail:
|
||||
raise
|
||||
else:
|
||||
logger.error(e)
|
||||
elif smart in ["decade", "year", "episode_year"] and smart_mod in ["", ".not"]:
|
||||
results_list = [(y, y) for y in util.get_year_list(smart_data, current_year, smart_final)]
|
||||
validation = self.validate_attribute(smart, smart_mod, smart_final, smart_data, validate, smart=True)
|
||||
if validation is None:
|
||||
continue
|
||||
elif smart in ["added", "episode_added", "release", "episode_air_date"] and smart_mod in ["", ".not"]:
|
||||
last_text = "is not in the last" if smart_mod == ".not" else "is in the last"
|
||||
last_mod = "%3E%3E" if smart_mod == "" else "%3C%3C"
|
||||
results, display_add = build_url_arg(f"-{validation}d", mod=last_mod, arg_s=f"{validation} Days", mod_s=last_text)
|
||||
elif smart in ["duration"] and smart_mod in [".gt", ".gte", ".lt", ".lte"]:
|
||||
results, display_add = build_url_arg(validation * 60000)
|
||||
elif smart == "hdr":
|
||||
hdr_mod = "" if validation else "!"
|
||||
hdr_arg = "true" if validation else "false"
|
||||
results, display_add = build_url_arg(1, mod=hdr_mod, arg_s=hdr_arg, mod_s="is", param_s="HDR")
|
||||
elif (smart in ["title", "episode_title", "studio", "decade", "year", "episode_year"] or smart in plex.tags) and smart_mod in ["", ".not", ".begins", ".ends"]:
|
||||
results = ""
|
||||
display_add = ""
|
||||
for og_value, result in validation:
|
||||
built_arg = build_url_arg(quote(result) if smart in string_filters else result, arg_s=og_value)
|
||||
display_add += built_arg[1]
|
||||
results += f"{conjunction if len(results) > 0 else ''}{built_arg[0]}"
|
||||
else:
|
||||
raise Failed(f"Collection Error: modifier: {smart_mod} not supported with the {smart} plex search attribute")
|
||||
results = ""
|
||||
display_add = ""
|
||||
for og_value, result in results_list:
|
||||
built_arg = build_url_arg(quote(result) if smart in string_filters else result, arg_s=og_value)
|
||||
display_add += built_arg[1]
|
||||
results += f"{conjunction if len(results) > 0 else ''}{built_arg[0]}"
|
||||
results, display_add = build_url_arg(validation)
|
||||
display += display_add
|
||||
output += f"{conjunction if len(output) > 0 else ''}{results}"
|
||||
return output, display
|
||||
|
@ -645,7 +618,7 @@ class CollectionBuilder:
|
|||
raise Failed(f"Collection Error: {base} attribute is blank")
|
||||
if not isinstance(smart_filter[smart_methods[base]], dict):
|
||||
raise Failed(f"Collection Error: {base} must be a dictionary: {smart_filter[smart_methods[base]]}")
|
||||
built_filter, filter_text = _filter(smart_filter[smart_methods[base]], validate, is_all=base_all)
|
||||
built_filter, filter_text = _filter(smart_filter[smart_methods[base]], is_all=base_all)
|
||||
self.smart_filter_details = f"{filter_details}Filter:{filter_text}"
|
||||
if len(built_filter) > 0:
|
||||
final_filter = built_filter[:-1] if base_all else f"push=1&{built_filter}pop=1"
|
||||
|
@ -683,17 +656,7 @@ class CollectionBuilder:
|
|||
elif "mal" in method_key.lower() and not config.MyAnimeList: raise Failed(f"Collection Error: {method_key} requires MyAnimeList to be configured")
|
||||
elif method_data is not None:
|
||||
logger.debug(f"Value: {method_data}")
|
||||
if method_key.lower() in method_alias:
|
||||
method_name = method_alias[method_key.lower()]
|
||||
logger.warning(f"Collection Warning: {method_key} attribute will run as {method_name}")
|
||||
elif method_key.lower() == "add_to_arr":
|
||||
method_name = "radarr_add" if self.library.is_movie else "sonarr_add"
|
||||
logger.warning(f"Collection Warning: {method_key} attribute will run as {method_name}")
|
||||
elif method_key.lower() in ["arr_tag", "arr_folder"]:
|
||||
method_name = f"{'rad' if self.library.is_movie else 'son'}{method_key.lower()}"
|
||||
logger.warning(f"Collection Warning: {method_key} attribute will run as {method_name}")
|
||||
else:
|
||||
method_name = method_key.lower()
|
||||
method_name, method_mod, method_final = self._split(method_key)
|
||||
if method_name in show_only_builders and self.library.is_movie:
|
||||
raise Failed(f"Collection Error: {method_name} attribute only works for show libraries")
|
||||
elif method_name in movie_only_builders and self.library.is_show:
|
||||
|
@ -839,15 +802,15 @@ class CollectionBuilder:
|
|||
elif method_name in ["title", "title.and", "title.not", "title.begins", "studio.ends", "studio", "studio.and", "studio.not", "studio.begins", "studio.ends"]:
|
||||
self.methods.append(("plex_search", [{method_name: util.get_list(method_data, split=False)}]))
|
||||
elif method_name in ["year.gt", "year.gte", "year.lt", "year.lte"]:
|
||||
self.methods.append(("plex_search", [{method_name: util.check_year(method_data, current_year, method_name)}]))
|
||||
elif method_name in ["added.before", "added.after", "originally_available.before", "originally_available.after"]:
|
||||
self.methods.append(("plex_search", [{method_name: util.check_year(method_data, self.current_year, method_name)}]))
|
||||
elif method_name in ["added.before", "added.after", "release.before", "release.after"]:
|
||||
self.methods.append(("plex_search", [{method_name: util.check_date(method_data, method_name, return_string=True, plex_date=True)}]))
|
||||
elif method_name in ["added", "added.not", "originally_available", "originally_available.not", "duration.gt", "duration.gte", "duration.lt", "duration.lte"]:
|
||||
elif method_name in ["added", "added.not", "release", "release.not", "duration.gt", "duration.gte", "duration.lt", "duration.lte"]:
|
||||
self.methods.append(("plex_search", [{method_name: util.check_number(method_data, method_name, minimum=1)}]))
|
||||
elif method_name in ["user_rating.gt", "user_rating.gte", "user_rating.lt", "user_rating.lte", "critic_rating.gt", "critic_rating.gte", "critic_rating.lt", "critic_rating.lte", "audience_rating.gt", "audience_rating.gte", "audience_rating.lt", "audience_rating.lte"]:
|
||||
self.methods.append(("plex_search", [{method_name: util.check_number(method_data, method_name, number_type="float", minimum=0, maximum=10)}]))
|
||||
elif method_name in ["decade", "year", "year.not"]:
|
||||
self.methods.append(("plex_search", [{method_name: util.get_year_list(method_data, current_year, method_name)}]))
|
||||
self.methods.append(("plex_search", [{method_name: util.get_year_list(method_data, self.current_year, method_name)}]))
|
||||
elif method_name in plex.searches:
|
||||
if method_name in plex.tmdb_searches:
|
||||
final_values = []
|
||||
|
@ -940,19 +903,21 @@ class CollectionBuilder:
|
|||
elif filter_data is None:
|
||||
raise Failed(f"Collection Error: {filter_method} filter is blank")
|
||||
elif filter_method == "year":
|
||||
valid_data = util.get_year_list(filter_data, current_year, f"{filter_method} filter")
|
||||
valid_data = util.get_year_list(filter_data, self.current_year, f"{filter_method} filter")
|
||||
elif filter_method in ["max_age", "duration.gte", "duration.lte", "tmdb_vote_count.gte", "tmdb_vote_count.lte"]:
|
||||
valid_data = util.check_number(filter_data, f"{filter_method} filter", minimum=1)
|
||||
elif filter_method in ["year.gte", "year.lte"]:
|
||||
valid_data = util.check_year(filter_data, current_year, f"{filter_method} filter")
|
||||
valid_data = util.check_year(filter_data, self.current_year, f"{filter_method} filter")
|
||||
elif filter_method in ["user_rating.gte", "user_rating.lte", "audience_rating.gte", "audience_rating.lte", "critic_rating.gte", "critic_rating.lte"]:
|
||||
valid_data = util.check_number(filter_data, f"{filter_method} filter", number_type="float", minimum=0.1, maximum=10)
|
||||
elif filter_method in ["originally_available.gte", "originally_available.lte"]:
|
||||
elif filter_method in ["release.gte", "release.lte"]:
|
||||
valid_data = util.check_date(filter_data, f"{filter_method} filter")
|
||||
elif filter_method in ["original_language", "original_language.not"]:
|
||||
valid_data = util.get_list(filter_data, lower=True)
|
||||
elif filter_method in ["collection", "collection.not"]:
|
||||
valid_data = filter_data if isinstance(filter_data, list) else [filter_data]
|
||||
elif filter_method in ["title", "title.not"]:
|
||||
valid_data = util.get_list(filter_data, split=False)
|
||||
elif filter_method in all_filters:
|
||||
valid_data = util.get_list(filter_data)
|
||||
else:
|
||||
|
@ -989,9 +954,7 @@ class CollectionBuilder:
|
|||
raise Failed("Collection Error: validate plex search attribute must be either true or false")
|
||||
validate = method_data["validate"]
|
||||
for search_name, search_data in method_data.items():
|
||||
search, modifier, search_final = _split(search_name)
|
||||
if search_name != search_final:
|
||||
logger.warning(f"Collection Warning: {search_name} plex search attribute will run as {search_final}")
|
||||
search, modifier, search_final = self._split(search_name)
|
||||
if search_final in plex.movie_only_searches and self.library.is_show:
|
||||
raise Failed(f"Collection Error: {search_final} plex search attribute only works for movie libraries")
|
||||
elif search_final in plex.show_only_searches and self.library.is_movie:
|
||||
|
@ -1012,38 +975,8 @@ class CollectionBuilder:
|
|||
searches[search] = search_data
|
||||
elif search_final not in plex.searches:
|
||||
raise Failed(f"Collection Error: {search_final} is not a valid plex search attribute")
|
||||
elif search in ["title", "studio"] and modifier in ["", ".and", ".not", ".begins", ".ends"]:
|
||||
searches[search_final] = util.get_list(search_data, split=False)
|
||||
elif search in plex.tags and modifier in ["", ".and", ".not", ".begins", ".ends"]:
|
||||
if search_final in plex.tmdb_searches:
|
||||
final_values = []
|
||||
for value in util.get_list(search_data):
|
||||
if value.lower() == "tmdb" and "tmdb_person" in self.details:
|
||||
for name in self.details["tmdb_person"]:
|
||||
final_values.append(name)
|
||||
else:
|
||||
final_values.append(value)
|
||||
else:
|
||||
final_values = util.get_list(search_data)
|
||||
try:
|
||||
searches[search_final] = self.library.validate_search_list(final_values, search)
|
||||
except Failed as e:
|
||||
if validate:
|
||||
raise
|
||||
else:
|
||||
logger.error(e)
|
||||
elif search == "year" and modifier in [".gt", ".gte", ".lt", ".lte"]:
|
||||
searches[search_final] = util.check_year(search_data, current_year, search_final)
|
||||
elif search in ["added", "originally_available"] and modifier in [".before", ".after"]:
|
||||
searches[search_final] = util.check_date(search_data, search_final, return_string=True, plex_date=True)
|
||||
elif search in ["added", "originally_available", "duration"] and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]:
|
||||
searches[search_final] = util.check_number(search_data, search_final, minimum=1)
|
||||
elif search in ["user_rating", "critic_rating", "audience_rating"] and modifier in [".gt", ".gte", ".lt", ".lte"]:
|
||||
searches[search_final] = util.check_number(search_data, search_final, number_type="float", minimum=0, maximum=10)
|
||||
elif search in ["decade", "year"] and modifier in ["", ".not"]:
|
||||
searches[search_final] = util.get_year_list(search_data, current_year, search_final)
|
||||
else:
|
||||
raise Failed(f"Collection Error: modifier: {modifier} not supported with the {search} plex search attribute")
|
||||
searches[search_final] = self.validate_attribute(search, modifier, search_final, search_data, validate)
|
||||
if len(searches) > 0:
|
||||
self.methods.append((method_name, [searches]))
|
||||
else:
|
||||
|
@ -1085,7 +1018,7 @@ class CollectionBuilder:
|
|||
elif discover_final in tmdb.discover_dates:
|
||||
new_dictionary[discover_final] = util.check_date(discover_data, f"{method_name} attribute {discover_final}", return_string=True)
|
||||
elif discover_final in ["primary_release_year", "year", "first_air_date_year"]:
|
||||
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1800, maximum=current_year + 1)
|
||||
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1800, maximum=self.current_year + 1)
|
||||
elif discover_final in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]:
|
||||
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1)
|
||||
elif discover_final in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]:
|
||||
|
@ -1130,10 +1063,10 @@ class CollectionBuilder:
|
|||
else:
|
||||
new_dictionary["sort_by"] = mal.season_sort[method_data[dict_methods["sort_by"]]]
|
||||
|
||||
if current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter"
|
||||
elif current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring"
|
||||
elif current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer"
|
||||
elif current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall"
|
||||
if self.current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter"
|
||||
elif self.current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring"
|
||||
elif self.current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer"
|
||||
elif self.current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall"
|
||||
|
||||
if "season" not in dict_methods:
|
||||
logger.warning(f"Collection Warning: mal_season season attribute not found using the current season: {new_dictionary['season']} as default")
|
||||
|
@ -1144,7 +1077,7 @@ class CollectionBuilder:
|
|||
else:
|
||||
new_dictionary["season"] = method_data[dict_methods["season"]]
|
||||
|
||||
new_dictionary["year"] = get_int(method_name, "year", method_data, dict_methods, current_time.year, minimum=1917, maximum=current_time.year + 1)
|
||||
new_dictionary["year"] = get_int(method_name, "year", method_data, dict_methods, self.current_time.year, minimum=1917, maximum=self.current_time.year + 1)
|
||||
new_dictionary["limit"] = get_int(method_name, "limit", method_data, dict_methods, 100, maximum=500)
|
||||
self.methods.append((method_name, [new_dictionary]))
|
||||
elif method_name == "mal_userlist":
|
||||
|
@ -1181,10 +1114,10 @@ class CollectionBuilder:
|
|||
new_dictionary = {"sort_by": "score"}
|
||||
dict_methods = {dm.lower(): dm for dm in method_data}
|
||||
if method_name == "anilist_season":
|
||||
if current_time.month in [12, 1, 2]: new_dictionary["season"] = "winter"
|
||||
elif current_time.month in [3, 4, 5]: new_dictionary["season"] = "spring"
|
||||
elif current_time.month in [6, 7, 8]: new_dictionary["season"] = "summer"
|
||||
elif current_time.month in [9, 10, 11]: new_dictionary["season"] = "fall"
|
||||
if self.current_time.month in [12, 1, 2]: new_dictionary["season"] = "winter"
|
||||
elif self.current_time.month in [3, 4, 5]: new_dictionary["season"] = "spring"
|
||||
elif self.current_time.month in [6, 7, 8]: new_dictionary["season"] = "summer"
|
||||
elif self.current_time.month in [9, 10, 11]: new_dictionary["season"] = "fall"
|
||||
|
||||
if "season" not in dict_methods:
|
||||
logger.warning(f"Collection Warning: anilist_season season attribute not found using the current season: {new_dictionary['season']} as default")
|
||||
|
@ -1195,7 +1128,7 @@ class CollectionBuilder:
|
|||
else:
|
||||
new_dictionary["season"] = method_data[dict_methods["season"]]
|
||||
|
||||
new_dictionary["year"] = get_int(method_name, "year", method_data, dict_methods, current_time.year, minimum=1917, maximum=current_time.year + 1)
|
||||
new_dictionary["year"] = get_int(method_name, "year", method_data, dict_methods, self.current_time.year, minimum=1917, maximum=self.current_time.year + 1)
|
||||
elif method_name == "anilist_genre":
|
||||
if "genre" not in dict_methods:
|
||||
raise Failed(f"Collection Warning: anilist_genre genre attribute not found")
|
||||
|
@ -1368,6 +1301,58 @@ class CollectionBuilder:
|
|||
elif "trakt" in method: check_map(self.config.Trakt.get_items(method, value, self.library.is_movie))
|
||||
else: logger.error(f"Collection Error: {method} method not supported")
|
||||
|
||||
def validate_attribute(self, attribute, modifier, final, data, validate, smart=False):
|
||||
def smart_pair(list_to_pair):
|
||||
return [(t, t) for t in list_to_pair] if smart else list_to_pair
|
||||
if attribute in ["title", "studio", "episode_title"] and modifier in ["", ".and", ".not", ".begins", ".ends"]:
|
||||
return smart_pair(util.get_list(data, split=False))
|
||||
elif attribute in plex.tags and modifier in ["", ".and", ".not", ".begins", ".ends"]:
|
||||
if final in plex.tmdb_searches:
|
||||
final_values = []
|
||||
for value in util.get_list(data):
|
||||
if value.lower() == "tmdb" and "tmdb_person" in self.details:
|
||||
for name in self.details["tmdb_person"]:
|
||||
final_values.append(name)
|
||||
else:
|
||||
final_values.append(value)
|
||||
else:
|
||||
final_values = util.get_list(data)
|
||||
try:
|
||||
return self.library.validate_search_list(final_values, attribute, title=not smart, pairs=smart)
|
||||
except Failed as e:
|
||||
if validate:
|
||||
raise
|
||||
else:
|
||||
logger.error(e)
|
||||
elif attribute in ["year", "episode_year"] and modifier in [".gt", ".gte", ".lt", ".lte"]:#
|
||||
return util.check_year(data, self.current_year, final)
|
||||
elif attribute in ["added", "episode_added", "release", "episode_air_date"] and modifier in [".before", ".after"]:#
|
||||
return util.check_date(data, final, return_string=True, plex_date=True)
|
||||
elif attribute in ["plays", "episode_plays", "added", "episode_added", "release", "episode_air_date", "duration"] and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]:
|
||||
return util.check_number(data, final, minimum=1)
|
||||
elif attribute in ["user_rating", "episode_user_rating", "critic_rating", "audience_rating"] and modifier in [".gt", ".gte", ".lt", ".lte"]:
|
||||
return util.check_number(data, final, number_type="float", minimum=0, maximum=10)
|
||||
elif attribute in ["decade", "year", "episode_year"] and modifier in ["", ".not"]:
|
||||
return smart_pair(util.get_year_list(data, self.current_year, final))
|
||||
elif attribute == "hdr":
|
||||
return util.get_bool(attribute, data)
|
||||
else:
|
||||
raise Failed(f"Collection Error: modifier: {modifier} not supported with the {attribute} attribute")
|
||||
|
||||
def _split(self, text):
|
||||
attribute, modifier = os.path.splitext(str(text).lower())
|
||||
attribute = method_alias[attribute] if attribute in method_alias else attribute
|
||||
modifier = modifier_alias[modifier] if modifier in modifier_alias else modifier
|
||||
|
||||
if attribute.lower() == "add_to_arr":
|
||||
attribute = "radarr_add" if self.library.is_movie else "sonarr_add"
|
||||
elif attribute.lower() in ["arr_tag", "arr_folder"]:
|
||||
attribute = f"{'rad' if self.library.is_movie else 'son'}{attribute.lower()}"
|
||||
final = f"{attribute}{modifier}"
|
||||
if text != final:
|
||||
logger.warning(f"Collection Warning: {text} attribute will run as {final}")
|
||||
return attribute, modifier, final
|
||||
|
||||
def fetch_item(self, item):
|
||||
try:
|
||||
current = self.library.fetchItem(item.ratingKey if isinstance(item, (Movie, Show)) else int(item))
|
||||
|
@ -1412,6 +1397,14 @@ class CollectionBuilder:
|
|||
threshold_date = datetime.now() - timedelta(days=filter_data)
|
||||
if current.originallyAvailableAt is None or current.originallyAvailableAt < threshold_date:
|
||||
return False
|
||||
elif method_name == "title":
|
||||
jailbreak = False
|
||||
for check_title in filter_data:
|
||||
if check_title.lower() in current.title.lower():
|
||||
jailbreak = True
|
||||
break
|
||||
if (jailbreak and modifier == ".not") or (not jailbreak and modifier != ".not"):
|
||||
return False
|
||||
elif method_name == "original_language":
|
||||
movie = None
|
||||
for key, value in self.library.movie_map.items():
|
||||
|
|
|
@ -118,7 +118,7 @@ class Metadata:
|
|||
else:
|
||||
logger.error(f"Metadata Error: {attr} attribute is blank")
|
||||
|
||||
def edit_tags(attr, obj, group, alias, key=None, extra=None, movie_library=False):
|
||||
def edit_tags(attr, obj, group, alias, extra=None, movie_library=False):
|
||||
if movie_library and not self.library.is_movie and (attr in alias or f"{attr}.sync" in alias or f"{attr}.remove" in alias):
|
||||
logger.error(f"Metadata Error: {attr} attribute only works for movie libraries")
|
||||
elif attr in alias and f"{attr}.sync" in alias:
|
||||
|
@ -137,7 +137,7 @@ class Metadata:
|
|||
add_tags.extend(extra)
|
||||
remove_tags = util.get_list(group[alias[f"{attr}.remove"]]) if f"{attr}.remove" in alias else None
|
||||
sync_tags = util.get_list(group[alias[f"{attr}.sync"]]) if f"{attr}.sync" in alias else None
|
||||
return self.library.edit_tags(attr, obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags, key=key)
|
||||
return self.library.edit_tags(attr, obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags)
|
||||
return False
|
||||
|
||||
def set_image(attr, obj, group, alias, poster=True, url=True):
|
||||
|
@ -252,30 +252,18 @@ class Metadata:
|
|||
updated = True
|
||||
|
||||
advance_edits = {}
|
||||
add_advanced_edit("episode_sorting", item, meta, methods, show_library=True)
|
||||
add_advanced_edit("keep_episodes", item, meta, methods, show_library=True)
|
||||
add_advanced_edit("delete_episodes", item, meta, methods, show_library=True)
|
||||
add_advanced_edit("season_display", item, meta, methods, show_library=True)
|
||||
add_advanced_edit("episode_ordering", item, meta, methods, show_library=True)
|
||||
add_advanced_edit("metadata_language", item, meta, methods, new_agent=True)
|
||||
add_advanced_edit("use_original_title", item, meta, methods, new_agent=True)
|
||||
for advance_edit in ["episode_sorting", "keep_episodes", "delete_episodes", "season_display", "episode_ordering", "metadata_language", "use_original_title"]:
|
||||
is_show = advance_edit in ["episode_sorting", "keep_episodes", "delete_episodes", "season_display", "episode_ordering"]
|
||||
is_new_agent = advance_edit in ["metadata_language", "use_original_title"]
|
||||
add_advanced_edit(advance_edit, item, meta, methods, show_library=is_show, new_agent=is_new_agent)
|
||||
if self.library.edit_item(item, mapping_name, item_type, advance_edits, advanced=True):
|
||||
updated = True
|
||||
|
||||
if edit_tags("genre", item, meta, methods, extra=genres):
|
||||
updated = True
|
||||
if edit_tags("label", item, meta, methods):
|
||||
updated = True
|
||||
if edit_tags("collection", item, meta, methods):
|
||||
updated = True
|
||||
if edit_tags("country", item, meta, methods, key="countries", movie_library=True):
|
||||
updated = True
|
||||
if edit_tags("director", item, meta, methods, movie_library=True):
|
||||
updated = True
|
||||
if edit_tags("producer", item, meta, methods, movie_library=True):
|
||||
updated = True
|
||||
if edit_tags("writer", item, meta, methods, movie_library=True):
|
||||
updated = True
|
||||
for tag_edit in ["genre", "label", "collection", "country", "director", "producer", "writer"]:
|
||||
is_movie = tag_edit in ["country", "director", "producer", "writer"]
|
||||
has_extra = genres if tag_edit == "genre" else None
|
||||
if edit_tags(tag_edit, item, meta, methods, movie_library=is_movie, extra=has_extra):
|
||||
updated = True
|
||||
|
||||
logger.info(f"{item_type}: {mapping_name} Details Update {'Complete' if updated else 'Not Needed'}")
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import glob, logging, os, requests
|
||||
from modules import util
|
||||
from modules import builder, util
|
||||
from modules.meta import Metadata
|
||||
from modules.util import Failed
|
||||
import plexapi
|
||||
|
@ -19,14 +19,14 @@ search_translation = {
|
|||
"content_rating": "contentRating",
|
||||
"subtitle_language": "subtitleLanguage",
|
||||
"added": "addedAt",
|
||||
"originally_available": "originallyAvailableAt",
|
||||
"release": "originallyAvailableAt",
|
||||
"audience_rating": "audienceRating",
|
||||
"critic_rating": "rating",
|
||||
"user_rating": "userRating",
|
||||
"plays": "viewCount",
|
||||
"episode_title": "episode.title",
|
||||
"episode_added": "episode.addedAt",
|
||||
"episode_originally_available": "episode.originallyAvailableAt",
|
||||
"episode_air_date": "episode.originallyAvailableAt",
|
||||
"episode_year": "episode.year",
|
||||
"episode_user_rating": "episode.userRating",
|
||||
"episode_plays": "episode.viewCount"
|
||||
|
@ -91,26 +91,41 @@ searches = [
|
|||
"producer", "producer.and", "producer.not",
|
||||
"subtitle_language", "subtitle_language.and", "subtitle_language.not",
|
||||
"writer", "writer.and", "writer.not",
|
||||
"decade", "resolution",
|
||||
"decade", "resolution", "hdr",
|
||||
"added", "added.not", "added.before", "added.after",
|
||||
"originally_available", "originally_available.not",
|
||||
"originally_available.before", "originally_available.after",
|
||||
"release", "release.not",
|
||||
"release.before", "release.after",
|
||||
"duration.gt", "duration.gte", "duration.lt", "duration.lte",
|
||||
"plays.gt", "plays.gte", "plays.lt", "plays.lte",
|
||||
"user_rating.gt", "user_rating.gte", "user_rating.lt", "user_rating.lte",
|
||||
"critic_rating.gt", "critic_rating.gte", "critic_rating.lt", "critic_rating.lte",
|
||||
"audience_rating.gt", "audience_rating.gte", "audience_rating.lt", "audience_rating.lte",
|
||||
"year", "year.not", "year.gt", "year.gte", "year.lt", "year.lte"
|
||||
"year", "year.not", "year.gt", "year.gte", "year.lt", "year.lte",
|
||||
"episode_title", "episode_title.and", "episode_title.not", "episode_title.begins", "episode_title.ends",
|
||||
"episode_added", "episode_added.not", "episode_added.before", "episode_added.after",
|
||||
"episode_air_date", "episode_air_date.not",
|
||||
"episode_air_date.before", "episode_air_date.after",
|
||||
"episode_plays.gt", "episode_plays.gte", "episode_plays.lt", "episode_plays.lte",
|
||||
"episode_user_rating.gt", "episode_user_rating.gte", "episode_user_rating.lt", "episode_user_rating.lte",
|
||||
"episode_year", "episode_year.not", "episode_year.gt", "episode_year.gte", "episode_year.lt", "episode_year.lte"
|
||||
]
|
||||
movie_only_searches = [
|
||||
"audio_language", "audio_language.and", "audio_language.not",
|
||||
"country", "country.and", "country.not",
|
||||
"subtitle_language", "subtitle_language.and", "subtitle_language.not",
|
||||
"director", "director.and", "director.not",
|
||||
"producer", "producer.and", "producer.not",
|
||||
"writer", "writer.and", "writer.not",
|
||||
"decade", "resolution",
|
||||
"originally_available.before", "originally_available.after",
|
||||
"duration.gt", "duration.gte", "duration.lt", "duration.lte"
|
||||
]
|
||||
show_only_searches = [
|
||||
"network", "network.and", "network.not",
|
||||
"episode_title", "episode_title.and", "episode_title.not", "episode_title.begins", "episode_title.ends",
|
||||
"episode_added", "episode_added.not", "episode_added.before", "episode_added.after",
|
||||
"episode_air_date", "episode_air_date.not",
|
||||
"episode_air_date.before", "episode_air_date.after",
|
||||
"episode_plays.gt", "episode_plays.gte", "episode_plays.lt", "episode_plays.lte",
|
||||
"episode_user_rating.gt", "episode_user_rating.gte", "episode_user_rating.lt", "episode_user_rating.lte",
|
||||
"episode_year", "episode_year.not", "episode_year.gt", "episode_year.gte", "episode_year.lt", "episode_year.lte"
|
||||
]
|
||||
tmdb_searches = [
|
||||
"actor", "actor.and", "actor.not",
|
||||
|
@ -122,6 +137,7 @@ sorts = {
|
|||
None: None,
|
||||
"title.asc": "titleSort:asc", "title.desc": "titleSort:desc",
|
||||
"originally_available.asc": "originallyAvailableAt:asc", "originally_available.desc": "originallyAvailableAt:desc",
|
||||
"release.asc": "originallyAvailableAt:asc", "release.desc": "originallyAvailableAt:desc",
|
||||
"critic_rating.asc": "rating:asc", "critic_rating.desc": "rating:desc",
|
||||
"audience_rating.asc": "audienceRating:asc", "audience_rating.desc": "audienceRating:desc",
|
||||
"duration.asc": "duration:asc", "duration.desc": "duration:desc",
|
||||
|
@ -185,8 +201,8 @@ smart_searches = [
|
|||
"writer", "writer.not",
|
||||
"decade", "resolution", "hdr",
|
||||
"added", "added.not", "added.before", "added.after",
|
||||
"originally_available", "originally_available.not",
|
||||
"originally_available.before", "originally_available.after",
|
||||
"release", "release.not",
|
||||
"release.before", "release.after",
|
||||
"plays.gt", "plays.gte", "plays.lt", "plays.lte",
|
||||
"duration.gt", "duration.gte", "duration.lt", "duration.lte",
|
||||
"user_rating.gt", "user_rating.gte", "user_rating.lt", "user_rating.lte",
|
||||
|
@ -195,8 +211,8 @@ smart_searches = [
|
|||
"year", "year.not", "year.gt", "year.gte", "year.lt","year.lte",
|
||||
"episode_title", "episode_title.not", "episode_title.begins", "episode_title.ends",
|
||||
"episode_added", "episode_added.not", "episode_added.before", "episode_added.after",
|
||||
"episode_originally_available", "episode_originally_available.not",
|
||||
"episode_originally_available.before", "episode_originally_available.after",
|
||||
"episode_air_date", "episode_air_date.not",
|
||||
"episode_air_date.before", "episode_air_date.after",
|
||||
"episode_year", "episode_year.not", "episode_year.gt", "episode_year.gte", "episode_year.lt","episode_year.lte",
|
||||
"episode_user_rating.gt", "episode_user_rating.gte", "episode_user_rating.lt","episode_user_rating.lte",
|
||||
"episode_plays.gt", "episode_plays.gte", "episode_plays.lt", "episode_plays.lte"
|
||||
|
@ -207,16 +223,15 @@ movie_only_smart_searches = [
|
|||
"producer", "producer.not",
|
||||
"writer", "writer.not",
|
||||
"decade",
|
||||
"originally_available", "originally_available.not",
|
||||
"originally_available.before", "originally_available.after",
|
||||
"plays.gt", "plays.gte", "plays.lt", "plays.lte",
|
||||
"duration.gt", "duration.gte", "duration.lt", "duration.lte"
|
||||
]
|
||||
show_only_smart_searches = [
|
||||
"network", "network.not",
|
||||
"episode_title", "episode_title.not", "episode_title.begins", "episode_title.ends",
|
||||
"episode_added", "episode_added.not", "episode_added.before", "episode_added.after",
|
||||
"episode_originally_available", "episode_originally_available.not",
|
||||
"episode_originally_available.before", "episode_originally_available.after",
|
||||
"episode_air_date", "episode_air_date.not",
|
||||
"episode_air_date.before", "episode_air_date.after",
|
||||
"episode_year", "episode_year.not", "episode_year.gt", "episode_year.gte", "episode_year.lt","episode_year.lte",
|
||||
"episode_user_rating.gt", "episode_user_rating.gte", "episode_user_rating.lt","episode_user_rating.lte",
|
||||
"episode_plays.gt", "episode_plays.gte", "episode_plays.lt", "episode_plays.lte"
|
||||
|
@ -225,6 +240,7 @@ movie_smart_sorts = {
|
|||
"title.asc": "titleSort", "title.desc": "titleSort%3Adesc",
|
||||
"year.asc": "year", "year.desc": "year%3Adesc",
|
||||
"originally_available.asc": "originallyAvailableAt", "originally_available.desc": "originallyAvailableAt%3Adesc",
|
||||
"release.asc": "originallyAvailableAt", "release.desc": "originallyAvailableAt%3Adesc",
|
||||
"critic_rating.asc": "rating", "critic_rating.desc": "rating%3Adesc",
|
||||
"audience_rating.asc": "audienceRating", "audience_rating.desc": "audienceRating%3Adesc",
|
||||
"user_rating.asc": "userRating", "user_rating.desc": "userRating%3Adesc",
|
||||
|
@ -238,6 +254,7 @@ show_smart_sorts = {
|
|||
"title.asc": "titleSort", "title.desc": "titleSort%3Adesc",
|
||||
"year.asc": "year", "year.desc": "year%3Adesc",
|
||||
"originally_available.asc": "originallyAvailableAt", "originally_available.desc": "originallyAvailableAt%3Adesc",
|
||||
"release.asc": "originallyAvailableAt", "release.desc": "originallyAvailableAt%3Adesc",
|
||||
"critic_rating.asc": "rating", "critic_rating.desc": "rating%3Adesc",
|
||||
"audience_rating.asc": "audienceRating", "audience_rating.desc": "audienceRating%3Adesc",
|
||||
"user_rating.asc": "userRating", "user_rating.desc": "userRating%3Adesc",
|
||||
|
@ -259,6 +276,7 @@ episode_smart_sorts = {
|
|||
"show.desc": "show.titleSort%3Adesc%2Cseason.index%3AnullsLast%2Cepisode.index%3AnullsLast%2Cepisode.originallyAvailableAt%3AnullsLast%2Cepisode.titleSort%2Cepisode.id",
|
||||
"year.asc": "year", "year.desc": "year%3Adesc",
|
||||
"originally_available.asc": "originallyAvailableAt", "originally_available.desc": "originallyAvailableAt%3Adesc",
|
||||
"release.asc": "originallyAvailableAt", "release.desc": "originallyAvailableAt%3Adesc",
|
||||
"critic_rating.asc": "rating", "critic_rating.desc": "rating%3Adesc",
|
||||
"audience_rating.asc": "audienceRating", "audience_rating.desc": "audienceRating%3Adesc",
|
||||
"user_rating.asc": "userRating", "user_rating.desc": "userRating%3Adesc",
|
||||
|
@ -551,9 +569,9 @@ class PlexAPI:
|
|||
else:
|
||||
search, modifier = os.path.splitext(str(search_method).lower())
|
||||
final_search = search_translation[search] if search in search_translation else search
|
||||
if search in ["added", "originally_available"] and modifier == "":
|
||||
if search in ["added", "release", "episode_air_date"] and modifier == "":
|
||||
final_mod = ">>"
|
||||
elif search in ["added", "originally_available"] and modifier == ".not":
|
||||
elif search in ["added", "release", "episode_air_date"] and modifier == ".not":
|
||||
final_mod = "<<"
|
||||
elif search in ["critic_rating", "audience_rating"] and modifier == ".gt":
|
||||
final_mod = "__gt"
|
||||
|
@ -565,12 +583,12 @@ class PlexAPI:
|
|||
|
||||
if search == "duration":
|
||||
search_terms[final_method] = search_data * 60000
|
||||
elif search in ["added", "originally_available"] and modifier in ["", ".not"]:
|
||||
elif search in ["added", "release", "episode_air_date"] and modifier in ["", ".not"]:
|
||||
search_terms[final_method] = f"{search_data}d"
|
||||
else:
|
||||
search_terms[final_method] = search_data
|
||||
|
||||
if search in ["added", "originally_available"] or modifier in [".gt", ".gte", ".lt", ".lte", ".before", ".after"]:
|
||||
if search in ["added", "release", "episode_air_date", "hdr"] or modifier in [".gt", ".gte", ".lt", ".lte", ".before", ".after"]:
|
||||
ors = f"{search_method}({search_data}"
|
||||
else:
|
||||
ors = ""
|
||||
|
@ -722,10 +740,9 @@ class PlexAPI:
|
|||
logger.error(f"{item_type}: {name}{' Advanced' if advanced else ''} Details Update Failed")
|
||||
return False
|
||||
|
||||
def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None, key=None):
|
||||
def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None):
|
||||
updated = False
|
||||
if key is None:
|
||||
key = f"{attr}s"
|
||||
key = builder.filter_translation[attr] if attr in builder.filter_translation else attr
|
||||
if add_tags or remove_tags or sync_tags:
|
||||
item_tags = [item_tag.tag for item_tag in getattr(obj, key)]
|
||||
input_tags = []
|
||||
|
|
Loading…
Reference in a new issue