Refactor the ModelAttribute class into two separate classes: ModelAttribute for executable model attributes and ModelField for actual ORM fields. Expose more document fields for use in smart links.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2018-09-17 02:43:04 -04:00
parent fafdb538b3
commit 03c54395cc
11 changed files with 196 additions and 77 deletions

View File

@@ -151,6 +151,10 @@
templates that only refresh the menu when there are changes. templates that only refresh the menu when there are changes.
Closes GitLab issue #511. Thanks to Daniel Carrico Closes GitLab issue #511. Thanks to Daniel Carrico
@daniel1113 for the report. @daniel1113 for the report.
- Refactor the ModelAttribute class into two separate classes:
ModelAttribute for executable model attributes and ModelField
for actual ORM fields.
- Expose more document fields for use in smart links.
3.0.3 (2018-08-17) 3.0.3 (2018-08-17)
================== ==================

View File

@@ -367,6 +367,11 @@ classes beyond the provide line chart.
templates that only refresh the menu when there are changes. templates that only refresh the menu when there are changes.
Closes GitLab issue #511. Thanks to Daniel Carrico Closes GitLab issue #511. Thanks to Daniel Carrico
@daniel1113 for the report. @daniel1113 for the report.
- Refactor the ModelAttribute class into two separate classes:
ModelAttribute for executable model attributes and ModelField
for actual ORM fields.
- Expose more document fields for use in smart links.
Removals Removals
-------- --------

View File

