Coverage for apis_core/relations/models.py: 64%

66 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-22 07:51 +0000

1import functools 

2 

3from django.contrib.contenttypes.fields import GenericForeignKey 

4from django.contrib.contenttypes.models import ContentType 

5from django.core.exceptions import ValidationError 

6from django.db import models 

7from django.db.models.base import ModelBase 

8from model_utils.managers import InheritanceManager 

9 

10from apis_core.generic.abc import GenericModel 

11 

12 

13# This ModelBase is simply there to check if the needed attributes 

14# are set in the Relation child classes. 

15class RelationModelBase(ModelBase): 

16 def __new__(metacls, name, bases, attrs): 

17 if name == "Relation": 

18 return super().__new__(metacls, name, bases, attrs) 

19 else: 

20 new_class = super().__new__(metacls, name, bases, attrs) 

21 if not hasattr(new_class, "subj_model"): 

22 raise ValueError( 

23 "%s inherits from Relation and must therefore specify subj_model" 

24 % name 

25 ) 

26 if not hasattr(new_class, "obj_model"): 

27 raise ValueError( 

28 "%s inherits from Relation and must therefore specify obj_model" 

29 % name 

30 ) 

31 

32 return new_class 

33 

34 

35@functools.cache 

36def get_by_natural_key(natural_key: str): 

37 app_label, name = natural_key.lower().split(".") 

38 return ContentType.objects.get_by_natural_key(app_label, name).model_class() 

39 

40 

41class Relation(models.Model, GenericModel, metaclass=RelationModelBase): 

42 subj_content_type = models.ForeignKey( 

43 ContentType, on_delete=models.CASCADE, related_name="relation_subj_set" 

44 ) 

45 subj_object_id = models.PositiveIntegerField() 

46 subj = GenericForeignKey("subj_content_type", "subj_object_id") 

47 obj_content_type = models.ForeignKey( 

48 ContentType, on_delete=models.CASCADE, related_name="relation_obj_set" 

49 ) 

50 obj_object_id = models.PositiveIntegerField() 

51 obj = GenericForeignKey("obj_content_type", "obj_object_id") 

52 

53 objects = InheritanceManager() 

54 

55 def save(self, *args, **kwargs): 

56 if self.subj_content_type: 

57 if self.subj_content_type.model_class() not in self.subj_list(): 

58 raise ValidationError( 

59 f"{self.subj} is not of any type in {self.subj_list()}" 

60 ) 

61 if self.obj_content_type: 

62 if self.obj_content_type.model_class() not in self.obj_list(): 

63 raise ValidationError( 

64 f"{self.obj} is not of any type in {self.obj_list()}" 

65 ) 

66 super().save(*args, **kwargs) 

67 

68 @property 

69 def subj_to_obj_text(self) -> str: 

70 if hasattr(self, "name"): 

71 return f"{self.subj} {self.name()} {self.obj}" 

72 return f"{self.subj} relation to {self.obj}" 

73 

74 @property 

75 def obj_to_subj_text(self) -> str: 

76 if hasattr(self, "reverse_name"): 

77 return f"{self.obj} {self.reverse_name()} {self.subj}" 

78 return f"{self.obj} relation to {self.subj}" 

79 

80 def __str__(self): 

81 return self.subj_to_obj_text 

82 

83 @classmethod 

84 def _get_models(cls, model): 

85 models = model if isinstance(model, list) else [model] 

86 return [ 

87 get_by_natural_key(model) if isinstance(model, str) else model 

88 for model in models 

89 ] 

90 

91 @classmethod 

92 def subj_list(cls) -> list[models.Model]: 

93 return cls._get_models(cls.subj_model) 

94 

95 @classmethod 

96 def obj_list(cls) -> list[models.Model]: 

97 return cls._get_models(cls.obj_model) 

98 

99 @classmethod 

100 def name(cls) -> str: 

101 return cls._meta.verbose_name 

102 

103 @classmethod 

104 def reverse_name(cls) -> str: 

105 return cls._meta.verbose_name + " reverse"