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

35 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-10-10 13:36 +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: subj_model 

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

33 relationcts, 

34 ) 

35 ) 

36 if obj_model is not None: 

37 relationcts = list( 

38 filter( 

39 lambda contenttype: obj_model 

40 is contenttype.model_class().obj_model_type(), 

41 relationcts, 

42 ) 

43 ) 

44 if any_model is not None: 

45 relationcts = list( 

46 filter( 

47 lambda contenttype: any_model 

48 is contenttype.model_class().obj_model_type() 

49 or any_model is contenttype.model_class().subj_model_type(), 

50 relationcts, 

51 ) 

52 ) 

53 if all(combination): 

54 left, right = combination 

55 rels = list( 

56 filter( 

57 lambda contenttype: right is contenttype.model_class().obj_model_type() 

58 and left is contenttype.model_class().subj_model_type(), 

59 relationcts, 

60 ) 

61 ) 

62 rels.extend( 

63 list( 

64 filter( 

65 lambda contenttype: left 

66 is contenttype.model_class().obj_model_type() 

67 and right is contenttype.model_class().subj_model_type(), 

68 relationcts, 

69 ) 

70 ) 

71 ) 

72 relationcts = rels 

73 return set(relationcts) 

74 

75 

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

77 """ 

78 test if a relation points to a target 

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

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

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

82 return the wrong result 

83 """ 

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

85 return True 

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

87 return True 

88 return False 

89 

90 

91@functools.cache 

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

93 """ 

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

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

96 

97 Returns: 

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

99 """ 

100 related_models = set() 

101 for rel in relation_content_types(): 

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

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

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