summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commit8b3347dd635eef93e7af1a71c4183630277a7f5f (patch)
tree68d54bb40b2006fc2a3d5ae938108e9b950eca59
parent07f24f648504004adbb57b16bd18fbd23a3c3998 (diff)
downloadIshtar-8b3347dd635eef93e7af1a71c4183630277a7f5f.tar.bz2
Ishtar-8b3347dd635eef93e7af1a71c4183630277a7f5f.zip
Document form: add related fields (refs #4107)
-rw-r--r--archaeological_finds/urls.py22
-rw-r--r--archaeological_finds/views.py8
-rw-r--r--ishtar_common/forms.py47
-rw-r--r--ishtar_common/forms_common.py53
-rw-r--r--ishtar_common/models.py5
-rw-r--r--ishtar_common/templates/blocks/bs_form_snippet.html15
-rw-r--r--ishtar_common/templates/ishtar/forms/document.html38
-rw-r--r--ishtar_common/templatetags/from_dict.py5
-rw-r--r--ishtar_common/utils.py21
-rw-r--r--ishtar_common/views.py2
-rw-r--r--ishtar_common/views_item.py19
11 files changed, 204 insertions, 31 deletions
diff --git a/archaeological_finds/urls.py b/archaeological_finds/urls.py
index ca7f10296..9a71c66d8 100644
--- a/archaeological_finds/urls.py
+++ b/archaeological_finds/urls.py
@@ -19,7 +19,7 @@
from django.conf.urls import url
-from ishtar_common.utils import check_rights
+from ishtar_common.utils import check_rights, get_urls_for_model
from archaeological_finds import views
from archaeological_operations.views import administrativeactfile_document
@@ -166,10 +166,6 @@ urlpatterns = [
name='autocomplete-integritytype'),
url(r'autocomplete-treatmentfile/$', views.autocomplete_treatmentfile,
name='autocomplete-treatmentfile'),
- url(r'get-find/own/(?P<type>.+)?$', views.get_find,
- name='get-own-find', kwargs={'force_own': True}),
- url(r'get-find/(?P<type>.+)?$', views.get_find,
- name='get-find'),
url(r'get-find-for-ope/own/(?P<type>.+)?$', views.get_find_for_ope,
name='get-own-find-for-ope', kwargs={'force_own': True}),
url(r'get-find-for-ope/(?P<type>.+)?$', views.get_find_for_ope,
@@ -190,21 +186,13 @@ urlpatterns = [
name='show-findbasket'),
url(r'^display-find/basket-(?P<pk>.+)/$', views.display_findbasket,
name='display-findbasket'),
- url(r'^show-find(?:/(?P<pk>.+))?/(?P<type>.+)?$', views.show_find,
- name=models.Find.SHOW_URL),
- url(r'^display-find/(?P<pk>.+)/$', views.display_find,
- name='display-' + models.Find.SLUG),
url(r'^show-historized-find/(?P<pk>.+)?/(?P<date>.+)?$',
views.show_find, name='show-historized-find'),
url(r'^revert-find/(?P<pk>.+)/(?P<date>.+)$',
views.revert_find, name='revert-find'),
- url(r'^get-treatment/(?P<type>.+)?$',
- views.get_treatment, name='get-treatment'),
url(r'get-treatment-shortcut/(?P<type>.+)?$',
views.get_treatment, name='get-treatment-shortcut',
kwargs={'full': 'shortcut'}),
- url(r'^show-treatment(?:/(?P<pk>.+))?/(?P<type>.+)?$', views.show_treatment,
- name=models.Treatment.SHOW_URL),
url(r'show-historized-treatment/(?P<pk>.+)?/(?P<date>.+)?$',
views.show_treatment, name='show-historized-treatment'),
url(r'^revert-treatment/(?P<pk>.+)/(?P<date>.+)$',
@@ -231,6 +219,8 @@ urlpatterns = [
kwargs={'treatment_file': True}),
]
-# url(r'show-treatmentfile(?:/(?P<pk>.+))?/(?P<type>.+)?$',
-# 'show_treatmentfile',
-# name=models.TreatmentFile.SHOW_URL),
+urlpatterns += get_urls_for_model(models.Find, views, own=True,
+ autocomplete=True)
+urlpatterns += get_urls_for_model(models.Treatment, views,
+ autocomplete=True)
+
diff --git a/archaeological_finds/views.py b/archaeological_finds/views.py
index 850151578..76f1977e6 100644
--- a/archaeological_finds/views.py
+++ b/archaeological_finds/views.py
@@ -37,7 +37,7 @@ from ishtar_common.models import IshtarUser, get_current_profile
from ishtar_common.views import get_autocomplete_generic, IshtarMixin, \
LoginRequiredMixin
from ishtar_common.views_item import display_item, get_item, show_item, \
- revert_item
+ revert_item, get_autocomplete_item
from ishtar_common.wizards import SearchWizard
from wizards import *
@@ -50,9 +50,15 @@ get_find_for_treatment = get_item(
models.Find, 'get_find', 'find',
own_table_cols=models.Find.TABLE_COLS_FOR_OPE, base_request={})
+autocomplete_find = get_autocomplete_item(model=models.Find)
+
+
show_treatment = show_item(models.Treatment, 'treatment')
revert_treatment = revert_item(models.Treatment)
get_treatment = get_item(models.Treatment, 'get_treatment', 'treatment')
+display_treatment = display_item(models.Treatment)
+
+autocomplete_treatment = get_autocomplete_item(model=models.Treatment)
get_administrativeacttreatment = get_item(
AdministrativeAct, 'get_administrativeacttreatment',
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()