Coverage for lino/mixins/sequenced.py : 32%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: UTF-8 -*- # Copyright 2009-2015 Luc Saffre # License: BSD (see file COPYING for details) :class:`Hierarchical`.
A `Sequenced` is something which has a sequence number and thus a sort order which can be manipulated by the user using actions :class:`MoveUp` and :class:`MoveDown`.
:class:`Hierarchical` is a :class:`Sequenced` with a `parent` field.
.. autosummary::
"""
"""Move current row one upwards. This action is available on any :class:`Sequenced` object as :attr:`Sequenced.move_up`.
"""
# label = _("Up") # label = "\u2191" thin arrow up # label = "\u25b2" # triangular arrow up # label = "↑" # # icon_name = 'arrow_up' #~ icon_file = 'arrow_up.png'
if ar.data_iterator is None: return False if not super(MoveUp, self).get_action_permission(ar, obj, state): return False #~ logger.info("20130927 %r", ar.data_iterator.__class__) if ar.data_iterator.count() == 0: return False if ar.data_iterator[0] == obj: return False return True
obj = ar.selected_rows[0] obj.swap_seqno(ar, -1) #~ obj.move_up() kw = dict() #~ kw.update(refresh=True) kw.update(refresh_all=True) kw.update(message=_("Moved up.")) ar.success(**kw)
"""Move current row one downwards. This action is available on any :class:`Sequenced` object as :attr:`Sequenced.move_down`.
""" # label = _("Down") # label = "\u25bc" # triangular arrow down # label = "\u2193" # icon_name = 'arrow_down' #~ icon_file = 'arrow_down.png'
if ar.data_iterator is None: return False if not super(MoveDown, self).get_action_permission(ar, obj, state): return False if ar.data_iterator.count() == 0: return False if ar.data_iterator[ar.data_iterator.count() - 1] == obj: return False #~ if obj.__class__.__name__=='Entry' and obj.seqno == 25: #~ print 20130706, ar.data_iterator.count(), ar.data_iterator return True
obj = ar.selected_rows[0] obj.swap_seqno(ar, 1) #~ obj.move_down() kw = dict() #~ kw.update(refresh=True) kw.update(refresh_all=True) kw.update(message=_("Moved down.")) ar.success(**kw)
"""Duplicate this row.""" obj = ar.selected_rows[0]
#~ print '20120605 duplicate', self.seqno, self.account seqno = obj.seqno qs = obj.get_siblings().filter(seqno__gte=seqno).reverse() if qs is None: raise Exception( "20121227 TODO: Tried to duplicate a root element?") for s in qs: #~ print '20120605 duplicate inc', s.seqno, s.account s.seqno += 1 s.save() kw.update(seqno=seqno) return super(DuplicateSequenced, self).run_from_code(ar, **kw)
"""Mixin for models that have a field :attr:`seqno` containing a "sequence number".
.. attribute:: seqno
The sequence number of this item with its parent.
.. method:: duplicate
Create a duplicate of this object and insert the new object below this one.
.. attribute:: move_up
Exchange the :attr:`seqno` of this item and the previous item.
.. attribute:: move_down
Exchange the :attr:`seqno` of this item and the next item.
.. attribute:: move_buttons
Displays buttons for certain actions on this row:
- :attr:`move_up` and :attr:`move_down` - duplicate
"""
"""The :class:`DuplicateSequenced` action for this object.
"""
"""The :class:`MoveUp` action on this object.
"""
"""The :class:`MoveDown` action on this object.
"""
return str(_("Row # %s") % self.seqno)
"""Return a Django Queryset with all siblings of this, or `None` if this is a root element which cannot have any siblings.
Siblings are all objects that belong to a same sequence. This is needed for automatic management of the `seqno` field.
The queryset will of course include `self`. The default implementation uses a global sequencing by returning all objects of `self`'s model.
A common case for overriding this method is when numbering restarts for each master. For example if you have a master model `Product` and a sequenced slave model `Property` with a ForeignKey field `product` which points to the Product, then you'll define::
class Property(dd.Sequenced):
def get_siblings(self): return Property.objects.filter( product=self.product).order_by('seqno')
Overridden e.g. in :class:`lino_xl.lib.thirds.models.Third` or :class:`lino_welfare.modlib.debts.models.Entry`.
""" return self.__class__.objects.order_by('seqno')
""" Initialize `seqno` to the `seqno` of eldest sibling + 1. """ qs = self.get_siblings() if qs is None: self.seqno = 0 else: n = qs.count() if n == 0: self.seqno = 1 else: last = qs[n - 1] self.seqno = last.seqno + 1
if self.seqno is None: self.set_seqno() # if hasattr(self, 'amount'): # logger.info("20151117 Sequenced.full_clean a %s", self.amount) # logger.info("20151117 %s", self.__class__.mro()) super(Sequenced, self).full_clean(*args, **kw) # if hasattr(self, 'amount'): # logger.info("20151117 Sequenced.full_clean b %s", self.amount)
""" Move this row "up or down" within its siblings """ #~ qs = self.get_siblings() qs = ar.data_iterator if qs is None: return nav = AttrDict(**navinfo(qs, self)) if not nav.recno: return new_recno = nav.recno + offset if new_recno <= 0: return if new_recno > qs.count(): return other = qs[new_recno - 1] prev_seqno = other.seqno other.seqno = self.seqno self.seqno = prev_seqno self.save() other.save()
def move_buttons(obj, ar): if ar is None: return '' actor = ar.actor l = [] state = None # TODO: support a possible state? for n in ('move_up', 'move_down', 'duplicate'): ba = actor.get_action_by_name(n) if ba.get_row_permission(ar, obj, state): l.append(ar.renderer.action_button(obj, ar, ba)) l.append(' ') return E.p(*l)
"""Abstract model mixin for things that have a "parent" and "siblings".
"""
verbose_name=_("Parent"), null=True, blank=True, related_name='children')
if self.parent: return self.parent.children.all() return self.__class__.objects.filter(parent__isnull=True)
#~ def save(self, *args, **kwargs): #~ super(Hierarchical, self).save(*args, **kwargs) p = self.parent while p is not None: if p == self: raise ValidationError("Cannot be your own ancestor") p = p.parent super(Hierarchical, self).full_clean(*args, **kwargs)
if self == other: return True p = self.parent while p is not None: if p == other: return True p = p.parent
rv = [] p = self.parent while p is not None: rv.insert(p) p = p.parent return rv
"""A top-level project is its own root.""" obj = self tree = [obj] while obj.parent is not None: obj = obj.parent if obj in tree: raise Exception("Circular parent") tree.insert(0, obj) return tree
# TODO: go deeper but check for circular references clan = set([self]) l1 = self.__class__.objects.filter(parent=self) if l1.count() == 0: return clan clan |= set(l1) l2 = self.__class__.objects.filter(parent__in=l1) if l2.count() == 0: return clan clan |= set(l2) l3 = self.__class__.objects.filter(parent__in=l2) if l3.count() == 0: return clan clan |= set(l3) # print 20150421, projects return clan
|