Files
mayan-edms/mayan/apps/linking/models.py
Roberto Rosario 8e69178e07 Project: Switch to full app paths
Instead of inserting the path of the apps into the Python app,
the apps are now referenced by their full import path.

This app name claves with external or native Python libraries.
Example: Mayan statistics app vs. Python new statistics library.

Every app reference is now prepended with 'mayan.apps'.

Existing config.yml files need to be updated manually.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
2018-12-05 02:04:20 -04:00

172 lines
5.7 KiB
Python

from __future__ import unicode_literals
from jinja2 import Template
from django.db import models
from django.db.models import Q
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from mayan.apps.documents.models import Document, DocumentType
from .literals import (
INCLUSION_AND, INCLUSION_CHOICES, INCLUSION_OR, OPERATOR_CHOICES
)
from .managers import SmartLinkManager
@python_2_unicode_compatible
class SmartLink(models.Model):
"""
This model stores the basic fields for a smart link. Smart links allow
linking documents using a programmatic method of conditions that mirror
Django's database filter operations.
"""
label = models.CharField(
db_index=True, max_length=96, verbose_name=_('Label')
)
dynamic_label = models.CharField(
blank=True, max_length=96, help_text=_(
'Enter a template to render. '
'Use Django\'s default templating language '
'(https://docs.djangoproject.com/en/1.11/ref/templates/builtins/). '
'The {{ document }} context variable is available.'
), verbose_name=_('Dynamic label')
)
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
document_types = models.ManyToManyField(
to=DocumentType, verbose_name=_('Document types')
)
objects = SmartLinkManager()
class Meta:
ordering = ('label',)
verbose_name = _('Smart link')
verbose_name_plural = _('Smart links')
def __str__(self):
return self.label
def get_dynamic_label(self, document):
"""
If the smart links was created using a template label instead of a
static label, resolve the template and return the result.
"""
if self.dynamic_label:
context = {'document': document}
try:
template = Template(self.dynamic_label)
return template.render(**context)
except Exception as exception:
return _(
'Error generating dynamic label; %s' % force_text(
exception
)
)
else:
return None
def get_linked_document_for(self, document):
"""
Execute the corresponding smart links conditions for the document
provided and return the resulting document queryset.
"""
if document.document_type.pk not in self.document_types.values_list('pk', flat=True):
raise Exception(
_(
'This smart link is not allowed for the selected '
'document\'s type.'
)
)
smart_link_query = Q()
context = {'document': document}
for condition in self.conditions.filter(enabled=True):
template = Template(condition.expression)
condition_query = Q(**{
'%s__%s' % (
condition.foreign_document_data, condition.operator
): template.render(**context)
})
if condition.negated:
condition_query = ~condition_query
if condition.inclusion == INCLUSION_AND:
smart_link_query &= condition_query
elif condition.inclusion == INCLUSION_OR:
smart_link_query |= condition_query
if smart_link_query:
return Document.objects.filter(smart_link_query)
else:
return Document.objects.none()
def resolve_for(self, document):
return ResolvedSmartLink(
smart_link=self, queryset=self.get_linked_document_for(
document=document
)
)
class ResolvedSmartLink(SmartLink):
"""
Proxy model to represent an already resolved smart link. Used for easier
colums registration.
"""
class Meta:
proxy = True
def get_label_for(self, document):
return self.get_dynamic_label(document=document) or self.label
@python_2_unicode_compatible
class SmartLinkCondition(models.Model):
"""
This model stores a single smart link condition. A smart link is a
collection of one of more smart link conditions.
"""
smart_link = models.ForeignKey(
on_delete=models.CASCADE, related_name='conditions', to=SmartLink,
verbose_name=_('Smart link')
)
inclusion = models.CharField(
choices=INCLUSION_CHOICES, default=INCLUSION_AND,
help_text=_('The inclusion is ignored for the first item.'),
max_length=16
)
foreign_document_data = models.CharField(
help_text=_('This represents the metadata of all other documents.'),
max_length=128, verbose_name=_('Foreign document attribute')
)
operator = models.CharField(choices=OPERATOR_CHOICES, max_length=16)
expression = models.TextField(
help_text=_(
'Enter a template to render. '
'Use Django\'s default templating language '
'(https://docs.djangoproject.com/en/1.11/ref/templates/builtins/). '
'The {{ document }} context variable is available.'
), verbose_name=_('Expression')
)
negated = models.BooleanField(
default=False, help_text=_('Inverts the logic of the operator.'),
verbose_name=_('Negated')
)
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
class Meta:
verbose_name = _('Link condition')
verbose_name_plural = _('Link conditions')
def __str__(self):
return '%s foreign %s %s %s %s' % (
self.get_inclusion_display(),
self.foreign_document_data, _('not') if self.negated else '',
self.get_operator_display(), self.expression
)