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

145 lines
4.2 KiB
Python

"""
Mixin-models, with minimal example implementations.
"""
from __future__ import unicode_literals
from django.utils.encoding import force_text
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes import fields as generic
class UnorderedTreeManager(models.Manager):
def roots(self):
"Return a list of tree roots, nodes having no parents"
return self.get_queryset().filter(part_of__isnull=True)
class UnorderedTreeMixin(models.Model):
part_of = models.ForeignKey(
'self',
on_delete=models.CASCADE,
blank=True,
null=True,
default=None,
related_name='has_%(class)s_children'
)
path = models.CharField(max_length=255, blank=True, default='')
_sep = '/'
class Meta:
abstract = True
def save(self, *args, **kwargs):
if not self.id:
super(UnorderedTreeMixin, self).save(*args, **kwargs)
self._set_path()
super(UnorderedTreeMixin, self).save(*args, **kwargs)
def _set_path(self):
if self.part_of:
self.path = "%s%i/" % (self.part_of.path, self.id)
else:
self.path = "%i/" % self.id
@property
def level(self):
"Count how far down in the tree self is"
return force_text(self.path).count(self._sep)
@classmethod
def roots(cls):
"Get all roots, nodes without parents"
return cls.tree.roots()
@classmethod
def get_path_for_tree(cls, treeobj):
"Get all ancestors in a path"
return [cls.tree.get(id=p) for p in force_text(treeobj.path).split(treeobj._sep) if p]
def get_path(self):
"Get all ancestors, ordered from root to self"
return self.get_path_for_tree(self)
@classmethod
def get_descendants_for_tree(cls, treeobj):
"Get all descendants in no particular order"
return cls.tree.filter(path__startswith=treeobj.path).exclude(id=treeobj.id)
def descendants(self):
"Get all descendants in no particular order"
return self.get_descendants_for_tree(self)
def parent(self):
"Get parent of self"
return self.part_of
def siblings(self):
"Get all nodes with the same parent"
if not self.part_of: return []
return [p for p in self.part_of.descendants() if p.level == self.level]
def children(self):
"Get nodes that have self as parent"
return [p for p in self.descendants() if p.level == self.level + 1]
def is_sibling_of(self, node):
"Check if <node> has the same parent as self"
return self.part_of == node.part_of
def is_child_of(self, node):
"Check if <node> is the parent of self"
return self.part_of == node
def is_root(self):
"""Check if self is a root. Roots have no parents"""
return not bool(self.part_of)
def is_leaf(self):
"""Check if self is a leaf. Leaves have no descendants"""
return self.descendants().count() == 0
class AbstractText(models.Model):
"Denormalized storage of text"
DEFAULT_TYPE = 'plaintext'
text = models.TextField()
text_formatted = models.TextField(editable=False)
text_type = models.CharField(max_length=64, default=DEFAULT_TYPE)
class Meta:
abstract = True
def save(self, formatters=None, *args, **kwargs):
if self.text_type == self.DEFAULT_TYPE:
self.text_formatted = self.text
else:
if formatters:
self.text_formatted = formatters(self.text_type)
super(AbstractText, self).save(*args, **kwargs)
class GenericForeignKeyAbstractModel(models.Model):
"""
An abstract base class for models with one GenericForeignKey
"""
# Content-object field
content_type = models.ForeignKey(
'contenttypes.ContentType',
on_delete=models.CASCADE,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s",
)
object_pk = models.TextField(_('object ID'))
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
class Meta:
abstract = True