Add new UI method to switch between required metadata and optional

metadata without incurring in metadata value loss.
Add modifying relationship of document type and metadata type
from the document type and from the metadata type views.
Closes GitLab issues #337 #373.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2017-07-05 04:39:54 -04:00
parent 32c53343f1
commit 95157460cb
8 changed files with 206 additions and 69 deletions

View File

@@ -44,7 +44,14 @@ Other Changes
its own separate $HOME directory. Additionally give every libreoffice
its own UserInstallation file in the $HOME directory. Works around
Libre Office issue: https://bugs.documentfoundation.org/show_bug.cgi?id=37531
Solves or affects GitLab issues #393 #258 #198 #175
Solves or affects GitLab issues #393 #258 #198 #175
- Document type-metadata type relationship. Add new UI method to switch between
required metadata and optional metadata for a document type. This new
method does not forces users to remove a metadata type before making the
switch and thus avoid deletion of existing metadata entries. GitLab issue #337.
- It is now possible to change the relationship between metadata types and
document types from either the metadata type list or document type list.
GitLab issue #373.
Removals
@@ -103,5 +110,7 @@ Bugs fixed or issues closed
* `GitHub issue #255 <https://github.com/mayan-edms/mayan-edms/issues/255>`_ Uploading a local file via api
* `GitLab issue #215 <https://gitlab.com/mayan-edms/mayan-edms/issues/215>`_ Download text contents
* `GitLab issue #286 <https://gitlab.com/mayan-edms/mayan-edms/issues/286>`_ User configurable mailer
* `GitLab issue #337 <https://gitlab.com/mayan-edms/mayan-edms/issues/337>`_ Better way to switch Optional to Required Metadata
* `GitLab issue #373 <https://gitlab.com/mayan-edms/mayan-edms/issues/373>`_ (feature request) Allow selecting document types for metadata
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/

View File

@@ -22,7 +22,17 @@
{% for field in form.visible_fields %}
<td title="{% if field.errors %}{% for error in field.errors %}{{ error }}{% if not forloop.last %} | {% endif %}{% endfor %}{% else %}{{ field.help_text }}{% endif %}">
{% if field.errors %}<div class="has-error">{% endif %}
{% render_field field class+="form-control" %}
{% if field|widget_type == 'radioselect' %}
<div class="btn-group" data-toggle="buttons">
{% for option in field %}
<label class="btn btn-default {% if field.value == option.choice_value %}active{% endif %}" for="{{ option.attrs.id }}">
<input type="radio" name="{{ option.name }}" id="{{ option.attrs.id }}" value="{{ option.choice_value }}" {% if field.value == option.choice_value %}checked{% endif %}> {{ option.choice_label }}
</label>
{% endfor %}
</div>
{% else %}
{% render_field field class+="form-control" %}
{% endif %}
{% if field.errors %}</div>{% endif %}
</td>
{% endfor %}
@@ -38,7 +48,6 @@
{% if field|widget_type != 'checkboxinput' and not field.field.widget.attrs.hidden %}
{% if not hide_labels %}{{ field.label_tag }}{% if field.field.required and not read_only %} ({% trans 'required' %}){% endif %}{% endif %}
{% endif %}
{% if field|widget_type == 'checkboxinput' %}
<div class="checkbox">
<label>

View File

@@ -32,9 +32,8 @@ from .links import (
link_metadata_add, link_metadata_edit, link_metadata_multiple_add,
link_metadata_multiple_edit, link_metadata_multiple_remove,
link_metadata_remove, link_metadata_view,
link_setup_document_type_metadata,
link_setup_document_type_metadata_required,
link_setup_metadata_type_create, link_setup_metadata_type_delete,
link_setup_document_type_metadata_types, link_setup_metadata_type_create,
link_setup_metadata_type_delete, link_setup_metadata_type_document_types,
link_setup_metadata_type_edit, link_setup_metadata_type_list,
)
from .permissions import (
@@ -206,13 +205,13 @@ class MetadataApp(MayanAppConfig):
)
menu_object.bind_links(
links=(
link_setup_document_type_metadata,
link_setup_document_type_metadata_required
link_setup_document_type_metadata_types,
), sources=(DocumentType,)
)
menu_object.bind_links(
links=(
link_setup_metadata_type_edit,
link_setup_metadata_type_document_types,
link_setup_metadata_type_delete
), sources=(MetadataType,)
)

View File

