Coverage for apis_core/collections/models.py: 65%

54 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-28 06:34 +0000

1from django.contrib.contenttypes.fields import GenericForeignKey 

2from django.contrib.contenttypes.models import ContentType 

3from django.db import models 

4from django.utils.translation import gettext_lazy as _ 

5 

6from apis_core.generic.abc import GenericModel 

7 

8 

9class SkosCollectionManager(models.Manager): 

10 def by_instance(self, instance): 

11 content_type = ContentType.objects.get_for_model(instance) 

12 scco = SkosCollectionContentObject.objects.filter( 

13 content_type=content_type, object_id=instance.id 

14 ) 

15 return self.get_queryset().filter( 

16 pk__in=scco.values_list("collection", flat=True) 

17 ) 

18 

19 

20class SkosCollection(GenericModel, models.Model): 

21 """ 

22 SKOS collections are labeled and/or ordered groups of SKOS concepts. 

23 Collections are useful where a group of concepts shares something in common, 

24 and it is convenient to group them under a common label, or 

25 where some concepts can be placed in a meaningful order. 

26 

27 Miles, Alistair, and Sean Bechhofer. "SKOS simple knowledge 

28 organization system reference. W3C recommendation (2009)." 

29 

30 """ 

31 

32 class Meta: 

33 ordering = ["name"] 

34 

35 class Config(GenericModel.Config): 

36 overview_section = _("Collections") 

37 

38 name = models.CharField( 

39 max_length=300, 

40 verbose_name="skos:prefLabel", 

41 help_text="Collection label or name", 

42 ) 

43 label_lang = models.CharField( 

44 max_length=3, 

45 blank=True, 

46 default="en", 

47 verbose_name="skos:prefLabel language", 

48 help_text="Language of preferred label given above", 

49 ) 

50 creator = models.TextField( 

51 blank=True, 

52 verbose_name="dc:creator", 

53 help_text="Person or organisation that created this collection" 

54 "If more than one list all using a semicolon ;", 

55 ) 

56 contributor = models.TextField( 

57 blank=True, 

58 verbose_name="dc:contributor", 

59 help_text="Person or organisation that made contributions to the collection" 

60 "If more than one list all using a semicolon ;", 

61 ) 

62 objects = SkosCollectionManager() 

63 

64 def __str__(self): 

65 return self.name 

66 

67 def add(self, instance: object): 

68 content_type = ContentType.objects.get_for_model(instance) 

69 SkosCollectionContentObject.objects.get_or_create( 

70 collection=self, content_type=content_type, object_id=instance.pk 

71 ) 

72 

73 def remove(self, instance: object): 

74 content_type = ContentType.objects.get_for_model(instance) 

75 SkosCollectionContentObject.objects.filter( 

76 collection=self, content_type=content_type, object_id=instance.pk 

77 ).delete() 

78 

79 @property 

80 def parent_collection(self): 

81 content_type = ContentType.objects.get_for_model(self) 

82 sccos = SkosCollectionContentObject.objects.filter( 

83 content_type=content_type, object_id=self.id 

84 ) 

85 if len(sccos) == 1: 

86 return sccos.first().collection 

87 raise SkosCollection.MultipleObjectsReturned( 

88 f'"{self}" is part of multiple collections' 

89 ) 

90 

91 def list(self, content_type=None): 

92 qs = SkosCollectionContentObject.objects.filter(collection=self) 

93 if content_type is not None: 

94 return qs.filter(content_type=content_type) 

95 return qs 

96 

97 def contains(self, instance: object): 

98 content_type = ContentType.objects.get_for_model(instance) 

99 return SkosCollectionContentObject.objects.filter( 

100 collection=self, content_type=content_type, object_id=instance.pk 

101 ).exists() 

102 

103 

104class SkosCollectionContentObject(GenericModel, models.Model): 

105 """ 

106 *Throughtable* datamodel to connect collections to arbitrary content 

107 """ 

108 

109 class Config(GenericModel.Config): 

110 overview_section = _("Collections") 

111 

112 collection = models.ForeignKey(SkosCollection, on_delete=models.CASCADE) 

113 

114 content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 

115 object_id = models.PositiveIntegerField() 

116 content_object = GenericForeignKey("content_type", "object_id") 

117 

118 def __str__(self): 

119 if self.content_object: 

120 return f"{self.content_object} -> {self.collection}" 

121 return super().__str__()