# MG_GPL_HEADER_BEGIN
#
# This file is part of Media Gallery, GNU GPLv2.
#
# Media Gallery is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# Media Gallery is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Media Gallery; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
# MG_GPL_HEADER_END
__author__ = "Media Gallery contributors <mg@lists.ssji.net>"
__copyright__ = "(c) 2010"
__credits__ = ["Dimitri Refauvelet", "Nicolas Pouillon"]
from django.db import models
from django.db import transaction
from django.db.models import F, Q
from django.db.models.signals import post_save, pre_delete
from django.contrib.auth.models import User
from mg.core.element.models import *
__all__ = [
'Presence', 'Directory', 'Reacheable',
]
class Presence(models.Model):
[docs] """
This is a Many2Many "through" table, adding an ordering constraint for simple
presence of an Element in a Directory
"""
directory = models.ForeignKey('Directory', related_name = 'directories')
element = models.ForeignKey(Element)
order = models.IntegerField(db_index = True)
class Meta:
unique_together = (
('directory', 'element'),
('directory', 'order'),
)
ordering = ("directory", "order")
def __unicode__(self):
return u'%s/%s'%(self.directory.name, self.element.name)
class Directory(Element):
[docs] """
This is a Directory of Elements, and it is itself an element.
"""
children = models.ManyToManyField(Element,
through = Presence,
related_name = "parents")
end = models.DateTimeField(null = True, blank = True)
@property
def children_elements(self):
[docs] return self.children.all().order_by('presence__order')
class Meta:
verbose_name_plural = "Directories"
def append(self, elem, offset = 0):
[docs] """
Adds an element to the direcotry
:param elem: Element to add to this directory
"""
om = Presence.objects\
.filter(directory = self)\
.order_by("-order")
if om:
omm = om[0].order + 1
else:
omm = 1
p = Presence(directory = self, element = elem, order = omm+offset)
p.save()
def remove(self, elem):
[docs] """
Removes an element form the Directory
:param elem: Element to remove from this directory
"""
Presence.objects \
.filter(directory = self, element = elem) \
.delete()
def rm_rf(self):
"""
Deletes all children of this directory (and itself), deleting
elements not referenced by other directories.
"""
for c in self.children.all():
if c.parents.all().count() == 1:
if c.type == "directory":
c.actual_element.rm_rf()
else:
c.delete()
self.delete()
def thumbnail_get(self):
try:
r = self.subtree_element_get_random()
except IndexError, e:
return None
return r.thumbnail_get()
def subtree_element_get_random(self):
[docs] filters = (
Q(parents = self)
|Q(parents__reacheable_parents__arity__gt = 0,
parents__reacheable_parents__parent = self)
)
return Element.objects.filter(filters)\
.exclude(type = "directory").order_by('?')[0]
Element.register_type("directory", Directory)
class ReacheableManager(models.Manager):
"""
Manager for Reacheable model, which only returns reacheabilities
where at least one link exists.
"""
def get_query_set(self):
return super(ReacheableManager, self).get_query_set().filter(arity__gt = 0)
class Reacheable(models.Model):
[docs] """
Reacheability is a table associating a directory to another
directory, with the constraint there is at least one path from
"parent" to "child" throught the tree.
"""
objects = ReacheableManager()
any = models.Manager()
parent = models.ForeignKey(Directory, related_name = "reacheable_children")
child = models.ForeignKey(Directory, related_name = "reacheable_parents")
arity = models.SmallIntegerField(db_index = True)
class Meta:
unique_together = (
('parent', 'child'),
)
def __unicode__(self):
return "%s -> %s %d"%(self.parent, self.child, self.arity)
@classmethod
@transaction.autocommit
def ref(cls, parent, child):
assert isinstance(parent, Directory), parent
assert isinstance(child, Directory), child
if 0 == cls.any\
.filter(parent = parent, child = child)\
.update(arity = F('arity')+1):
cls(parent = parent, child = child, arity = 1).save()
@classmethod
@transaction.autocommit
def unref(cls, parent, child):
assert isinstance(parent, Directory), parent
assert isinstance(child, Directory), child
assert 1 == cls.any\
.filter(parent = parent, child = child)\
.update(arity = F('arity')-1)
@classmethod
def presence_add(cls, sender, instance, created, **kwargs):
if not instance.element.type == "directory" or not created:
return
left = instance.directory
right = instance.element.directory
assert isinstance(left, Directory), left
assert isinstance(right, Directory), right
lefts = list(Directory.objects.filter(reacheable_children__child = left))
rights = list(Directory.objects.filter(reacheable_parents__parent = right))
for lefter in lefts:
cls.ref(lefter, right)
for righter in rights:
cls.ref(lefter, righter)
for righter in rights:
cls.ref(left, righter)
cls.ref(left, right)
@classmethod
def presence_rem(cls, sender, instance, **kwargs):
if not instance.element.type == "directory":
return
left = instance.directory
right = instance.element.directory
for lefter in left.reacheable_parents.all():
for righter in Directory.objects.filter(reacheable_parents = right):
cls.unref(lefter, righter)
cls.unref(left, right)
post_save.connect(Reacheable.presence_add, sender = Presence)
pre_delete.connect(Reacheable.presence_rem, sender = Presence)