@@ -170,3 +170,70 @@ class DocumentMetadataRemoveForm(DocumentMetadataForm):
DocumentMetadataRemoveFormSet = formset_factory(
DocumentMetadataRemoveForm, extra=0
)
class DocumentTypeMetadataTypeRelationshipForm(forms.Form):
label = forms.CharField(
label=_('Label'), required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
relationship = forms.ChoiceField(
label=_('Relationship'),
widget=forms.RadioSelect(), choices=(
('none', _('None')),
('optional', _('Optional')),
('required', _('Required')),
)
)
def __init__(self, *args, **kwargs):
super(DocumentTypeMetadataTypeRelationshipForm, self).__init__(
*args, **kwargs
)
if self.initial['main_model'] == 'metadata_type':
self.fields['label'].initial = self.initial['document_type'].label
else:
self.fields['label'].initial = self.initial['metadata_type'].label
relationship = self.initial['document_type'].metadata.filter(
metadata_type=self.initial['metadata_type']
)
if relationship.exists():
if relationship.get().required:
self.fields['relationship'].initial = 'required'
else:
self.fields['relationship'].initial = 'optional'
else:
self.fields['relationship'].initial = 'none'
def save(self):
relationship = self.initial['document_type'].metadata.filter(
metadata_type=self.initial['metadata_type']
)
if self.cleaned_data['relationship'] == 'none':
relationship.delete()
elif self.cleaned_data['relationship'] == 'optional':
if relationship.exists():
instance = relationship.get()
instance.required = False
instance.save()
else:
relationship.create(
document_type=self.initial['document_type'],
metadata_type=self.initial['metadata_type']
)
elif self.cleaned_data['relationship'] == 'required':
if relationship.exists():
instance = relationship.get()
instance.required = True
instance.save()
else:
relationship.create(
document_type=self.initial['document_type'],
metadata_type=self.initial['metadata_type'], required=True
)
DocumentTypeMetadataTypeRelationshipFormSet = formset_factory(
DocumentTypeMetadataTypeRelationshipForm, extra=0
)

View File

@@ -36,15 +36,17 @@ link_metadata_remove = Link(
)
link_metadata_view = Link(
icon='fa fa-pencil', permissions=(permission_metadata_document_view,),
text=_('Metadata'), view='metadata:metadata_view', args='resolved_object.pk'
text=_('Metadata'), view='metadata:metadata_view',
args='resolved_object.pk'
)
link_setup_document_type_metadata = Link(
permissions=(permission_document_type_edit,), text=_('Optional metadata'),
view='metadata:setup_document_type_metadata', args='resolved_object.pk'
link_setup_document_type_metadata_types = Link(
permissions=(permission_document_type_edit,), text=_('Metadata types'),
view='metadata:setup_document_type_metadata_types',
args='resolved_object.pk'
)
link_setup_document_type_metadata_required = Link(
permissions=(permission_document_type_edit,), text=_('Required metadata'),
view='metadata:setup_document_type_metadata_required',
link_setup_metadata_type_document_types = Link(
permissions=(permission_document_type_edit,), text=_('Document types'),
view='metadata:setup_metadata_type_document_types',
args='resolved_object.pk'
)
link_setup_metadata_type_create = Link(

View File

@@ -14,7 +14,22 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='metadatatype',
name='parser',
field=models.CharField(blank=True, help_text='The parser will reformat the value entered to conform to the expected format.', max_length=64, verbose_name='Parser', choices=[(b'metadata.parsers.DateAndTimeParser', b'metadata.parsers.DateAndTimeParser'), (b'metadata.parsers.DateParser', b'metadata.parsers.DateParser'), (b'metadata.parsers.TimeParser', b'metadata.parsers.TimeParser')]),
preserve_default=True,
field=models.CharField(
blank=True, help_text='The parser will reformat the value '
'entered to conform to the expected format.', max_length=64,
verbose_name='Parser',
choices=[
(
b'metadata.parsers.DateAndTimeParser',
b'metadata.parsers.DateAndTimeParser'
), (
b'metadata.parsers.DateParser',
b'metadata.parsers.DateParser'
), (
b'metadata.parsers.TimeParser',
b'metadata.parsers.TimeParser'
)
]
), preserve_default=True,
),
]

View File

@@ -11,8 +11,8 @@ from .views import (
DocumentMetadataAddView, DocumentMetadataEditView,
DocumentMetadataListView, DocumentMetadataRemoveView,
MetadataTypeCreateView, MetadataTypeDeleteView, MetadataTypeEditView,
MetadataTypeListView, SetupDocumentTypeMetadataOptionalView,
SetupDocumentTypeMetadataRequiredView
MetadataTypeListView, SetupDocumentTypeMetadataTypes,
SetupMetadataTypesDocumentTypes
)
urlpatterns = [
@@ -61,16 +61,15 @@ urlpatterns = [
r'^setup/type/(?P<pk>\d+)/delete/$',
MetadataTypeDeleteView.as_view(), name='setup_metadata_type_delete'
),
url(
r'^setup/document/type/(?P<pk>\d+)/metadata/edit/$',
SetupDocumentTypeMetadataOptionalView.as_view(),
name='setup_document_type_metadata'
r'^setup/document_types/(?P<pk>\d+)/metadata_types/$',
SetupDocumentTypeMetadataTypes.as_view(),
name='setup_document_type_metadata_types'
),
url(
r'^setup/document/type/(?P<pk>\d+)/metadata/edit/required/$',
SetupDocumentTypeMetadataRequiredView.as_view(),
name='setup_document_type_metadata_required'
r'^setup/metadata_types/(?P<pk>\d+)/document_types/$',
SetupMetadataTypesDocumentTypes.as_view(),
name='setup_metadata_type_document_types'
),
]

View File

@@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _, ungettext
from acls.models import AccessControlList
from common.generics import (
AssignRemoveView, MultipleObjectFormActionView, SingleObjectCreateView,
FormView, MultipleObjectFormActionView, SingleObjectCreateView,
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
)
from documents.models import Document, DocumentType
@@ -23,7 +23,8 @@ from documents.permissions import (
from .api import save_metadata_list
from .forms import (
DocumentAddMetadataForm, DocumentMetadataFormSet,
DocumentMetadataRemoveFormSet, MetadataTypeForm
DocumentMetadataRemoveFormSet,
DocumentTypeMetadataTypeRelationshipFormSet, MetadataTypeForm
)
from .models import DocumentMetadata, MetadataType
from .permissions import (
@@ -44,7 +45,9 @@ class DocumentMetadataAddView(MultipleObjectFormActionView):
)
def dispatch(self, request, *args, **kwargs):
result = super(DocumentMetadataAddView, self).dispatch(request, *args, **kwargs)
result = super(
DocumentMetadataAddView, self
).dispatch(request, *args, **kwargs)
queryset = self.get_queryset()
@@ -198,7 +201,9 @@ class DocumentMetadataEditView(MultipleObjectFormActionView):
)
def dispatch(self, request, *args, **kwargs):
result = super(DocumentMetadataEditView, self).dispatch(request, *args, **kwargs)
result = super(
DocumentMetadataEditView, self
).dispatch(request, *args, **kwargs)
queryset = self.get_queryset()
@@ -274,7 +279,9 @@ class DocumentMetadataEditView(MultipleObjectFormActionView):
document.add_as_recent_document_for_user(self.request.user)
for document_metadata in document.metadata.all():
metadata_dict.setdefault(document_metadata.metadata_type, set())
metadata_dict.setdefault(
document_metadata.metadata_type, set()
)
if document_metadata.value:
metadata_dict[
@@ -364,7 +371,9 @@ class DocumentMetadataRemoveView(MultipleObjectFormActionView):
)
def dispatch(self, request, *args, **kwargs):
result = super(DocumentMetadataRemoveView, self).dispatch(request, *args, **kwargs)
result = super(
DocumentMetadataRemoveView, self
).dispatch(request, *args, **kwargs)
queryset = self.get_queryset()
@@ -543,59 +552,87 @@ class MetadataTypeListView(SingleObjectListView):
}
class SetupDocumentTypeMetadataOptionalView(AssignRemoveView):
decode_content_type = True
class SetupDocumentTypeMetadataTypes(FormView):
form_class = DocumentTypeMetadataTypeRelationshipFormSet
main_model = 'document_type'
model = DocumentType
submodel = MetadataType
view_permission = permission_document_type_edit
left_list_title = _('Available metadata types')
right_list_title = _('Metadata types assigned')
def add(self, item):
self.get_object().metadata.create(metadata_type=item, required=False)
def form_valid(self, form):
try:
for instance in form:
instance.save()
except Exception as exception:
messages.error(
self.request,
_('Error updating relationship; %s') % exception
)
else:
messages.success(
self.request, _('Relationships updated successfully')
)
return super(
SetupDocumentTypeMetadataTypes, self
).form_valid(form=form)
def get_object(self):
return get_object_or_404(DocumentType, pk=self.kwargs['pk'])
def left_list(self):
return AssignRemoveView.generate_choices(
set(MetadataType.objects.all()) - set(
MetadataType.objects.filter(
id__in=self.get_object().metadata.values_list(
'metadata_type', flat=True
)
)
)
)
def right_list(self):
return AssignRemoveView.generate_choices(
self.get_object().metadata.filter(required=False)
)
def remove(self, item):
item.delete()
return get_object_or_404(self.model, pk=self.kwargs['pk'])
def get_extra_context(self):
return {
'form_display_mode_table': True,
'object': self.get_object(),
'title': _(
'Optional metadata types for document type: %s'
) % self.get_object(),
'Metadata types for document type: %s'
) % self.get_object()
}
def get_initial(self):
obj = self.get_object()
initial = []
class SetupDocumentTypeMetadataRequiredView(SetupDocumentTypeMetadataOptionalView):
def add(self, item):
self.get_object().metadata.create(metadata_type=item, required=True)
for element in self.get_queryset():
initial.append({
'document_type': obj,
'main_model': self.main_model,
'metadata_type': element,
})
return initial
def right_list(self):
return AssignRemoveView.generate_choices(
self.get_object().metadata.filter(required=True)
)
def get_post_action_redirect(self):
return reverse('documents:document_type_list')
def get_queryset(self):
return self.submodel.objects.all()
class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes):
main_model = 'metadata_type'
model = MetadataType
submodel = DocumentType
def get_extra_context(self):
return {
'form_display_mode_table': True,
'object': self.get_object(),
'title': _(
'Required metadata types for document type: %s'
) % self.get_object(),
'Document types for metadata type: %s'
) % self.get_object()
}
def get_initial(self):
obj = self.get_object()
initial = []
for element in self.get_queryset():
initial.append({
'document_type': element,
'main_model': self.main_model,
'metadata_type': obj,
})
return initial
def get_post_action_redirect(self):
return reverse('metadata:setup_metadata_type_list')