4141 "provider" ,
4242 "tag" ,
4343 "tag_mode" ,
44+ "list" ,
4445)
4546
4647TAG_MODE_CHOICES = {"and" , "or" , "not" }
7273 "provider" : "" ,
7374 "tag" : [],
7475 "tag_mode" : "or" ,
76+ "list" : [],
7577}
7678
7779MAX_RATING = 10.0
@@ -242,6 +244,22 @@ def _payload_getlist(payload, key: str) -> list[str]:
242244 return []
243245
244246
247+ def _valid_linked_list_ids (owner , raw_values : list [str ]) -> list [int ]:
248+ """Return ids from raw_values that are non-smart lists owner can access."""
249+ candidate_ids = {int (value ) for value in raw_values if str (value ).strip ().isdigit ()}
250+ if not candidate_ids or not owner :
251+ return []
252+
253+ from lists .models import CustomList
254+
255+ return list (
256+ CustomList .objects .filter (id__in = candidate_ids , is_smart = False )
257+ .filter (Q (owner = owner ) | Q (collaborators = owner ))
258+ .distinct ()
259+ .values_list ("id" , flat = True ),
260+ )
261+
262+
245263def get_available_media_types (owner ) -> list [str ]:
246264 """Return enabled media types that can participate in smart rules."""
247265 if owner and hasattr (owner , "get_enabled_media_types" ):
@@ -368,6 +386,8 @@ def normalize_rule_payload(payload, owner):
368386 seen_tags .add (key )
369387 deduped_tags .append (value )
370388
389+ list_ids = _valid_linked_list_ids (owner , _payload_getlist (payload , "list" ))
390+
371391 return {
372392 "media_types" : normalized_media_types ,
373393 "status" : normalized_statuses ,
@@ -396,6 +416,7 @@ def normalize_rule_payload(payload, owner):
396416 "provider" : str (_payload_get (payload , "provider" , "" ) or "" ).strip (),
397417 "tag" : deduped_tags ,
398418 "tag_mode" : tag_mode ,
419+ "list" : list_ids ,
399420 }
400421
401422
@@ -836,6 +857,21 @@ def _resolve_tag_id_sets(
836857 return set ().union (* per_tag_id_sets ), None
837858
838859
860+ def _resolve_list_membership_item_ids (list_ids : list [int ]) -> set [int ]:
861+ """Return the union of item ids belonging to the given linked lists."""
862+ if not list_ids :
863+ return set ()
864+
865+ from lists .models import CustomListItem
866+
867+ return set (
868+ CustomListItem .objects .filter (custom_list_id__in = list_ids ).values_list (
869+ "item_id" ,
870+ flat = True ,
871+ ),
872+ )
873+
874+
839875def collect_matching_item_ids (
840876 owner ,
841877 normalized_rules : dict ,
@@ -981,6 +1017,8 @@ def _tag_filter_excludes(item_id: int) -> bool:
9811017 continue
9821018 matched_ids .add (item .id )
9831019
1020+ matched_ids |= _resolve_list_membership_item_ids (normalized_rules .get ("list" ) or [])
1021+
9841022 return matched_ids
9851023
9861024
@@ -995,6 +1033,16 @@ def item_matches_rules(
9951033 if not owner or not item :
9961034 return False
9971035
1036+ list_ids = normalized_rules .get ("list" ) or []
1037+ if list_ids :
1038+ from lists .models import CustomListItem
1039+
1040+ if CustomListItem .objects .filter (
1041+ custom_list_id__in = list_ids ,
1042+ item_id = item .id ,
1043+ ).exists ():
1044+ return True
1045+
9981046 target_media_types = _target_media_types (
9991047 owner , normalized_rules .get ("media_types" , [])
10001048 )
@@ -1151,6 +1199,7 @@ def build_rule_filter_data(
11511199 * ,
11521200 include_collection_only_untracked : bool = False ,
11531201 precomputed_tags : list [str ] | None = None ,
1202+ include_list_options : bool = True ,
11541203):
11551204 """Build menu options for smart-rule filters from matched candidate media."""
11561205 target_media_types = _target_media_types (owner , media_types )
@@ -1345,4 +1394,15 @@ def build_rule_filter_data(
13451394 .order_by ("name" )
13461395 )
13471396
1397+ filter_data ["lists" ] = []
1398+ if include_list_options :
1399+ from lists .models import CustomList
1400+
1401+ filter_data ["lists" ] = [
1402+ {"id" : custom_list .id , "label" : custom_list .name }
1403+ for custom_list in CustomList .objects .get_user_lists (owner )
1404+ .filter (is_smart = False )
1405+ .order_by ("name" )
1406+ ]
1407+
13481408 return filter_data
0 commit comments