diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2018-06-07 16:52:02 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2018-06-12 10:46:13 +0200 |
commit | 875462381bc57b13a8ca6b68a52ad1ca065ef95e (patch) | |
tree | 68d54bb40b2006fc2a3d5ae938108e9b950eca59 /ishtar_common | |
parent | fdd0231543be132b846e3bf12127cd01860f55bf (diff) | |
download | Ishtar-875462381bc57b13a8ca6b68a52ad1ca065ef95e.tar.bz2 Ishtar-875462381bc57b13a8ca6b68a52ad1ca065ef95e.zip |
Document form: add related fields (refs #4107)
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/forms.py | 47 | ||||
-rw-r--r-- | ishtar_common/forms_common.py | 53 | ||||
-rw-r--r-- | ishtar_common/models.py | 5 | ||||
-rw-r--r-- | ishtar_common/templates/blocks/bs_form_snippet.html | 15 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/forms/document.html | 38 | ||||
-rw-r--r-- | ishtar_common/templatetags/from_dict.py | 5 | ||||
-rw-r--r-- | ishtar_common/utils.py | 21 | ||||
-rw-r--r-- | ishtar_common/views.py | 2 | ||||
-rw-r--r-- | ishtar_common/views_item.py | 19 |
9 files changed, 191 insertions, 14 deletions
diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index 59f3e141a..b0f1920df 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -33,6 +33,7 @@ from django.forms.formsets import BaseFormSet, DELETION_FIELD_NAME from django.utils import formats, translation from django.utils.functional import lazy from django.utils.safestring import mark_safe +from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from bootstrap_datepicker.widgets import DatePicker, DATE_FORMAT, DateField @@ -356,14 +357,46 @@ class FieldType(object): class FormHeader(object): - def __init__(self, label, level=4): + def __init__(self, label, level=4, collapse=False): self.label = label + self.collapse = collapse + if collapse: + level = 5 self.level = level def render(self): - return mark_safe(u"<h{level}>{label}</h{level}>".format( - label=self.label, level=self.level - )) + if not self.collapse: + return mark_safe(u"<h{level}>{label}</h{level}>".format( + label=self.label, level=self.level + )) + html = u"""<div id="collapse-parent-{slug}"> + <div class="card"> + <div class="card-header" id="collapse-head-{slug}"> + <h{level} href="#"> + <button class="btn btn-link" type="button" data-toggle="collapse" + data-target="#collapse-{slug}" aria-expanded="true" + aria-controls="collapse-{slug}"> + <i class="fa fa-compress" aria-hidden="true"></i> + {label} + </button> + </h{level}> + </div> + + <div id="collapse-{slug}" class="collapse" + aria-labelledby="collapse-head-{slug}" + data-parent="#colapse-parent-{slug}"> + <div class="card-body"> +""".format(label=self.label, slug=slugify(self.label), level=self.level) + return mark_safe(html) + + def render_end(self): + if not self.collapse: + return "" + return mark_safe(u""" + </div> + </div> + </div> + </div>""") class IshtarForm(forms.Form): @@ -404,6 +437,12 @@ class IshtarForm(forms.Form): self.fields[field.key].choices = field.get_choices() self.fields[field.key].help_text = field.get_help() + def headers(self, key): + if key not in self.HEADERS: + return + self.current_header = self.HEADERS[key] + return self.current_header + class TableSelect(IshtarForm): def __init__(self, *args, **kwargs): diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index 9abc6551a..0cfc2a609 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -41,7 +41,7 @@ import widgets from bootstrap_datepicker.widgets import DatePicker from ishtar_common.templatetags.link_to_window import link_to_window from forms import FinalForm, FormSet, reverse_lazy, name_validator, \ - TableSelect, ManageOldType, CustomForm, FieldType, \ + TableSelect, ManageOldType, CustomForm, FieldType, FormHeader, \ FormSetWithDeleteSwitches, IshtarForm, get_data_from_formset from ishtar_common.utils import is_downloadable, clean_session_cache @@ -1079,7 +1079,7 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): label=_(u"Image"), help_text=mark_safe(get_image_help()), max_length=255, required=False, widget=widgets.ImageFileInput()) associated_file = forms.FileField( - label=pgettext(u"File", u"Not directory"), max_length=255, + label=pgettext(u"Not directory", u"File"), max_length=255, required=False) reference = forms.CharField( label=_(u"Reference"), @@ -1111,12 +1111,31 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): class Meta: model = models.Document fields = [ - 'title', 'source_type', 'authors', 'associated_url', 'image', - 'associated_file', 'reference', 'internal_reference', - 'receipt_date', 'creation_date', 'receipt_date_in_documentation', + 'title', 'source_type', 'reference', 'internal_reference', + 'image', 'associated_file', 'associated_url', + 'authors', 'receipt_date', + 'receipt_date_in_documentation', 'creation_date', 'comment', 'description', 'additional_information', 'duplicate' ] + HEADERS = { + 'title': FormHeader(_(u"Identification")), + 'image': FormHeader(_(u"Content")), + 'authors': FormHeader(_(u"Authors")), + 'receipt_date': FormHeader(_(u"Dates")), + 'comment': FormHeader(_(u"Advanced"), collapse=True), + 'finds': FormHeader(_(u"Related items")), + } + + def __init__(self, *args, **kwargs): + super(DocumentForm, self).__init__(*args, **kwargs) + for related_key in models.Document.RELATED_MODELS_ALT: + model = models.Document._meta.get_field(related_key).related_model + self.fields[related_key] = widgets.Select2MultipleField( + model=model, remote=True, label=model._meta.verbose_name_plural, + required=False, long_widget=True + ) + def clean(self): cleaned_data = self.cleaned_data if not cleaned_data.get('title', None) and \ @@ -1126,7 +1145,29 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): raise forms.ValidationError(_(u"You should at least fill one of " u"this field: title, url, image or " u"file.")) - return cleaned_data + for rel in models.Document.RELATED_MODELS: + if cleaned_data.get(rel, None): + return cleaned_data + raise forms.ValidationError(_(u"A document have to attached at least " + u"to one item")) + + def save(self, commit=True): + item = super(DocumentForm, self).save(commit=commit) + for related_key in models.Document.RELATED_MODELS: + related = getattr(item, related_key) + initial = dict([(item.pk, item) + for item in related.all()]) + new = [int(pk) + for pk in sorted(self.cleaned_data.get(related_key, []))] + for pk in initial.keys(): + if pk in new: + continue + related.remove(initial[pk]) + for new_pk in new: + if new_pk in initial.keys(): + continue + related.add(related.model.objects.get(pk=new_pk)) + return item class DocumentSelect(TableSelect): diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 758596279..3715c0326 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -3060,6 +3060,11 @@ class Document(OwnPerms, ImageModel, FullSearch): 'treatment_files', 'treatments', 'finds', 'context_records', 'operations', 'sites', 'warehouses', ] + # same fields but in order for forms + RELATED_MODELS_ALT = [ + 'finds', 'context_records', 'operations', 'sites', 'warehouses', + 'treatments', 'treatment_files', + ] SLUG = 'document' LINK_SPLIT = u"<||>" diff --git a/ishtar_common/templates/blocks/bs_form_snippet.html b/ishtar_common/templates/blocks/bs_form_snippet.html index 47779cdc1..74dbadf5c 100644 --- a/ishtar_common/templates/blocks/bs_form_snippet.html +++ b/ishtar_common/templates/blocks/bs_form_snippet.html @@ -63,13 +63,26 @@ {% if field.name in form.HEADERS %} {% if forloop.counter0 %} </div>{% endif %} -<h3>{{field.name|from_dict:form.HEADERS|call:'render'}}</h3> + +{% if form.current_header %} + {{form.current_header|call:'render_end'}} +{% endif %} + +{% with "headers,"|add:field.name as call_arg %} +{{form|call:call_arg|call:"render"}} +{% endwith %} + <div class="form-row{% if odd %} odd{% endif %}"> {% elif not search and not forloop.counter0 or search and forloop.counter0 == 1 %} <div class="form-row{% if odd %} odd{% endif %}"> {% endif %} {% include "blocks/bs_field_snippet.html" %} {% if forloop.last %} + +{% if form.current_header %} + {{form.current_header|call:'render_end'}} +{% endif %} + {% if search and forloop.counter0 >= 1 %} </div> <div class="modal-footer"> diff --git a/ishtar_common/templates/ishtar/forms/document.html b/ishtar_common/templates/ishtar/forms/document.html new file mode 100644 index 000000000..38bb3cacf --- /dev/null +++ b/ishtar_common/templates/ishtar/forms/document.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% load i18n inline_formset table_form %} +{% block extra_head %} +{{form.media}} +{% endblock %} + +{% block pre_container %} +<form enctype="multipart/form-data" action="." method="post"{% if confirm %} + onsubmit='return confirm("{{confirm}}");'{% endif %}>{% csrf_token %} +{% endblock %} +{% block content %} +<h2>{{page_name}}</h2> +<div class='form'> + {% for error in form.non_field_errors %} + <p>{{ error }}</p> + {% endfor %} + {% bs_form form %} +</div> +{% endblock %} + +{% block footer %} +<div id="footer"> + {% if form.SEARCH_AND_SELECT %} + <p class="confirm-message">{% trans "Search and select an item in the table" %}</p> + {% endif %} + <div id='validation-bar' class="row text-center"> + <div class="col-sm"> + <button type="submit" id="submit_form" name='validate' + value="validate" class="btn btn-success"> + {% if submit_label %}{{submit_label}}{% else%}{% trans "Validate" %}{% endif %} + </button> + </div> + </div> + {% include 'ishtar/blocks/footer.html' %} +</div> +</form> +{% endblock %} + diff --git a/ishtar_common/templatetags/from_dict.py b/ishtar_common/templatetags/from_dict.py index c64190ab1..f9b5bdb99 100644 --- a/ishtar_common/templatetags/from_dict.py +++ b/ishtar_common/templatetags/from_dict.py @@ -15,4 +15,7 @@ def from_dict(value, dct): @register.filter def call(value, call): - return getattr(value, call)() + args = call.split(",") + return getattr(value, args[0])(*args[1:]) + + diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 443a22111..b089ddcee 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -791,7 +791,7 @@ def create_default_json_fields(model): ) -def get_urls_for_model(model, views): +def get_urls_for_model(model, views, own=False, autocomplete=False): """ Generate get and show url for a model """ @@ -804,9 +804,28 @@ def get_urls_for_model(model, views): check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( getattr(views, 'display_' + model.SLUG)), name='display-' + model.SLUG), + ] + if own: + urls += [ + url(r'get-{}/own/(?P<type>.+)?$'.format(model.SLUG), + check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( + getattr(views, 'get_' + model.SLUG)), + name="get-own-" + model.SLUG, kwargs={'force_own': True}), + ] + + urls += [ url(r'get-{}/(?P<type>.+)?$'.format(model.SLUG), check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( getattr(views, 'get_' + model.SLUG)), name="get-" + model.SLUG), ] + + if autocomplete: + urls += [ + url(r'autocomplete-{}/$'.format(model.SLUG), + check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( + getattr(views, 'autocomplete_' + model.SLUG)), + name='autocomplete-' + model.SLUG), + ] + return urls diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 81397dd70..cfe98cff1 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -1587,7 +1587,7 @@ class DocumentFormView(IshtarMixin, LoginRequiredMixin, CreateView): page_name = _(u"New Document") form_class = forms.DocumentForm - template_name = 'ishtar/form.html' + template_name = 'ishtar/forms/document.html' model = models.Document def get_success_url(self): diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py index eef3440bc..4be1ec144 100644 --- a/ishtar_common/views_item.py +++ b/ishtar_common/views_item.py @@ -53,6 +53,25 @@ CURRENT_ITEM_KEYS = (('file', File), CURRENT_ITEM_KEYS_DICT = dict(CURRENT_ITEM_KEYS) +def get_autocomplete_item(model, extra=None): + if not extra: + extra = {} + + def func(request, current_right=None): + q = request.GET.get('term') or "" + query = Q(**extra) + for q in q.split(' '): + if not q: + continue + query = query & Q(cached_label__icontains=q) + limit = 20 + objects = model.objects.filter(query)[:limit] + data = json.dumps([{'id': obj.pk, 'value': obj.cached_label} + for obj in objects]) + return HttpResponse(data, content_type='text/plain') + return func + + def check_permission(request, action_slug, obj_id=None): MAIN_MENU = Menu(None) MAIN_MENU.init() |