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

47 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-02-19 16:54 +0000

1from django.contrib.contenttypes.fields import GenericForeignKey 

2from django.contrib.contenttypes.models import ContentType 

3from django.db import models 

4 

5from apis_core.generic.abc import GenericModel 

6 

7 

8class SkosCollectionManager(models.Manager): 

9 def get_by_full_path(self, name: str): 

10 """ 

11 Return a collection specified by its full path, from the root colletion 

12 to the leaf collection, delimited by `|`. I.e. if there is a collection 

13 named `foo` and it has a parent named `bar` and `bar` does not have a 

14 parent, then you can use the string "bar|foo" to get the `foo` collection. 

15 """ 

16 names = name.split("|") 

17 parent = None 

18 while names: 

19 parent = self.get(parent=parent, name=names.pop(0)) 

20 return parent 

21 

22 def by_instance(self, instance): 

23 content_type = ContentType.objects.get_for_model(instance) 

24 scco = SkosCollectionContentObject.objects.filter( 

25 content_type=content_type, object_id=instance.id 

26 ) 

27 return self.get_queryset().filter( 

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

29 ) 

30 

31 

32class SkosCollection(GenericModel, models.Model): 

33 """ 

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

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

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

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

38 

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

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

41 

42 """ 

43 

44 class Meta: 

45 ordering = ["name"] 

46 constraints = [ 

47 models.UniqueConstraint( 

48 fields=( 

49 "name", 

50 "parent", 

51 ), 

52 name="unique_name_parent", 

53 nulls_distinct=False, 

54 violation_error_message="The combination of name and parent collection must be unique", 

55 ), 

56 models.CheckConstraint( 

57 check=~models.Q(name__contains="|"), 

58 name="check_name_pipe", 

59 violation_error_message="The name must not contain the pipe symbol: |", 

60 ), 

61 ] 

62 

63 parent = models.ForeignKey("self", null=True, on_delete=models.CASCADE, blank=True) 

64 name = models.CharField( 

65 max_length=300, 

66 verbose_name="skos:prefLabel", 

67 help_text="Collection label or name", 

68 ) 

69 label_lang = models.CharField( 

70 max_length=3, 

71 blank=True, 

72 default="en", 

73 verbose_name="skos:prefLabel language", 

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

75 ) 

76 creator = models.TextField( 

77 blank=True, 

78 verbose_name="dc:creator", 

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

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

81 ) 

82 contributor = models.TextField( 

83 blank=True, 

84 verbose_name="dc:contributor", 

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

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

87 ) 

88 objects = SkosCollectionManager() 

89 

90 def __str__(self): 

91 return self.name 

92 

93 def children(self): 

94 return SkosCollection.objects.filter(parent=self) 

95 

96 def children_tree_as_list(self): 

97 childtrees = [self] 

98 for child in self.children(): 

99 childtrees.extend(child.children_tree_as_list()) 

100 return childtrees 

101 

102 def add(self, instance: object): 

103 content_type = ContentType.objects.get_for_model(instance) 

104 SkosCollectionContentObject.objects.get_or_create( 

105 collection=self, content_type=content_type, object_id=instance.id 

106 ) 

107 

108 def remove(self, instance: object): 

109 content_type = ContentType.objects.get_for_model(instance) 

110 SkosCollectionContentObject.objects.filter( 

111 collection=self, content_type=content_type, object_id=instance.id 

112 ).delete() 

113 

114 

115class SkosCollectionContentObject(GenericModel, models.Model): 

116 """ 

117 *Throughtable* datamodel to connect collections to arbitrary content 

118 """ 

119 

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

121 

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

123 object_id = models.PositiveIntegerField() 

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

125 

126 def __str__(self): 

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