def __init__(self, model, **kw): parameters = dict( #~ merge_from=models.ForeignKey(model,verbose_name=_("Merge...")), merge_to=fields.ForeignKey(model, verbose_name=_("into..."), blank=False, null=False), reason=models.CharField(_("Reason"), max_length=100) #~ notify=models.BooleanField(_("Send notifications")) ) keep_volatiles = [] # logger.info("20160621 MergeAction for %s", model) # logger.info("20160621 MergeAction for %s : _lino_ddh.fklist is %s", # model, model._lino_ddh.fklist) for m, fk in traverse_ddh_fklist(model): if fk.name in m.allow_cascaded_delete: fieldname = full_model_name(m, '_') if fieldname not in keep_volatiles: keep_volatiles.append(fieldname) parameters[fieldname] = models.BooleanField( m._meta.verbose_name_plural, default=False) # logger.info( # "20160621 %r in %r", fk.name, m.allow_cascaded_delete) layout = dict() if len(keep_volatiles) == 0: width = 50 main = """ merge_to reason """ else: COLCOUNT = 2 width = 70 if len(keep_volatiles) > COLCOUNT: tpl = '' for i, name in enumerate(keep_volatiles): if i % COLCOUNT == 0: tpl += '\n' else: tpl += ' ' tpl += name else: tpl = ' '.join(keep_volatiles) main = """ merge_to keep_volatiles reason """ layout.update(keep_volatiles=layouts.Panel( tpl, label=_("Also reassign volatile related objects"))) layout.update(window_size=(width, 'auto')) kw.update(parameters=parameters, params_layout=layouts.Panel(main, **layout)) super(MergeAction, self).__init__(**kw)
class ProjectRelated(model.Model): """ Mixin for models that are related to a "project". This adds a field named `project` and related default behaviour. A project in this context means what the users consider "the central most important model that is used to classify most other things". For example in :ref:`avanti` the "project" is a Client while in :ref:`tera` it is a therapy. The application's project model is specified in :attr:`lino.core.site.Site.project_model`. .. attribute:: project Pointer to the project to which this object is related. If the application's :attr:`project_model <lino.core.site.Site.project_model>` is empty, the :attr:`project` field will be a :class:`DummyField <lino.core.fields.DummyField>`. """ class Meta(object): abstract = True if settings.SITE.project_model: project = fields.ForeignKey( settings.SITE.project_model, blank=True, null=True, related_name="%(app_label)s_%(class)s_set_by_project", ) else: project = fields.DummyField('project') def get_related_project(self): if settings.SITE.project_model: return self.project def summary_row(self, ar, **kw): s = [ar.obj2html(self)] if settings.SITE.project_model: # if self.project and not dd.has_fk(rr,'project'): if self.project: # s += " (" + ui.obj2html(self.project) + ")" s += [" (", ar.obj2html(self.project), ")"] return s def update_owned_instance(self, controllable): """ When a :class:`project-related <ProjectRelated>` object controls another project-related object, then the controlled automatically inherits the `project` of its controller. """ if isinstance(controllable, ProjectRelated): controllable.project = self.project super(ProjectRelated, self).update_owned_instance(controllable) def get_mailable_recipients(self): if isinstance(self.project, settings.SITE.models.contacts.Partner): if self.project.email: yield ('to', self.project) for r in super(ProjectRelated, self).get_mailable_recipients(): yield r def get_postable_recipients(self): if isinstance(self.project, settings.SITE.models.contacts.Partner): yield self.project for p in super(ProjectRelated, self).get_postable_recipients(): yield p
class Hierarchical(Duplicable): """Abstract model mixin for things that have a "parent" and "siblings". Pronounciation: [hai'ra:kikl] """ class Meta(object): abstract = True parent = fields.ForeignKey('self', verbose_name=_("Parent"), null=True, blank=True, related_name='children') @fields.displayfield(_("Children")) def children_summary(self, ar): if ar is None: return '' elems = [ar.obj2html(ch) for ch in self.children.all()] elems = join_elems(elems, sep=', ') return E.p(*elems) def get_siblings(self): 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) def full_clean(self, *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) def is_parented(self, other): if self == other: return True p = self.parent while p is not None: if p == other: return True p = p.parent def get_parents(self): rv = [] p = self.parent while p is not None: rv.insert(p) p = p.parent return rv def get_parental_line(self): """Return an ordered list of all ancestors of this instance. The last element of the list is this. 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 def whole_clan(self): """Return a set of this instance and all children and grandchildren. """ # 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