Coverage for apis_core/apis_entities/models.py: 69%

68 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-06-25 10:00 +0000

1import functools 

2import logging 

3import re 

4 

5from django.conf import settings 

6from django.contrib.contenttypes.models import ContentType 

7from django.db.models.base import ModelBase 

8from django.db.models.signals import post_save 

9from django.dispatch import receiver 

10from django.urls import NoReverseMatch, reverse 

11 

12from apis_core.apis_metainfo.models import RootObject, Uri 

13from apis_core.utils.settings import apis_base_uri 

14 

15NEXT_PREV = getattr(settings, "APIS_NEXT_PREV", True) 

16 

17logger = logging.getLogger(__name__) 

18 

19 

20class AbstractEntityModelBase(ModelBase): 

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

22 if name == "AbstractEntity": 

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

24 else: 

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

26 if not new_class._meta.ordering: 

27 logger.warning( 

28 f"{name} inherits from AbstractEntity but does not specify 'ordering' in its Meta class. " 

29 "Empty ordering could result in inconsitent results with pagination. " 

30 "Set a ordering or inherit the Meta class from AbstractEntity.", 

31 ) 

32 

33 return new_class 

34 

35 

36class AbstractEntity(RootObject, metaclass=AbstractEntityModelBase): 

37 """ 

38 Abstract super class which encapsulates common logic between the 

39 different entity kinds and provides various methods relating to either 

40 all or one specific entity kind. 

41 

42 Most of the class methods are designed to be used in the subclass as they 

43 are considering contexts which depend on the subclass entity type. 

44 So they are to be understood in that dynamic context. 

45 """ 

46 

47 class Meta: 

48 abstract = True 

49 

50 @classmethod 

51 def get_or_create_uri(cls, uri): 

52 uri = str(uri) 

53 try: 

54 if re.match(r"^[0-9]*$", uri): 

55 p = cls.objects.get(pk=uri) 

56 else: 

57 p = cls.objects.get(uri__uri=uri) 

58 return p 

59 except Exception as e: 

60 print("Found no object corresponding to given uri." + e) 

61 return False 

62 

63 # TODO 

64 @classmethod 

65 def get_entity_list_filter(cls): 

66 return None 

67 

68 @functools.cached_property 

69 def get_prev_id(self): 

70 if NEXT_PREV: 

71 prev_instance = ( 

72 type(self) 

73 .objects.filter(id__lt=self.id) 

74 .order_by("-id") 

75 .only("id") 

76 .first() 

77 ) 

78 if prev_instance is not None: 

79 return prev_instance.id 

80 return False 

81 

82 @functools.cached_property 

83 def get_next_id(self): 

84 if NEXT_PREV: 

85 next_instance = ( 

86 type(self) 

87 .objects.filter(id__gt=self.id) 

88 .order_by("id") 

89 .only("id") 

90 .first() 

91 ) 

92 if next_instance is not None: 

93 return next_instance.id 

94 return False 

95 

96 

97@receiver(post_save, dispatch_uid="create_default_uri") 

98def create_default_uri(sender, instance, created, raw, using, update_fields, **kwargs): 

99 # disable the handler during fixture loading 

100 if raw: 

101 return 

102 create_default_uri = getattr(settings, "CREATE_DEFAULT_URI", True) 

103 skip_default_uri = getattr(instance, "skip_default_uri", False) 

104 if create_default_uri and not skip_default_uri: 

105 if isinstance(instance, AbstractEntity) and created: 

106 base = apis_base_uri().strip("/") 

107 try: 

108 route = reverse("GetEntityGenericRoot", kwargs={"pk": instance.pk}) 

109 except NoReverseMatch: 

110 route = reverse( 

111 "apis_core:GetEntityGeneric", kwargs={"pk": instance.pk} 

112 ) 

113 uri = f"{base}{route}" 

114 content_type = ContentType.objects.get_for_model(instance) 

115 Uri.objects.create( 

116 uri=uri, 

117 content_type=content_type, 

118 object_id=instance.id, 

119 )