You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
4.2 KiB

  1. from django.contrib.auth.models import User
  2. from django.core.exceptions import ValidationError
  3. from django.db import models
  4. from .managers import LabelManager, CategoryManager, SpanManager, TextLabelManager
  5. from api.models import Project
  6. from examples.models import Example
  7. from label_types.models import CategoryType, SpanType, RelationType
  8. class Label(models.Model):
  9. objects = LabelManager()
  10. prob = models.FloatField(default=0.0)
  11. manual = models.BooleanField(default=False)
  12. user = models.ForeignKey(User, on_delete=models.CASCADE)
  13. created_at = models.DateTimeField(auto_now_add=True)
  14. updated_at = models.DateTimeField(auto_now=True)
  15. class Meta:
  16. abstract = True
  17. class Category(Label):
  18. objects = CategoryManager()
  19. example = models.ForeignKey(
  20. to=Example,
  21. on_delete=models.CASCADE,
  22. related_name='categories'
  23. )
  24. label = models.ForeignKey(to=CategoryType, on_delete=models.CASCADE)
  25. class Meta:
  26. unique_together = (
  27. 'example',
  28. 'user',
  29. 'label'
  30. )
  31. class Span(Label):
  32. objects = SpanManager()
  33. example = models.ForeignKey(
  34. to=Example,
  35. on_delete=models.CASCADE,
  36. related_name='spans'
  37. )
  38. label = models.ForeignKey(to=SpanType, on_delete=models.CASCADE)
  39. start_offset = models.IntegerField()
  40. end_offset = models.IntegerField()
  41. def validate_unique(self, exclude=None):
  42. allow_overlapping = getattr(self.example.project, 'allow_overlapping', False)
  43. is_collaborative = self.example.project.collaborative_annotation
  44. if allow_overlapping:
  45. super().validate_unique(exclude=exclude)
  46. return
  47. overlapping_span = Span.objects.exclude(id=self.id).filter(example=self.example).filter(
  48. models.Q(start_offset__gte=self.start_offset, start_offset__lt=self.end_offset) |
  49. models.Q(end_offset__gt=self.start_offset, end_offset__lte=self.end_offset) |
  50. models.Q(start_offset__lte=self.start_offset, end_offset__gte=self.end_offset)
  51. )
  52. if is_collaborative:
  53. if overlapping_span.exists():
  54. raise ValidationError('This overlapping is not allowed in this project.')
  55. else:
  56. if overlapping_span.filter(user=self.user).exists():
  57. raise ValidationError('This overlapping is not allowed in this project.')
  58. def save(self, force_insert=False, force_update=False, using=None,
  59. update_fields=None):
  60. self.full_clean()
  61. super().save(force_insert, force_update, using, update_fields)
  62. def is_overlapping(self, other: 'Span'):
  63. return (other.start_offset <= self.start_offset < other.end_offset) or\
  64. (other.start_offset < self.end_offset <= other.end_offset) or\
  65. (self.start_offset < other.start_offset and other.end_offset < self.end_offset)
  66. class Meta:
  67. constraints = [
  68. models.CheckConstraint(check=models.Q(start_offset__gte=0), name='startOffset >= 0'),
  69. models.CheckConstraint(check=models.Q(end_offset__gte=0), name='endOffset >= 0'),
  70. models.CheckConstraint(check=models.Q(start_offset__lt=models.F('end_offset')), name='start < end')
  71. ]
  72. class TextLabel(Label):
  73. objects = TextLabelManager()
  74. example = models.ForeignKey(
  75. to=Example,
  76. on_delete=models.CASCADE,
  77. related_name='texts'
  78. )
  79. text = models.TextField()
  80. def is_same_text(self, other: 'TextLabel'):
  81. return self.text == other.text
  82. class Meta:
  83. unique_together = (
  84. 'example',
  85. 'user',
  86. 'text'
  87. )
  88. class Relation(models.Model):
  89. annotation_id_1 = models.IntegerField()
  90. annotation_id_2 = models.IntegerField()
  91. type = models.ForeignKey(RelationType, related_name='annotation_relations', on_delete=models.CASCADE)
  92. timestamp = models.DateTimeField()
  93. user = models.ForeignKey(User, related_name='annotation_relations', on_delete=models.CASCADE)
  94. project = models.ForeignKey(Project, related_name='annotation_relations', on_delete=models.CASCADE)
  95. def __str__(self):
  96. return self.__dict__.__str__()
  97. class Meta:
  98. unique_together = ('annotation_id_1', 'annotation_id_2', 'type', 'project')