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.

115 lines
4.5 KiB

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