Development of an internal social media platform with personalised dashboards for students
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.

models.py 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. from __future__ import unicode_literals
  2. from django.contrib.contenttypes.fields import GenericForeignKey
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.db import IntegrityError, models, transaction
  5. from django.db.models.query import QuerySet
  6. from django.template.defaultfilters import slugify as default_slugify
  7. from django.utils.encoding import python_2_unicode_compatible
  8. from django.utils.translation import ugettext
  9. from django.utils.translation import ugettext_lazy as _
  10. try:
  11. from unidecode import unidecode
  12. except ImportError:
  13. def unidecode(tag):
  14. return tag
  15. @python_2_unicode_compatible
  16. class TagBase(models.Model):
  17. name = models.CharField(verbose_name=_('Name'), unique=True, max_length=100)
  18. slug = models.SlugField(verbose_name=_('Slug'), unique=True, max_length=100)
  19. def __str__(self):
  20. return self.name
  21. def __gt__(self, other):
  22. return self.name.lower() > other.name.lower()
  23. def __lt__(self, other):
  24. return self.name.lower() < other.name.lower()
  25. class Meta:
  26. abstract = True
  27. def save(self, *args, **kwargs):
  28. if self._state.adding and not self.slug:
  29. self.slug = self.slugify(self.name)
  30. from django.db import router
  31. using = kwargs.get("using") or router.db_for_write(
  32. type(self), instance=self)
  33. # Make sure we write to the same db for all attempted writes,
  34. # with a multi-master setup, theoretically we could try to
  35. # write and rollback on different DBs
  36. kwargs["using"] = using
  37. # Be oportunistic and try to save the tag, this should work for
  38. # most cases ;)
  39. try:
  40. with transaction.atomic(using=using):
  41. res = super(TagBase, self).save(*args, **kwargs)
  42. return res
  43. except IntegrityError:
  44. pass
  45. # Now try to find existing slugs with similar names
  46. slugs = set(
  47. self.__class__._default_manager
  48. .filter(slug__startswith=self.slug)
  49. .values_list('slug', flat=True)
  50. )
  51. i = 1
  52. while True:
  53. slug = self.slugify(self.name, i)
  54. if slug not in slugs:
  55. self.slug = slug
  56. # We purposely ignore concurrecny issues here for now.
  57. # (That is, till we found a nice solution...)
  58. return super(TagBase, self).save(*args, **kwargs)
  59. i += 1
  60. else:
  61. return super(TagBase, self).save(*args, **kwargs)
  62. def slugify(self, tag, i=None):
  63. slug = default_slugify(unidecode(tag))
  64. if i is not None:
  65. slug += "_%d" % i
  66. return slug
  67. class Tag(TagBase):
  68. class Meta:
  69. verbose_name = _("Tag")
  70. verbose_name_plural = _("Tags")
  71. app_label = 'taggit'
  72. @python_2_unicode_compatible
  73. class ItemBase(models.Model):
  74. def __str__(self):
  75. return ugettext("%(object)s tagged with %(tag)s") % {
  76. "object": self.content_object,
  77. "tag": self.tag
  78. }
  79. class Meta:
  80. abstract = True
  81. @classmethod
  82. def tag_model(cls):
  83. field = cls._meta.get_field('tag')
  84. return field.remote_field.model
  85. @classmethod
  86. def tag_relname(cls):
  87. field = cls._meta.get_field('tag')
  88. return field.remote_field.related_name
  89. @classmethod
  90. def lookup_kwargs(cls, instance):
  91. return {
  92. 'content_object': instance
  93. }
  94. @classmethod
  95. def bulk_lookup_kwargs(cls, instances):
  96. return {
  97. "content_object__in": instances,
  98. }
  99. class TaggedItemBase(ItemBase):
  100. tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE)
  101. class Meta:
  102. abstract = True
  103. @classmethod
  104. def tags_for(cls, model, instance=None, **extra_filters):
  105. kwargs = extra_filters or {}
  106. if instance is not None:
  107. kwargs.update({
  108. '%s__content_object' % cls.tag_relname(): instance
  109. })
  110. return cls.tag_model().objects.filter(**kwargs)
  111. kwargs.update({
  112. '%s__content_object__isnull' % cls.tag_relname(): False
  113. })
  114. return cls.tag_model().objects.filter(**kwargs).distinct()
  115. class CommonGenericTaggedItemBase(ItemBase):
  116. content_type = models.ForeignKey(
  117. ContentType,
  118. on_delete=models.CASCADE,
  119. verbose_name=_('Content type'),
  120. related_name="%(app_label)s_%(class)s_tagged_items"
  121. )
  122. content_object = GenericForeignKey()
  123. class Meta:
  124. abstract = True
  125. @classmethod
  126. def lookup_kwargs(cls, instance):
  127. return {
  128. 'object_id': instance.pk,
  129. 'content_type': ContentType.objects.get_for_model(instance)
  130. }
  131. @classmethod
  132. def bulk_lookup_kwargs(cls, instances):
  133. if isinstance(instances, QuerySet):
  134. # Can do a real object_id IN (SELECT ..) query.
  135. return {
  136. "object_id__in": instances,
  137. "content_type": ContentType.objects.get_for_model(instances.model),
  138. }
  139. else:
  140. # TODO: instances[0], can we assume there are instances.
  141. return {
  142. "object_id__in": [instance.pk for instance in instances],
  143. "content_type": ContentType.objects.get_for_model(instances[0]),
  144. }
  145. @classmethod
  146. def tags_for(cls, model, instance=None, **extra_filters):
  147. ct = ContentType.objects.get_for_model(model)
  148. kwargs = {
  149. "%s__content_type" % cls.tag_relname(): ct
  150. }
  151. if instance is not None:
  152. kwargs["%s__object_id" % cls.tag_relname()] = instance.pk
  153. if extra_filters:
  154. kwargs.update(extra_filters)
  155. return cls.tag_model().objects.filter(**kwargs).distinct()
  156. class GenericTaggedItemBase(CommonGenericTaggedItemBase):
  157. object_id = models.IntegerField(verbose_name=_('Object id'), db_index=True)
  158. class Meta:
  159. abstract = True
  160. class GenericUUIDTaggedItemBase(CommonGenericTaggedItemBase):
  161. object_id = models.UUIDField(verbose_name=_('Object id'), db_index=True)
  162. class Meta:
  163. abstract = True
  164. class TaggedItem(GenericTaggedItemBase, TaggedItemBase):
  165. class Meta:
  166. verbose_name = _("Tagged Item")
  167. verbose_name_plural = _("Tagged Items")
  168. app_label = 'taggit'
  169. index_together = [
  170. ["content_type", "object_id"],
  171. ]