@@ -178,19 +178,15 @@ class MissingItem(object):
@python_2_unicode_compatible @python_2_unicode_compatible
class ModelAttribute(object): class ModelAttribute(object):
__registry = {} _registry = {}
@classmethod @classmethod
def get_for(cls, model, type_names=None): def get_for(cls, model):
result = [] result = []
try: try:
for type_name, attributes in cls.__registry[model].items(): return cls._registry[model]
if not type_names or type_name in type_names: except KeyError:
result.extend(attributes)
return result
except IndexError:
# We were passed a model instance, try again using the model of # We were passed a model instance, try again using the model of
# the instance # the instance
@@ -198,30 +194,39 @@ class ModelAttribute(object):
if model.__class__ == models.base.ModelBase: if model.__class__ == models.base.ModelBase:
raise raise
return cls.get_for[type(model)] return cls.get_for(model=type(model))
@classmethod @classmethod
def get_choices_for(cls, model, type_names=None): def get_choices_for(cls, model):
return [ return [
( (attribute.name, attribute) for attribute in cls.get_for(model)
attribute.name, attribute
) for attribute in cls.get_for(model, type_names)
] ]
@classmethod @classmethod
def help_text_for(cls, model, type_names=None): def get_help_text_for(cls, model, show_name=False):
result = [] result = []
for count, attribute in enumerate(cls.get_for(model, type_names), 1): for count, attribute in enumerate(cls.get_for(model=model), 1):
result.append( result.append(
'{}) {}'.format( '{}) {}'.format(
count, force_text(attribute.get_display(show_name=True)) count, force_text(attribute.get_display(show_name=show_name))
) )
) )
return ' '.join( return ' '.join(
[ugettext('Available attributes: \n'), ', \n'.join(result)] [ugettext('Available attributes: \n'), '\n'.join(result)]
) )
def __init__(self, model, name, label=None, description=None):
self.model = model
self.label = label
self.name = name
self.description = description
self._registry.setdefault(model, [])
self._registry[model].append(self)
def __str__(self):
return self.get_display()
def get_display(self, show_name=False): def get_display(self, show_name=False):
if self.description: if self.description:
return '{} - {}'.format( return '{} - {}'.format(
@@ -230,29 +235,101 @@ class ModelAttribute(object):
else: else:
return force_text(self.name if show_name else self.label) return force_text(self.name if show_name else self.label)
def __str__(self):
return self.get_display()
def __init__(self, model, name, label=None, description=None, type_name=None): class ModelField(ModelAttribute):
self.model = model """Subclass to handle model database fields"""
self.label = label _registry = {}
self.name = name
self.description = description
for field in model._meta.fields: @classmethod
if field.name == name: def get_help_text_for(cls, model, show_name=False):
self.label = field.verbose_name result = []
self.description = field.help_text for count, model_field in enumerate(cls.get_for(model=model), 1):
result.append(
'{}) {} - {}'.format(
count,
model_field.name if show_name else model_field.label,
model_field.description
)
)
self.__registry.setdefault(model, {}) return ' '.join(
[ugettext('Available fields: \n'), '\n'.join(result)]
)
if isinstance(type_name, list): def __init__(self, *args, **kwargs):
for single_type in type_name: super(ModelField, self).__init__(*args, **kwargs)
self.__registry[model].setdefault(single_type, []) self._final_model_verbose_name = None
self.__registry[model][single_type].append(self)
if not self.label:
self.label = self.get_field_attribute(
attribute='verbose_name'
)
if self.label != self._final_model_verbose_name:
self.label = '{} {}'.format(
self._final_model_verbose_name, self.label
)
if not self.description:
self.description = self.get_field_attribute(
attribute='help_text'
)
def get_field_attribute(self, attribute, model=None, field_name=None):
if not model:
model = self.model
if not field_name:
field_name = self.name
parts = field_name.split('__')
if len(parts) > 1:
return self.get_field_attribute(
model=model._meta.get_field(parts[0]).related_model,
field_name='__'.join(parts[1:]), attribute=attribute
)
else: else:
self.__registry[model].setdefault(type_name, []) self._final_model_verbose_name = model._meta.verbose_name
self.__registry[model][type_name].append(self) return getattr(
model._meta.get_field(field_name=field_name),
attribute
)
class ModelProperty(object):
_registry = []
@classmethod
def get_for(cls, model):
result = []
for klass in cls._registry:
result.extend(klass.get_for(model=model))
return result
@classmethod
def get_choices_for(cls, model):
result = []
for klass in cls._registry:
result.extend(klass.get_choices_for(model=model))
return result
@classmethod
def get_help_text_for(cls, model, show_name=False):
result = []
for klass in cls._registry:
result.append(
klass.get_help_text_for(model=model, show_name=show_name)
)
return '\n'.join(result)
@classmethod
def register(cls, klass):
cls._registry.append(klass)
class Package(object): class Package(object):
@@ -321,3 +398,7 @@ class Template(object):
self.html = result.content self.html = result.content
self.hex_hash = hashlib.sha256(result.content).hexdigest() self.hex_hash = hashlib.sha256(result.content).hexdigest()
return self return self
ModelProperty.register(ModelAttribute)
ModelProperty.register(ModelField)

View File

@@ -5,7 +5,7 @@ from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from acls.models import AccessControlList from acls.models import AccessControlList
from common.classes import ModelAttribute from common.classes import ModelProperty
from documents.models import Document from documents.models import Document
from .models import Index, IndexTemplateNode from .models import Index, IndexTemplateNode
@@ -40,8 +40,9 @@ class IndexTemplateNodeForm(forms.ModelForm):
self.fields['expression'].help_text = ' '.join( self.fields['expression'].help_text = ' '.join(
[ [
force_text(self.fields['expression'].help_text), force_text(self.fields['expression'].help_text),
ModelAttribute.help_text_for( '<br>',
Document, type_names=['indexing'] ModelProperty.get_help_text_for(
model=Document, show_name=True
).replace('\n', '<br>') ).replace('\n', '<br>')
] ]
) )

View File

@@ -15,6 +15,7 @@ from common import (
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary, MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
menu_tools menu_tools
) )
from common.classes import ModelField
from common.settings import settings_db_sync_task_delay from common.settings import settings_db_sync_task_delay
from documents.search import document_search, document_page_search from documents.search import document_search, document_page_search
from documents.signals import post_version_upload from documents.signals import post_version_upload
@@ -97,6 +98,10 @@ class DocumentParsingApp(MayanAppConfig):
'submit_for_parsing', document_version_parsing_submit 'submit_for_parsing', document_version_parsing_submit
) )
ModelField(
Document, name='versions__pages__content__content'
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(
permission_content_view, permission_parse_document permission_content_view, permission_parse_document
@@ -110,6 +115,7 @@ class DocumentParsingApp(MayanAppConfig):
ModelPermission.register_inheritance( ModelPermission.register_inheritance(
model=DocumentTypeSettings, related='document_type', model=DocumentTypeSettings, related='document_type',
) )
SourceColumn( SourceColumn(
source=DocumentVersionParseError, label=_('Document'), source=DocumentVersionParseError, label=_('Document'),
func=lambda context: document_link(context['object'].document_version.document) func=lambda context: document_link(context['object'].document_version.document)

View File

@@ -90,18 +90,19 @@ class DocumentStatesApp(MayanAppConfig):
WorkflowAction.initialize() WorkflowAction.initialize()
ModelAttribute( ModelAttribute(
Document, 'workflow.< workflow internal name >.get_current_state', model=Document,
name='workflow.< workflow internal name >.get_current_state',
label=_('Current state of a workflow'), description=_( label=_('Current state of a workflow'), description=_(
'Return the current state of the selected workflow' 'Return the current state of the selected workflow'
), type_name=['property', 'indexing'] )
) )
ModelAttribute( ModelAttribute(
Document, model=Document,
'workflow.< workflow internal name >.get_current_state.completion', name='workflow.< workflow internal name >.get_current_state.completion',
label=_('Current state of a workflow'), description=_( label=_('Current state of a workflow'), description=_(
'Return the completion value of the current state of the ' 'Return the completion value of the current state of the '
'selected workflow' 'selected workflow'
), type_name=['property', 'indexing'] )
) )
ModelPermission.register( ModelPermission.register(

View File

@@ -14,7 +14,7 @@ from common import (
MayanAppConfig, MissingItem, menu_facet, menu_main, menu_object, MayanAppConfig, MissingItem, menu_facet, menu_main, menu_object,
menu_secondary, menu_setup, menu_sidebar, menu_multi_item, menu_tools menu_secondary, menu_setup, menu_sidebar, menu_multi_item, menu_tools
) )
from common.classes import ModelAttribute from common.classes import ModelAttribute, ModelField
from common.dashboards import dashboard_main from common.dashboards import dashboard_main
from common.signals import post_initial_setup from common.signals import post_initial_setup
from common.widgets import TwoStateWidget from common.widgets import TwoStateWidget
@@ -142,14 +142,29 @@ class DocumentsApp(MayanAppConfig):
view='documents:document_type_list' view='documents:document_type_list'
) )
ModelAttribute( ModelField(Document, name='description')
Document, label=_('Label'), name='label', type_name='field' ModelField(Document, name='date_added')
ModelField(Document, name='deleted_date_time')
ModelField(Document, name='document_type__label')
ModelField(Document, name='in_trash')
ModelField(Document, name='is_stub')
ModelField(Document, name='label')
ModelField(Document, name='language')
ModelField(Document, name='uuid')
ModelField(
Document, name='versions__checksum'
) )
ModelField(
ModelAttribute( Document, label=_('Versions comment'), name='versions__comment'
Document, )
description=_('The MIME type of any of the versions of a document'), ModelField(
label=_('MIME type'), name='versions__mimetype', type_name='field' Document, label=_('Versions encoding'), name='versions__encoding'
)
ModelField(
Document, label=_('Versions mime type'), name='versions__mimetype'
)
ModelField(
Document, label=_('Versions timestamp'), name='versions__timestamp'
) )
ModelEventType.register( ModelEventType.register(

View File

@@ -4,7 +4,7 @@ from django import forms
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.classes import ModelAttribute from common.classes import ModelAttribute, ModelField, ModelProperty
from documents.models import Document from documents.models import Document
from .models import SmartLink, SmartLinkCondition from .models import SmartLink, SmartLinkCondition
@@ -16,8 +16,8 @@ class SmartLinkForm(forms.ModelForm):
self.fields['dynamic_label'].help_text = ' '.join( self.fields['dynamic_label'].help_text = ' '.join(
[ [
force_text(self.fields['dynamic_label'].help_text), force_text(self.fields['dynamic_label'].help_text),
ModelAttribute.help_text_for( ModelProperty.get_help_text_for(
Document, type_names=['field', 'related', 'property'] model=Document, show_name=True
).replace('\n', '<br>') ).replace('\n', '<br>')
] ]
) )
@@ -31,15 +31,15 @@ class SmartLinkConditionForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SmartLinkConditionForm, self).__init__(*args, **kwargs) super(SmartLinkConditionForm, self).__init__(*args, **kwargs)
self.fields['foreign_document_data'] = forms.ChoiceField( self.fields['foreign_document_data'] = forms.ChoiceField(
choices=ModelAttribute.get_choices_for( choices=ModelField.get_choices_for(
Document, type_names=['field', 'query'] model=Document,
), label=_('Foreign document attribute') ), label=_('Foreign document field')
) )
self.fields['expression'].help_text = ' '.join( self.fields['expression'].help_text = ' '.join(
[ [
force_text(self.fields['expression'].help_text), force_text(self.fields['expression'].help_text),
ModelAttribute.help_text_for( ModelProperty.get_help_text_for(
Document, type_names=['field', 'related', 'property'] model=Document, show_name=True
).replace('\n', '<br>') ).replace('\n', '<br>')
] ]
) )

View File

@@ -15,7 +15,7 @@ from common import (
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary, MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
menu_setup, menu_sidebar menu_setup, menu_sidebar
) )
from common.classes import ModelAttribute from common.classes import ModelAttribute, ModelField
from common.widgets import TwoStateWidget from common.widgets import TwoStateWidget
from documents.search import document_page_search, document_search from documents.search import document_page_search, document_search
from documents.signals import post_document_type_change from documents.signals import post_document_type_change
@@ -92,26 +92,18 @@ class MetadataApp(MayanAppConfig):
) )
ModelAttribute( ModelAttribute(
Document, 'metadata', type_name='related', Document, 'metadata_value_of',
description=_(
'Queryset containing a MetadataType instance reference and a '
'value for that metadata type'
)
)
ModelAttribute(
Document, 'metadata__metadata_type__name',
label=_('Metadata type name'), type_name='query'
)
ModelAttribute(
Document, 'metadata__value', label=_('Metadata type value'),
type_name='query'
)
ModelAttribute(
Document, 'metadata_value_of', label=_('Value of a metadata'),
description=_( description=_(
'Return the value of a specific document metadata' 'Return the value of a specific document metadata'
), ),
type_name=['property', 'indexing'] )
ModelField(
Document, 'metadata__metadata_type__name',
label=_('Metadata type name')
)
ModelField(
Document, 'metadata__value', label=_('Metadata type value'),
) )
ModelEventType.register( ModelEventType.register(

View File

@@ -15,6 +15,7 @@ from common import (
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary, MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary,
menu_tools menu_tools
) )
from common.classes import ModelField
from common.settings import settings_db_sync_task_delay from common.settings import settings_db_sync_task_delay
from documents.search import document_search, document_page_search from documents.search import document_search, document_page_search
from documents.signals import post_version_upload from documents.signals import post_version_upload
@@ -98,6 +99,10 @@ class OCRApp(MayanAppConfig):
'submit_for_ocr', document_version_ocr_submit 'submit_for_ocr', document_version_ocr_submit
) )
ModelField(
Document, name='versions__pages__ocr_content__content'
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(
permission_ocr_document, permission_ocr_content_view permission_ocr_document, permission_ocr_content_view
@@ -111,6 +116,7 @@ class OCRApp(MayanAppConfig):
ModelPermission.register_inheritance( ModelPermission.register_inheritance(
model=DocumentTypeSettings, related='document_type', model=DocumentTypeSettings, related='document_type',
) )
SourceColumn( SourceColumn(
source=DocumentVersionOCRError, label=_('Document'), source=DocumentVersionOCRError, label=_('Document'),
func=lambda context: document_link(context['object'].document_version.document) func=lambda context: document_link(context['object'].document_version.document)

View File

@@ -11,6 +11,7 @@ from common import (
MayanAppConfig, menu_facet, menu_object, menu_main, menu_multi_item, MayanAppConfig, menu_facet, menu_object, menu_main, menu_multi_item,
menu_sidebar menu_sidebar
) )
from common.classes import ModelField
from documents.search import document_page_search, document_search from documents.search import document_page_search, document_search
from events import ModelEventType from events import ModelEventType
from events.links import ( from events.links import (
@@ -73,6 +74,13 @@ class TagsApp(MayanAppConfig):
) )
) )
ModelField(
Document, name='tags__label'
)
ModelField(
Document, name='tags__color'
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(
permission_tag_attach, permission_tag_remove, permission_tag_attach, permission_tag_remove,