Coverage for apis_core / relations / utils.py: 54%

35 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-27 05:15 +0000

1import functools 

2 

3from django.contrib.contenttypes.models import ContentType 

4 

5from apis_core.relations.models import Relation 

6 

7 

8def is_relation(ct: ContentType) -> bool: 

9 mc = ct.model_class() 

10 return ( 

11 issubclass(mc, Relation) 

12 and hasattr(mc, "subj_model") 

13 and hasattr(mc, "obj_model") 

14 ) 

15 

16 

17@functools.cache 

18def relation_content_types( 

19 subj_model=None, obj_model=None, any_model=None, combination=(None, None) 

20) -> set[ContentType]: 

21 allcts = list( 

22 filter( 

23 lambda contenttype: contenttype.model_class() is not None, 

24 ContentType.objects.all(), 

25 ) 

26 ) 

27 relationcts = list(filter(lambda contenttype: is_relation(contenttype), allcts)) 

28 if subj_model is not None: 

29 relationcts = list( 

30 filter( 

31 lambda contenttype: ( 

32 subj_model is contenttype.model_class().subj_model_type() 

33 ), 

34 relationcts, 

35 ) 

36 ) 

37 if obj_model is not None: 

38 relationcts = list( 

39 filter( 

40 lambda contenttype: ( 

41 obj_model is contenttype.model_class().obj_model_type() 

42 ), 

43 relationcts, 

44 ) 

45 ) 

46 if any_model is not None: 

47 relationcts = list( 

48 filter( 

49 lambda contenttype: ( 

50 any_model is contenttype.model_class().obj_model_type() 

51 or any_model is contenttype.model_class().subj_model_type() 

52 ), 

53 relationcts, 

54 ) 

55 ) 

56 if all(combination): 

57 left, right = combination 

58 rels = list( 

59 filter( 

60 lambda contenttype: ( 

61 right is contenttype.model_class().obj_model_type() 

62 and left is contenttype.model_class().subj_model_type() 

63 ), 

64 relationcts, 

65 ) 

66 ) 

67 rels.extend( 

68 list( 

69 filter( 

70 lambda contenttype: ( 

71 left is contenttype.model_class().obj_model_type() 

72 and right is contenttype.model_class().subj_model_type() 

73 ), 

74 relationcts, 

75 ) 

76 ) 

77 ) 

78 relationcts = rels 

79 return set(relationcts) 

80 

81 

82def relation_match_target(relation, target: ContentType) -> bool: 

83 """ 

84 test if a relation points to a target 

85 this function should not be cached, because the `forward` attribute 

86 is an annotation that does not seem to be part of the relation, so 

87 if cached, method could be called with another `forward` value and 

88 return the wrong result 

89 """ 

90 if relation.forward and relation.obj_content_type == target: 

91 return True 

92 if not relation.forward and relation.subj_content_type == target: 

93 return True 

94 return False 

95 

96 

97@functools.cache 

98def get_all_relation_subj_and_obj() -> list[ContentType]: 

99 """ 

100 Return the model classes of any model that is in some way 

101 connected to a relation - either as obj or as subj 

102 

103 Returns: 

104 list[ContentType]: A list of unique ContentTypes for related models. 

105 """ 

106 related_models = set() 

107 for rel in relation_content_types(): 

108 related_models.add(rel.model_class().subj_model_type()) 

109 related_models.add(rel.model_class().obj_model_type()) 

110 return [ContentType.objects.get_for_model(item) for item in related_models]