diff options
| -rw-r--r-- | archaeological_context_records/views.py | 5 | ||||
| -rw-r--r-- | archaeological_files/views.py | 2 | ||||
| -rw-r--r-- | archaeological_finds/views.py | 6 | ||||
| -rw-r--r-- | archaeological_operations/views.py | 2 | ||||
| -rw-r--r-- | archaeological_warehouse/views.py | 2 | ||||
| -rw-r--r-- | ishtar_common/context_processors.py | 2 | ||||
| -rw-r--r-- | ishtar_common/forms_common.py | 35 | ||||
| -rw-r--r-- | ishtar_common/models.py | 61 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/sheet_document.html | 4 | ||||
| -rw-r--r-- | ishtar_common/templatetags/window_field.py | 24 | ||||
| -rw-r--r-- | ishtar_common/urls.py | 8 | ||||
| -rw-r--r-- | ishtar_common/utils.py | 46 | ||||
| -rw-r--r-- | ishtar_common/views.py | 1003 | ||||
| -rw-r--r-- | ishtar_common/views_item.py | 936 | ||||
| -rw-r--r-- | ishtar_common/widgets.py | 54 | 
15 files changed, 1166 insertions, 1024 deletions
| diff --git a/archaeological_context_records/views.py b/archaeological_context_records/views.py index 787b76b31..3fd393a4d 100644 --- a/archaeological_context_records/views.py +++ b/archaeological_context_records/views.py @@ -30,8 +30,9 @@ import models  from archaeological_operations.views import site_extra_context  from forms import *  from ishtar_common.utils import put_session_message -from ishtar_common.views import get_item, show_item, revert_item, \ -    IshtarMixin, LoginRequiredMixin, display_item +from ishtar_common.views import IshtarMixin, LoginRequiredMixin +from ishtar_common.views_item import display_item, get_item, show_item, \ +    revert_item  from ishtar_common.wizards import SearchWizard  from wizards import * diff --git a/archaeological_files/views.py b/archaeological_files/views.py index 86fa83ee4..1ca8d41ac 100644 --- a/archaeological_files/views.py +++ b/archaeological_files/views.py @@ -26,7 +26,7 @@ from django.http import HttpResponse  from django.shortcuts import redirect, render  from django.utils.translation import ugettext_lazy as _ -from ishtar_common.views import get_item, show_item, revert_item +from ishtar_common.views_item import get_item, show_item, revert_item  from archaeological_operations.models import Operation  import models diff --git a/archaeological_finds/views.py b/archaeological_finds/views.py index 2dc599807..850151578 100644 --- a/archaeological_finds/views.py +++ b/archaeological_finds/views.py @@ -34,8 +34,10 @@ from archaeological_operations.wizards import AdministrativeActDeletionWizard  from forms import *  from ishtar_common.forms import FinalForm  from ishtar_common.models import IshtarUser, get_current_profile -from ishtar_common.views import get_item, show_item, display_item, \ -    revert_item, get_autocomplete_generic, IshtarMixin, LoginRequiredMixin +from ishtar_common.views import get_autocomplete_generic, IshtarMixin, \ +    LoginRequiredMixin +from ishtar_common.views_item import display_item, get_item, show_item, \ +    revert_item  from ishtar_common.wizards import SearchWizard  from wizards import * diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py index 5c5a3e89b..165dfd635 100644 --- a/archaeological_operations/views.py +++ b/archaeological_operations/views.py @@ -50,7 +50,7 @@ from ishtar_common.forms import ClosingDateFormSelection, FinalForm, \      FinalDeleteForm  from ishtar_common.models import get_current_profile, IshtarSiteProfile  from ishtar_common.utils import put_session_message, check_rights_condition -from ishtar_common.views import get_item, show_item, revert_item, new_item +from ishtar_common.views_item import get_item, show_item, revert_item, new_item  from ishtar_common.wizards import SearchWizard diff --git a/archaeological_warehouse/views.py b/archaeological_warehouse/views.py index c801fbae6..ec160cbce 100644 --- a/archaeological_warehouse/views.py +++ b/archaeological_warehouse/views.py @@ -26,7 +26,7 @@ from django.shortcuts import redirect  from django.utils.translation import ugettext_lazy as _  from forms import * -from ishtar_common.views import get_item, new_item, show_item +from ishtar_common.views_item import get_item, show_item, new_item  from ishtar_common.wizards import SearchWizard  from wizards import * diff --git a/ishtar_common/context_processors.py b/ishtar_common/context_processors.py index 108fb7ec4..225b004a4 100644 --- a/ishtar_common/context_processors.py +++ b/ishtar_common/context_processors.py @@ -28,7 +28,7 @@ from menus import Menu  def get_base_context(request): -    dct = {'URL_PATH': settings.URL_PATH} +    dct = {'URL_PATH': settings.URL_PATH, 'BASE_URL': ''}      if 'HTTP_HOST' in request.META:          dct['BASE_URL'] = "{}://{}".format(request.scheme,                                             request.META['HTTP_HOST']) diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index 0473d19b6..9abc6551a 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -34,7 +34,7 @@ from django.core.files import File  from django.forms.formsets import formset_factory  from django.forms.models import BaseModelFormSet, BaseFormSet  from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, pgettext  import models  import widgets @@ -1057,24 +1057,30 @@ def get_image_help():  ####################### -class DocumentForm(CustomForm, ManageOldType): -    form_label = _(u"Documentation informations") +class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): +    form_label = _(u"Documentation")      form_admin_name = _("Document - General") - +    form_slug = "document-general"      file_upload = True      associated_models = {'source_type': models.SourceType} +      title = forms.CharField(label=_(u"Title"), required=False,                              validators=[validators.MaxLengthValidator(200)]) -    source_type = forms.ChoiceField(label=_(u"Source type"), choices=[], -                                    required=False) +    source_type = widgets.ModelChoiceField( +        model=models.SourceType, label=_(u"Source type"), choices=[], +        required=False) +    authors = widgets.ModelJQueryAutocompleteField( +        model=models.Author, multiple=True, label=_(u"Authors"), new=True, +        long_widget=True, required=False) +    associated_url = forms.URLField( +        max_length=1000, required=False, +        label=_(u"Numerical ressource (web address)"))      image = forms.ImageField(          label=_(u"Image"), help_text=mark_safe(get_image_help()),          max_length=255, required=False, widget=widgets.ImageFileInput())      associated_file = forms.FileField( -        label=_(u"File"), max_length=255, required=False) -    associated_url = forms.URLField( -        max_length=1000, required=False, -        label=_(u"Numerical ressource (web address)")) +        label=pgettext(u"File", u"Not directory"), max_length=255, +        required=False)      reference = forms.CharField(          label=_(u"Reference"),          validators=[validators.MaxLengthValidator(100)], required=False) @@ -1102,6 +1108,15 @@ class DocumentForm(CustomForm, ManageOldType):          FieldType('source_type', models.SourceType),      ] +    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', +            'comment', 'description', 'additional_information', 'duplicate' +        ] +      def clean(self):          cleaned_data = self.cleaned_data          if not cleaned_data.get('title', None) and \ diff --git a/ishtar_common/models.py b/ishtar_common/models.py index de34a2b9c..d13def4c7 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -88,41 +88,6 @@ def post_save_user(sender, **kwargs):  post_save.connect(post_save_user, sender=User) -def check_model_access_control(request, model, available_perms=None): -    """ -    Check access control to a model for a specific request - -    :param request: the current request -    :param model: the concerned model -    :param available_perms: specific permissions to check if not specified -    "view" and "view_own" will be checked -    :return: (allowed, own) tuple -    """ -    own = True  # more restrictive by default -    allowed = False -    if not request.user.is_authenticated(): -        return allowed, own - -    if not available_perms: -        available_perms = ['view_' + model.__name__.lower(), -                           'view_own_' + model.__name__.lower()] -    if request.user.ishtaruser.has_right('administrator', -                                         session=request.session): -        allowed = True -        own = False -        return allowed, own -    for perm, lbl in model._meta.permissions: -        if perm not in available_perms: -            continue -        if request.user.ishtaruser.person.has_right( -                perm, session=request.session): -            allowed = True -            if "_own_" not in perm: -                own = False -                break  # max right reach -    return allowed, own - -  class ValueGetter(object):      _prefix = ""      GET_VALUES_EXTRA = [] @@ -3128,7 +3093,7 @@ class Document(OwnPerms, ImageModel, FullSearch):      scale = models.CharField(_(u"Scale"), max_length=30, null=True,                               blank=True)      authors = models.ManyToManyField(Author, verbose_name=_(u"Authors"), -                                     related_name="%(class)s_related") +                                     related_name="documents")      authors_raw = models.CharField(verbose_name=_(u"Authors (raw)"),                                     blank=True, null=True, max_length=250)      associated_url = models.URLField( @@ -3182,6 +3147,19 @@ class Document(OwnPerms, ImageModel, FullSearch):                                     self.index)      """ +    @property +    def images(self): +        # mimic a queryset pointing to himself +        return Document.objects.filter(pk=self.pk, +                                       image__isnull=False).exclude(image='') + +    @property +    def has_related(self): +        for rel in self.RELATED_MODELS: +            if getattr(self, rel).count(): +                return True +        return False +      @classmethod      def get_query_owns(cls, ishtaruser):          Operation = cls.operations.rel.related_model @@ -3204,11 +3182,12 @@ class Document(OwnPerms, ImageModel, FullSearch):          return slugify(u"-".join(values))      def _get_base_image_paths(self): -        for related_model in self.RELATED_MODELS: -            q = getattr(self, related_model).all() -            if q.count(): -                item = q.all()[0] -                yield item._get_base_image_path() +        if self.pk:  # m2m not available if not created... +            for related_model in self.RELATED_MODELS: +                q = getattr(self, related_model).all() +                if q.count(): +                    item = q.all()[0] +                    yield item._get_base_image_path()      def _get_base_image_path(self):          for path in self._get_base_image_paths(): diff --git a/ishtar_common/templates/ishtar/sheet_document.html b/ishtar_common/templates/ishtar/sheet_document.html index 3d48963a2..4f067fe56 100644 --- a/ishtar_common/templates/ishtar/sheet_document.html +++ b/ishtar_common/templates/ishtar/sheet_document.html @@ -26,6 +26,8 @@      {% field_flex "Title" item.title %}      {% field_flex "Index" item.index %}      {% field_flex "Source type" item.source_type %} +    {% trans "File" context "Not directory" as file_label %} +    {% field_flex_file file_label item.associated_file %}      {% field_flex "Format type" item.format_type %}      {% field_flex "Scale" item.scale %}      {% trans "Web link" as weblink_label %} @@ -44,8 +46,8 @@  </div>  {% block related %} +{% if item.has_related %}  <h2>{% trans "Related items" %}</h2> -{% if item.operations.count %}  {% field_flex_full "Sites" item.sites|add_links %}  {% field_flex_full "Operations" item.operations|add_links %}  {% field_flex_full "Context records" item.context_records|add_links %} diff --git a/ishtar_common/templatetags/window_field.py b/ishtar_common/templatetags/window_field.py index cbc6f28c3..db57add4a 100644 --- a/ishtar_common/templatetags/window_field.py +++ b/ishtar_common/templatetags/window_field.py @@ -1,4 +1,7 @@ +import os +  from django import template +from django.conf import settings  from django.utils.translation import ugettext_lazy as _  from ishtar_common.templatetags.link_to_window import link_to_window @@ -37,12 +40,13 @@ def field_flex_full(caption, data, pre_data='', post_data=''):  @register.inclusion_tag('ishtar/blocks/window_field_url.html') -def field_url(caption, link, link_name='', li=False): +def field_url(caption, link, link_name='', li=False, get_base_url=False):      if link:          link = link.strip()          if not link.startswith('http://') and not link.startswith('https://'):              link = 'http://' + link -    return {'caption': caption, 'link': link, "link_name": link_name, 'li': li} +    return {'caption': caption, 'link': link, "link_name": link_name, 'li': li, +            'get_base_url': get_base_url}  @register.inclusion_tag('ishtar/blocks/window_field_url.html') @@ -55,6 +59,22 @@ def field_flex_url(caption, link, link_name=''):      return field_url(caption, link, link_name) +@register.inclusion_tag('ishtar/blocks/window_field_url.html', +                        takes_context=True) +def field_file(context, caption, link): +    link_name = "" +    if link and getattr(link, 'path', None): +        link = context['BASE_URL'] + link.url +        link_name = link.split(os.sep)[-1] +    return field_url(caption, link, link_name, get_base_url=True) + + +@register.inclusion_tag('ishtar/blocks/window_field_flex_url.html', +                        takes_context=True) +def field_flex_file(context, caption, link): +    return field_file(context, caption, link) + +  @register.inclusion_tag('ishtar/blocks/window_field_multiple.html')  def field_multiple(caption, data, li=False):      return {'caption': caption, 'data': data, 'li': li} diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index 787c72aba..5ef316723 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -228,13 +228,13 @@ urlpatterns += [          check_rights(['view_document', 'view_own_document'])(              views.document_search_wizard),          name='document_search'), -    url(r'document_creation/(?P<step>.+)?$', +    url(r'document_creation/$',          check_rights(['add_document', 'add_own_document'])( -            views.NewDocumentFormView.as_view()), +            views.DocumentFormView.as_view()),          name='document_creation'), -    url(r'document_modification/(?P<step>.+)?$', +    url(r'document_modification/(?P<pk>.+)/$',          check_rights(['change_document', 'change_own_document'])( -            views.NewDocumentFormView.as_view()), +            views.DocumentFormView.as_view()),          name='document_modification'),  ] diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 0b5b1bd57..443a22111 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -17,6 +17,7 @@  # See the file COPYING for details. +from csv import QUOTE_ALL  import datetime  from functools import wraps  from itertools import chain @@ -37,6 +38,7 @@ from django.contrib.sessions.backends.db import SessionStore  from django.core.cache import cache  from django.core.files import File  from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect  from django.utils.datastructures import MultiValueDict as BaseMultiValueDict  from django.utils.safestring import mark_safe  from django.utils.translation import ugettext_lazy as _, ugettext @@ -57,6 +59,9 @@ class BColors:      UNDERLINE = '\033[4m' +CSV_OPTIONS = {'delimiter': ',', 'quotechar': '"', 'quoting': QUOTE_ALL} + +  def check_rights(rights=[], redirect_url='/'):      """      Decorator that checks the rights to access the view. @@ -103,6 +108,41 @@ def check_rights_condition(rights):      return func +def check_model_access_control(request, model, available_perms=None): +    """ +    Check access control to a model for a specific request + +    :param request: the current request +    :param model: the concerned model +    :param available_perms: specific permissions to check if not specified +    "view" and "view_own" will be checked +    :return: (allowed, own) tuple +    """ +    own = True  # more restrictive by default +    allowed = False +    if not request.user.is_authenticated(): +        return allowed, own + +    if not available_perms: +        available_perms = ['view_' + model.__name__.lower(), +                           'view_own_' + model.__name__.lower()] +    if request.user.ishtaruser.has_right('administrator', +                                         session=request.session): +        allowed = True +        own = False +        return allowed, own +    for perm, lbl in model._meta.permissions: +        if perm not in available_perms: +            continue +        if request.user.ishtaruser.person.has_right( +                perm, session=request.session): +            allowed = True +            if "_own_" not in perm: +                own = False +                break  # max right reach +    return allowed, own + +  class MultiValueDict(BaseMultiValueDict):      def get(self, *args, **kwargs):          v = super(MultiValueDict, self).getlist(*args, **kwargs) @@ -756,10 +796,14 @@ def get_urls_for_model(model, views):      Generate get and show url for a model      """      urls = [ -        url(r'show-{}(?:/(?P<pk>.+))?/(?P<type>.+)?$'.format(model.SLUG), +        url(r'show-{}/(?P<pk>.+)/(?P<type>.+)?$'.format(model.SLUG),              check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])(                  getattr(views, 'show_' + model.SLUG)),              name="show-" + model.SLUG), +        url(r'^display-{}/(?P<pk>.+)/$'.format(model.SLUG), +            check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( +                getattr(views, 'display_' + model.SLUG)), +            name='display-' + model.SLUG),          url(r'get-{}/(?P<type>.+)?$'.format(model.SLUG),              check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])(                  getattr(views, 'get_' + model.SLUG)), diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 187712676..81397dd70 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -17,74 +17,50 @@  # See the file COPYING for details. -from copy import copy, deepcopy  import csv  import datetime  import json  import logging -from markdown import markdown -import optparse -import re -from tempfile import NamedTemporaryFile -from tidylib import tidy_document as tidy  import unicodedata -from unidecode import unidecode -import unicodecsv -from weasyprint import HTML, CSS -from weasyprint.fonts import FontConfiguration - -from extra_views import ModelFormSetView +import unicodecsv  from django.conf import settings  from django.contrib.auth import logout  from django.contrib.auth.decorators import login_required -from django.contrib.postgres.search import SearchQuery -from django.contrib.staticfiles.templatetags.staticfiles import static  from django.core.exceptions import ObjectDoesNotExist  from django.core.urlresolvers import reverse, NoReverseMatch -from django.db.models import Q, ImageField -from django.db.models.fields import FieldDoesNotExist +from django.db.models import Q  from django.forms.models import modelformset_factory  from django.http import HttpResponse, Http404, HttpResponseRedirect, \      HttpResponseBadRequest  from django.shortcuts import redirect, render -from django.template import loader  from django.utils.decorators import method_decorator  from django.utils.translation import ugettext, ugettext_lazy as _ -from django.views.generic import ListView, UpdateView, TemplateView, FormView +from django.views.generic import ListView, UpdateView, TemplateView  from django.views.generic.edit import CreateView, DeleteView, FormView +from extra_views import ModelFormSetView +from markdown import markdown -from xhtml2odt import xhtml2odt - -from menus import Menu - -from archaeological_files.models import File -from archaeological_operations.models import Operation +import models  from archaeological_context_records.models import ContextRecord -from archaeological_finds.models import Find, Treatment, TreatmentFile, \ -    FindBasket - -from archaeological_operations.forms import DashboardForm as DashboardFormOpe  from archaeological_files.forms import DashboardForm as DashboardFormFile +from archaeological_files.models import File  from archaeological_finds.forms import DashboardTreatmentForm, \      DashboardTreatmentFileForm - -from ishtar_common.forms import FinalForm, FinalDeleteForm -from ishtar_common.widgets import JQueryAutoComplete -from ishtar_common.utils import clean_session_cache, \ -    get_all_field_names, get_field_labels_from_path, \ -    get_random_item_image_link, shortify +from archaeological_finds.models import Find, Treatment, TreatmentFile +from archaeological_operations.forms import DashboardForm as DashboardFormOpe +from archaeological_operations.models import Operation  from ishtar_common import forms_common as forms  from ishtar_common import wizards -from ishtar_common.models import HistoryError, PRIVATE_FIELDS, \ -    get_current_profile - +from ishtar_common.forms import FinalForm, FinalDeleteForm +from ishtar_common.models import get_current_profile  from ishtar_common.templatetags.link_to_window import link_to_window +from ishtar_common.utils import clean_session_cache, CSV_OPTIONS, \ +    get_field_labels_from_path, get_random_item_image_link, shortify +from ishtar_common.widgets import JQueryAutoComplete -import models - -CSV_OPTIONS = {'delimiter': ',', 'quotechar': '"', 'quoting': csv.QUOTE_ALL} -ENCODING = settings.ENCODING or 'utf-8' +from views_item import CURRENT_ITEM_KEYS, display_item, get_item, new_item, \ +    show_item  logger = logging.getLogger(__name__) @@ -367,15 +343,6 @@ def shortcut_menu(request):      return render(request, 'ishtar/blocks/shortcut_menu.html', dct) -CURRENT_ITEM_KEYS = (('file', File), -                     ('operation', Operation), -                     ('contextrecord', ContextRecord), -                     ('find', Find), -                     ('treatmentfile', TreatmentFile), -                     ('treatment', Treatment)) -CURRENT_ITEM_KEYS_DICT = dict(CURRENT_ITEM_KEYS) - -  def get_current_items(request):      currents = {}      for key, model in CURRENT_ITEM_KEYS: @@ -466,17 +433,27 @@ def update_current_item(request, item_type=None, pk=None):      return HttpResponse('ok') -def check_permission(request, action_slug, obj_id=None): -    MAIN_MENU = Menu(None) -    MAIN_MENU.init() -    if action_slug not in MAIN_MENU.items: -        # TODO -        return True -    if obj_id: -        return MAIN_MENU.items[action_slug].is_available( -            request.user, obj_id, session=request.session) -    return MAIN_MENU.items[action_slug].can_be_available( -        request.user, session=request.session) +def get_by_importer(request, slug, data_type='json', full=False, +                    force_own=False, **dct): +    q = models.ImporterType.objects.filter(slug=slug) +    if not q.count(): +        res = '' +        if data_type == "json": +            res = '{}' +        return HttpResponse(res, content_type='text/plain') +    imp = q.all()[0].get_importer_class() +    cols, col_names = [], [] +    for formater in imp.LINE_EXPORT_FORMAT: +        if not formater: +            cols.append('') +            col_names.append("") +            continue +        cols.append(formater.export_field_name) +        col_names.append(formater.label) +    obj_name = imp.OBJECT_CLS.__name__.lower() +    return get_item( +        imp.OBJECT_CLS, 'get_' + obj_name, obj_name, own_table_cols=cols +    )(request, data_type, full, force_own, col_names=col_names, **dct)  def autocomplete_person_permissive(request, person_types=None, @@ -603,873 +580,6 @@ def department_by_state(request, state_id=''):      return HttpResponse(data, content_type='text/plain') -def format_val(val): -    if val is None: -        return u"" -    if type(val) == bool: -        if val: -            return unicode(_(u"True")) -        else: -            return unicode(_(u"False")) -    if type(val) == str: -        val = val.decode('utf-8') -    return unicode(val) - - -HIERARCHIC_LEVELS = 5 -HIERARCHIC_FIELDS = ['periods', 'period', 'unit', 'material_types', -                     'material_type', 'conservatory_state', 'object_types'] - - -def _get_values(request, val): -    if hasattr(val, 'all'):  # manage related objects -        vals = list(val.all()) -    else: -        vals = [val] -    new_vals = [] -    for v in vals: -        if callable(v): -            v = v() -        if hasattr(v, 'url'): -            v = request.is_secure() and \ -                'https' or 'http' + '://' + \ -                           request.get_host() + v.url -        new_vals.append(v) -    return new_vals - - -def _search_manage_search_vector(dct): -    if 'search_vector' in dct: -        dct['search_vector'] = SearchQuery( -            unidecode(dct['search_vector']), -            config=settings.ISHTAR_SEARCH_LANGUAGE -        ) -    return dct - - -DEFAULT_ROW_NUMBER = 10 -# length is used by ajax DataTables requests -EXCLUDED_FIELDS = ['length'] - - -def get_item(model, func_name, default_name, extra_request_keys=[], -             base_request=None, bool_fields=[], reversed_bool_fields=[], -             dated_fields=[], associated_models=[], relative_session_names=[], -             specific_perms=[], own_table_cols=None, relation_types_prefix={}, -             do_not_deduplicate=False): -    """ -    Generic treatment of tables - -    :param model: model used for query -    :param func_name: name of the function (used for session storage) -    :param default_name: key used for default search in session -    :param extra_request_keys: default query limitation -    :param base_request: -    :param bool_fields: -    :param reversed_bool_fields: -    :param dated_fields: -    :param associated_models: -    :param relative_session_names: -    :param specific_perms: -    :param own_table_cols: -    :param relation_types_prefix: -    :param do_not_deduplicate: duplication of id can occurs on large queryset a -    mecanism of deduplication is used. But duplicate ids can be normal (for -    instance for record_relations view). -    :return: -    """ -    def func(request, data_type='json', full=False, force_own=False, -             col_names=None, **dct): -        available_perms = [] -        if specific_perms: -            available_perms = specific_perms[:] -        EMPTY = '' -        if 'type' in dct: -            data_type = dct.pop('type') -        if not data_type: -            EMPTY = '[]' -            data_type = 'json' - -        allowed, own = models.check_model_access_control(request, model, -                                                         available_perms) -        if not allowed: -            return HttpResponse(EMPTY, content_type='text/plain') - -        if force_own: -            own = True -        if full == 'shortcut' and 'SHORTCUT_SEARCH' in request.session and \ -                request.session['SHORTCUT_SEARCH'] == 'own': -            own = True - -        # get defaults from model -        if not extra_request_keys and hasattr(model, 'EXTRA_REQUEST_KEYS'): -            my_extra_request_keys = copy(model.EXTRA_REQUEST_KEYS) -        else: -            my_extra_request_keys = copy(extra_request_keys) -        if base_request is None and hasattr(model, 'BASE_REQUEST'): -            my_base_request = copy(model.BASE_REQUEST) -        elif base_request is not None: -            my_base_request = copy(base_request) -        else: -            my_base_request = {} -        if not bool_fields and hasattr(model, 'BOOL_FIELDS'): -            my_bool_fields = model.BOOL_FIELDS[:] -        else: -            my_bool_fields = bool_fields[:] -        if not reversed_bool_fields and hasattr(model, 'REVERSED_BOOL_FIELDS'): -            my_reversed_bool_fields = model.REVERSED_BOOL_FIELDS[:] -        else: -            my_reversed_bool_fields = reversed_bool_fields[:] -        if not dated_fields and hasattr(model, 'DATED_FIELDS'): -            my_dated_fields = model.DATED_FIELDS[:] -        else: -            my_dated_fields = dated_fields[:] -        if not associated_models and hasattr(model, 'ASSOCIATED_MODELS'): -            my_associated_models = model.ASSOCIATED_MODELS[:] -        else: -            my_associated_models = associated_models[:] -        if not relative_session_names and hasattr(model, -                                                  'RELATIVE_SESSION_NAMES'): -            my_relative_session_names = model.RELATIVE_SESSION_NAMES[:] -        else: -            my_relative_session_names = relative_session_names[:] -        if not relation_types_prefix and hasattr(model, -                                                 'RELATION_TYPES_PREFIX'): -            my_relation_types_prefix = copy(model.RELATION_TYPES_PREFIX) -        else: -            my_relation_types_prefix = copy(relation_types_prefix) - -        fields = [model._meta.get_field(k) -                  for k in get_all_field_names(model)] - -        request_keys = dict([ -            (field.name, -             field.name + (hasattr(field, 'rel') and field.rel and '__pk' -                           or '')) -            for field in fields]) -        for associated_model, key in my_associated_models: -            if type(associated_model) in (str, unicode): -                if associated_model not in globals(): -                    continue -                associated_model = globals()[associated_model] -            associated_fields = [ -                associated_model._meta.get_field(k) -                for k in get_all_field_names(associated_model)] -            request_keys.update( -                dict([(key + "__" + field.name, -                       key + "__" + field.name + -                       (hasattr(field, 'rel') and field.rel and '__pk' or '')) -                      for field in associated_fields])) -        request_keys.update(my_extra_request_keys) -        request_items = request.method == 'POST' and request.POST \ -            or request.GET - -        # pager -        try: -            row_nb = int(request_items.get('length')) -        except (ValueError, TypeError): -            row_nb = DEFAULT_ROW_NUMBER -        dct_request_items = {} - -        # filter requested fields -        for k in request_items: -            if k in EXCLUDED_FIELDS: -                continue -            key = k[:] -            if key.startswith('searchprefix_'): -                key = key[len('searchprefix_'):] -            dct_request_items[key] = request_items[k] -        request_items = dct_request_items - -        dct = my_base_request -        if full == 'shortcut': -            dct['cached_label__icontains'] = request.GET.get('term', None) -        and_reqs, or_reqs = [], [] -        try: -            old = 'old' in request_items and int(request_items['old']) -        except ValueError: -            return HttpResponse('[]', content_type='text/plain') - -        # manage relations types -        if 'relation_types' not in my_relation_types_prefix: -            my_relation_types_prefix['relation_types'] = '' -        relation_types = {} -        for rtype_key in my_relation_types_prefix: -            relation_types[my_relation_types_prefix[rtype_key]] = set() -            for k in request_items: -                if k.startswith(rtype_key): -                    relation_types[my_relation_types_prefix[rtype_key]].add( -                        request_items[k]) -                    continue - -        for k in request_keys: -            val = request_items.get(k) -            if not val: -                continue -            req_keys = request_keys[k] -            if type(req_keys) not in (list, tuple): -                dct[req_keys] = val -                continue -            # multiple choice target -            reqs = Q(**{req_keys[0]: val}) -            for req_key in req_keys[1:]: -                q = Q(**{req_key: val}) -                reqs |= q -            and_reqs.append(reqs) - -        pinned_search = "" -        if 'submited' not in request_items and full != 'shortcut': -            # default search -            # an item is selected in the default menu -            if default_name in request.session and \ -               request.session[default_name]: -                value = request.session[default_name] -                if 'basket-' in value: -                    try: -                        dct = {"basket__pk": -                               request.session[default_name].split('-')[-1]} -                        pinned_search = unicode(FindBasket.objects.get( -                            pk=dct["basket__pk"])) -                    except FindBasket.DoesNotExist: -                        pass -                else: -                    try: -                        dct = {"pk": request.session[default_name]} -                        pinned_search = unicode(model._meta.verbose_name)\ -                            + u" - " + unicode( -                                model.objects.get(pk=dct["pk"])) -                    except model.DoesNotExist: -                        pass -            elif dct == (my_base_request or {}): -                # a parent item may be selected in the default menu -                for name, key in my_relative_session_names: -                    if name in request.session and request.session[name] \ -                            and 'basket-' not in request.session[name] \ -                            and name in CURRENT_ITEM_KEYS_DICT: -                        up_model = CURRENT_ITEM_KEYS_DICT[name] -                        try: -                            dct.update({key: request.session[name]}) -                            pinned_search = unicode(up_model._meta.verbose_name)\ -                                + u" - " + unicode( -                                    up_model.objects.get(pk=dct[key])) -                            break -                        except up_model.DoesNotExist: -                            pass -            if (not dct or data_type == 'csv') \ -                    and func_name in request.session: -                dct = request.session[func_name] -        else: -            request.session[func_name] = dct -        for k in (list(my_bool_fields) + list(my_reversed_bool_fields)): -            if k in dct: -                if dct[k] == u"1": -                    dct.pop(k) -                else: -                    dct[k] = dct[k] == u"2" and True or False -                    if k in my_reversed_bool_fields: -                        dct[k] = not dct[k] -                    # check also for empty value with image field -                    field_name = k.split('__')[0] -                    # TODO: can be improved in later version of Django -                    try: -                        c_field = model._meta.get_field(field_name) -                        if k.endswith('__isnull') and \ -                           isinstance(c_field, ImageField): -                            if dct[k]: -                                or_reqs.append( -                                    (k, {k.split('__')[0] + '__exact': ''})) -                            else: -                                dct[k.split('__')[0] + '__regex'] = '.{1}.*' -                    except FieldDoesNotExist: -                        pass -        for k in my_dated_fields: -            if k in dct: -                if not dct[k]: -                    dct.pop(k) -                try: -                    items = dct[k].split('/') -                    assert len(items) == 3 -                    dct[k] = datetime.date(*map(lambda x: int(x), -                                                reversed(items)))\ -                                     .strftime('%Y-%m-%d') -                except AssertionError: -                    dct.pop(k) -        # manage hierarchic conditions -        for req in dct.copy(): -            if req.endswith('town__pk') or req.endswith('towns__pk'): -                val = dct.pop(req) -                reqs = Q(**{req: val}) -                base_req = req[:-2] + '__' -                req = base_req[:] -                for idx in range(HIERARCHIC_LEVELS): -                    req = req[:-2] + 'parents__pk' -                    q = Q(**{req: val}) -                    reqs |= q -                req = base_req[:] -                for idx in range(HIERARCHIC_LEVELS): -                    req = req[:-2] + 'children__pk' -                    q = Q(**{req: val}) -                    reqs |= q -                and_reqs.append(reqs) -                continue - -            for k_hr in HIERARCHIC_FIELDS: -                if type(req) in (list, tuple): -                    val = dct.pop(req) -                    q = None -                    for idx, r in enumerate(req): -                        if not idx: -                            q = Q(**{r: val}) -                        else: -                            q |= Q(**{r: val}) -                    and_reqs.append(q) -                    break -                elif req.endswith(k_hr + '__pk'): -                    val = dct.pop(req) -                    reqs = Q(**{req: val}) -                    req = req[:-2] + '__' -                    for idx in range(HIERARCHIC_LEVELS): -                        req = req[:-2] + 'parent__pk' -                        q = Q(**{req: val}) -                        reqs |= q -                    and_reqs.append(reqs) -                    break -        dct = _search_manage_search_vector(dct) -        query = Q(**dct) -        for k, or_req in or_reqs: -            alt_dct = dct.copy() -            alt_dct.pop(k) -            alt_dct.update(or_req) -            query |= Q(**alt_dct) - -        for rtype_prefix in relation_types: -            vals = list(relation_types[rtype_prefix]) -            if not vals: -                continue -            alt_dct = { -                rtype_prefix + 'right_relations__relation_type__pk__in': vals} -            for k in dct: -                val = dct[k] -                if rtype_prefix: -                    # only get conditions related to the object -                    if rtype_prefix not in k: -                        continue -                    # tricky: reconstruct the key to make sense - remove the -                    # prefix from the key -                    k = k[0:k.index(rtype_prefix)] + k[ -                        k.index(rtype_prefix) + len(rtype_prefix):] -                if k.endswith('year'): -                    k += '__exact' -                alt_dct[rtype_prefix + 'right_relations__right_record__' + k] =\ -                    val -            if not dct: -                # fake condition to trick Django (1.4): without it only the -                # alt_dct is managed -                query &= Q(pk__isnull=False) -            query |= Q(**alt_dct) -            for k, or_req in or_reqs: -                altor_dct = alt_dct.copy() -                altor_dct.pop(k) -                for j in or_req: -                    val = or_req[j] -                    if j == 'year': -                        j = 'year__exact' -                    altor_dct[ -                        rtype_prefix + 'right_relations__right_record__' + j] =\ -                        val -                query |= Q(**altor_dct) - -        if own: -            q = models.IshtarUser.objects.filter(user_ptr=request.user) -            if q.count(): -                query = query & model.get_query_owns(q.all()[0]) -            else: -                return HttpResponse(EMPTY, content_type='text/plain') - -        for and_req in and_reqs: -            query = query & and_req - -        # manage hierarchic in shortcut menu -        if full == 'shortcut': -            ASSOCIATED_ITEMS = { -                Operation: (File, 'associated_file__pk'), -                ContextRecord: (Operation, 'operation__pk'), -                Find: (ContextRecord, 'base_finds__context_record__pk'), -            } -            if model in ASSOCIATED_ITEMS: -                upper_model, upper_key = ASSOCIATED_ITEMS[model] -                model_name = upper_model.SLUG -                current = model_name in request.session \ -                    and request.session[model_name] -                if current: -                    dct = {upper_key: current} -                    query &= Q(**dct) - -        items = model.objects.filter(query).distinct() -        # print(items.query) - -        if 'search_vector' in dct:  # for serialization -            dct['search_vector'] = dct['search_vector'].value - -        # table cols -        if own_table_cols: -            table_cols = own_table_cols -        else: -            if full: -                table_cols = [field.name for field in model._meta.fields -                              if field.name not in PRIVATE_FIELDS] -                table_cols += [field.name for field in model._meta.many_to_many -                               if field.name not in PRIVATE_FIELDS] -                if hasattr(model, 'EXTRA_FULL_FIELDS'): -                    table_cols += model.EXTRA_FULL_FIELDS -            else: -                table_cols = model.TABLE_COLS -        query_table_cols = [] -        for cols in table_cols: -            if type(cols) not in (list, tuple): -                cols = [cols] -            for col in cols: -                query_table_cols += col.split('|') - -        # contextual (full, simple, etc.) col -        contxt = full and 'full' or 'simple' -        if hasattr(model, 'CONTEXTUAL_TABLE_COLS') and \ -                contxt in model.CONTEXTUAL_TABLE_COLS: -            for idx, col in enumerate(table_cols): -                if col in model.CONTEXTUAL_TABLE_COLS[contxt]: -                    query_table_cols[idx] = \ -                        model.CONTEXTUAL_TABLE_COLS[contxt][col] -        if full == 'shortcut': -            query_table_cols = ['cached_label'] -            table_cols = ['cached_label'] - -        # manage sort tables -        manual_sort_key = None - -        sorts = {} -        for k in request_items: -            if not k.startswith('order['): -                continue -            num = int(k.split(']')[0][len("order["):]) -            if num not in sorts: -                sorts[num] = ['', '']  # sign, col_num -            if k.endswith('[dir]'): -                order = request_items[k] -                sign = order and order == u'desc' and "-" or '' -                sorts[num][0] = sign -            if k.endswith('[column]'): -                sorts[num][1] = request_items[k] -        sign = "" -        if not sorts and model._meta.ordering: -            orders = [k for k in model._meta.ordering] -            items = items.order_by(*orders) -        else: -            orders = [] -            for idx in sorted(sorts.keys()): -                signe, col_num = sorts[idx] -                k = query_table_cols[int(col_num) - 2]  # remove id and link col -                if k in request_keys: -                    ks = request_keys[k] -                    if type(ks) not in (tuple, list): -                        ks = [ks] -                    for k in ks: -                        if k.endswith("__pk"): -                            k = k[:-len("__pk")] + "__label" -                        if '__' in k: -                            k = k.split('__')[0] -                        orders.append(signe + k) -                else: -                    # not a standard request key -                    if idx:  # not the first - we ignore this sort -                        continue -                    sign = signe -                    manual_sort_key = k -                    logger.warning( -                        "**WARN get_item - {}**: manual sort key '{}'".format( -                            func_name, k)) -                    break -            if not manual_sort_key: -                items = items.order_by(*orders) - -        # pager management -        start, end = 0, None -        page_nb = 1 -        if row_nb and data_type == "json": -            try: -                start = int(request_items.get('start')) -                page_nb = start / row_nb + 1 -                assert page_nb >= 1 -            except (TypeError, ValueError, AssertionError): -                start = 0 -                page_nb = 1 -            end = page_nb * row_nb -        if full == 'shortcut': -            start = 0 -            end = 20 - -        items_nb = items.count() -        if manual_sort_key: -            items = items.all() -        else: -            items = items[start:end] - -        datas = [] -        if old: -            items = [item.get_previous(old) for item in items] -        c_ids = [] -        for item in items: -            # manual deduplicate when distinct is not enough -            if not do_not_deduplicate and item.pk in c_ids: -                continue -            c_ids.append(item.pk) -            data = [item.pk] -            for keys in query_table_cols: -                if type(keys) not in (list, tuple): -                    keys = [keys] -                my_vals = [] -                for k in keys: -                    if hasattr(model, 'EXTRA_REQUEST_KEYS') \ -                            and k in model.EXTRA_REQUEST_KEYS: -                        k = model.EXTRA_REQUEST_KEYS[k] -                        if type(k) in (list, tuple): -                            k = k[0] -                    for filtr in ('__icontains', '__contains'): -                        if k.endswith(filtr): -                            k = k[:len(k) - len(filtr)] -                    vals = [item] -                    # foreign key may be divided by "." or "__" -                    splitted_k = [] -                    for ky in k.split('.'): -                        if '__' in ky: -                            splitted_k += ky.split('__') -                        else: -                            splitted_k.append(ky) -                    for ky in splitted_k: -                        new_vals = [] -                        for val in vals: -                            if hasattr(val, 'all'):  # manage related objects -                                val = list(val.all()) -                                for v in val: -                                    v = getattr(v, ky) -                                    new_vals += _get_values(request, v) -                            elif val: -                                try: -                                    val = getattr(val, ky) -                                    new_vals += _get_values(request, val) -                                except AttributeError: -                                    # must be a query key such as "contains" -                                    pass -                        vals = new_vals -                    # manage last related objects -                    if vals and hasattr(vals[0], 'all'): -                        new_vals = [] -                        for val in vals: -                            new_vals += list(val.all()) -                        vals = new_vals -                    if not my_vals: -                        my_vals = [format_val(va) for va in vals] -                    else: -                        new_vals = [] -                        if not vals: -                            for idx, my_v in enumerate(my_vals): -                                new_vals.append(u"{}{}{}".format( -                                    my_v, u' - ', '')) -                        else: -                            for idx, v in enumerate(vals): -                                new_vals.append(u"{}{}{}".format( -                                    vals[idx], u' - ', format_val(v))) -                        my_vals = new_vals[:] -                data.append(u" & ".join(my_vals) or u"") -            datas.append(data) -        if manual_sort_key: -            # +1 because the id is added as a first col -            idx_col = None -            if manual_sort_key in query_table_cols: -                idx_col = query_table_cols.index(manual_sort_key) + 1 -            else: -                for idx, col in enumerate(query_table_cols): -                    if type(col) in (list, tuple) and \ -                            manual_sort_key in col: -                        idx_col = idx + 1 -            if idx_col is not None: -                datas = sorted(datas, key=lambda x: x[idx_col]) -                if sign == '-': -                    datas = reversed(datas) -                datas = list(datas)[start:end] -        link_template = "<a class='display_details' href='#' "\ -            "onclick='load_window(\"%s\")'>"\ -            "<i class=\"fa fa-info-circle\" aria-hidden=\"true\"></i></a>" -        link_ext_template = '<a href="{}" target="_blank">{}</a>' -        if data_type == "json": -            rows = [] -            for data in datas: -                try: -                    lnk = link_template % reverse('show-' + default_name, -                                                  args=[data[0], '']) -                except NoReverseMatch: -                    logger.warning( -                        '**WARN "show-' + default_name + '" args (' -                        + unicode(data[0]) + ") url not available") -                    lnk = '' -                res = {'id': data[0], 'link': lnk} -                for idx, value in enumerate(data[1:]): -                    if value: -                        table_col = table_cols[idx] -                        if type(table_col) not in (list, tuple): -                            table_col = [table_col] -                        tab_cols = [] -                        # foreign key may be divided by "." or "__" -                        for tc in table_col: -                            if '.' in tc: -                                tab_cols += tc.split('.') -                            elif '__' in tc: -                                tab_cols += tc.split('__') -                            else: -                                tab_cols.append(tc) -                        k = "__".join(tab_cols) -                        if hasattr(model, 'COL_LINK') and k in model.COL_LINK: -                            value = link_ext_template.format(value, value) -                        res[k] = value -                if full == 'shortcut' and 'cached_label' in res: -                    res['value'] = res.pop('cached_label') -                rows.append(res) -            if full == 'shortcut': -                data = json.dumps(rows) -            else: -                data = json.dumps({ -                    "recordsTotal": items_nb, -                    "recordsFiltered": items_nb, -                    "rows": rows, -                    "pinned-search": pinned_search, -                    "page": page_nb, -                    "total": (items_nb / row_nb + 1) if row_nb else items_nb, -                }) -            return HttpResponse(data, content_type='text/plain') -        elif data_type == "csv": -            response = HttpResponse(content_type='text/csv') -            n = datetime.datetime.now() -            filename = u'%s_%s.csv' % (default_name, -                                       n.strftime('%Y%m%d-%H%M%S')) -            response['Content-Disposition'] = 'attachment; filename=%s'\ -                                              % filename -            writer = csv.writer(response, **CSV_OPTIONS) -            if col_names: -                col_names = [name.encode(ENCODING, errors='replace') -                             for name in col_names] -            else: -                col_names = [] -                for field_name in table_cols: -                    if type(field_name) in (list, tuple): -                        field_name = u" & ".join(field_name) -                    if hasattr(model, 'COL_LABELS') and\ -                            field_name in model.COL_LABELS: -                        field = model.COL_LABELS[field_name] -                        col_names.append(unicode(field).encode(ENCODING)) -                        continue -                    else: -                        try: -                            field = model._meta.get_field(field_name) -                        except: -                            col_names.append(u"".encode(ENCODING)) -                            logger.warning( -                                "**WARN get_item - csv export**: no col name " -                                "for {}\nadd explicit label to " -                                "COL_LABELS attribute of " -                                "{}".format(field_name, model)) -                            continue -                        col_names.append( -                            unicode(field.verbose_name).encode(ENCODING)) -            writer.writerow(col_names) -            for data in datas: -                row, delta = [], 0 -                # regroup cols with join "|" -                for idx, col_name in enumerate(table_cols): -                    if len(data[1:]) <= idx + delta: -                        break -                    val = data[1:][idx + delta].encode( -                        ENCODING, errors='replace') -                    if col_name and "|" in col_name[0]: -                        for delta_idx in range( -                                len(col_name[0].split('|')) - 1): -                            delta += 1 -                            val += data[1:][idx + delta].encode( -                                ENCODING, errors='replace') -                    row.append(val) -                writer.writerow(row) -            return response -        return HttpResponse('{}', content_type='text/plain') - -    return func - - -def get_by_importer(request, slug, data_type='json', full=False, -                    force_own=False, **dct): -    q = models.ImporterType.objects.filter(slug=slug) -    if not q.count(): -        res = '' -        if data_type == "json": -            res = '{}' -        return HttpResponse(res, content_type='text/plain') -    imp = q.all()[0].get_importer_class() -    cols, col_names = [], [] -    for formater in imp.LINE_EXPORT_FORMAT: -        if not formater: -            cols.append('') -            col_names.append("") -            continue -        cols.append(formater.export_field_name) -        col_names.append(formater.label) -    obj_name = imp.OBJECT_CLS.__name__.lower() -    return get_item( -        imp.OBJECT_CLS, 'get_' + obj_name, obj_name, own_table_cols=cols -    )(request, data_type, full, force_own, col_names=col_names, **dct) - - -def display_item(model, extra_dct=None, show_url=None): -    def func(request, pk, **dct): -        if show_url: -            dct['show_url'] = "/{}{}/".format(show_url, pk) -        else: -            dct['show_url'] = "/show-{}/{}/".format(model.SLUG, pk) -        return render(request, 'ishtar/display_item.html', dct) -    return func - - -def show_item(model, name, extra_dct=None): -    def func(request, pk, **dct): -        allowed, own = models.check_model_access_control(request, model) -        if not allowed: -            return HttpResponse('', content_type="application/xhtml") -        q = model.objects -        if own: -            query_own = model.get_query_owns(request.user) -            if query_own: -                q = q.filter(query_own) -        try: -            item = q.get(pk=pk) -        except ObjectDoesNotExist: -            return HttpResponse('NOK') -        doc_type = 'type' in dct and dct.pop('type') -        url_name = u"/".join(reverse('show-' + name, args=['0', ''] -                                     ).split('/')[:-2]) + u"/" -        dct['CURRENCY'] = get_current_profile().currency -        dct['ENCODING'] = settings.ENCODING -        dct['DOT_GENERATION'] = settings.DOT_BINARY and True -        dct['current_window_url'] = url_name -        date = None -        if 'date' in dct: -            date = dct.pop('date') -        dct['sheet_id'] = "%s-%d" % (name, item.pk) -        dct['window_id'] = "%s-%d-%s" % ( -            name, item.pk, datetime.datetime.now().strftime('%M%s')) -        if hasattr(item, 'history'): -            if date: -                try: -                    date = datetime.datetime.strptime(date, -                                                      '%Y-%m-%dT%H:%M:%S.%f') -                    item = item.get_previous(date=date) -                    assert item is not None -                except (ValueError, AssertionError): -                    return HttpResponse(None, content_type='text/plain') -                dct['previous'] = item._previous -                dct['next'] = item._next -            else: -                historized = item.history.all() -                if historized: -                    item.history_date = historized[0].history_date -                if len(historized) > 1: -                    dct['previous'] = historized[1].history_date -        dct['item'], dct['item_name'] = item, name -        # add context -        if extra_dct: -            dct.update(extra_dct(request, item)) -        context_instance = deepcopy(dct) -        context_instance['output'] = 'html' -        if hasattr(item, 'history_object'): -            filename = item.history_object.associated_filename -        else: -            filename = item.associated_filename -        if doc_type == "odt" and settings.ODT_TEMPLATE: -            tpl = loader.get_template('ishtar/sheet_%s.html' % name) -            context_instance['output'] = 'ODT' -            content = tpl.render(context_instance, request) -            try: -                tidy_options = {'output-xhtml': 1, 'indent': 1, -                                'tidy-mark': 0, 'doctype': 'auto', -                                'add-xml-decl': 1, 'wrap': 1} -                html, errors = tidy(content, options=tidy_options) -                html = html.encode('utf-8').replace(" ", " ") -                html = re.sub('<pre([^>]*)>\n', '<pre\\1>', html) - -                odt = NamedTemporaryFile() -                options = optparse.Values() -                options.with_network = True -                for k, v in (('input', ''), -                             ('output', odt.name), -                             ('template', settings.ODT_TEMPLATE), -                             ('with_network', True), -                             ('top_header_level', 1), -                             ('img_width', '8cm'), -                             ('img_height', '6cm'), -                             ('verbose', False), -                             ('replace_keyword', 'ODT-INSERT'), -                             ('cut_start', 'ODT-CUT-START'), -                             ('htmlid', None), -                             ('url', "#")): -                    setattr(options, k, v) -                odtfile = xhtml2odt.ODTFile(options) -                odtfile.open() -                odtfile.import_xhtml(html) -                odtfile = odtfile.save() -            except xhtml2odt.ODTExportError: -                return HttpResponse(content, content_type="application/xhtml") -            response = HttpResponse( -                content_type='application/vnd.oasis.opendocument.text') -            response['Content-Disposition'] = 'attachment; filename=%s.odt' % \ -                                              filename -            response.write(odtfile) -            return response -        elif doc_type == 'pdf': -            tpl = loader.get_template('ishtar/sheet_%s_pdf.html' % name) -            context_instance['output'] = 'PDF' -            html = tpl.render(context_instance, request) -            font_config = FontConfiguration() -            css = CSS(string=''' -            @font-face { -                font-family: Gentium; -                src: url(%s); -            } -            body{ -                font-family: Gentium -            } -            ''' % (static("gentium/GentiumPlus-R.ttf"))) -            css2 = CSS(filename=settings.STATIC_ROOT + '/media/style_basic.css') -            pdf = HTML(string=html, base_url=request.build_absolute_uri() -                       ).write_pdf(stylesheets=[css, css2], -                                   font_config=font_config) -            response = HttpResponse(pdf, content_type='application/pdf') -            response['Content-Disposition'] = 'attachment; filename=%s.pdf' % \ -                                              filename -            return response -        else: -            tpl = loader.get_template('ishtar/sheet_%s_window.html' % name) -            content = tpl.render(context_instance, request) -            return HttpResponse(content, content_type="application/xhtml") -    return func - - -def revert_item(model): -    def func(request, pk, date, **dct): -        try: -            item = model.objects.get(pk=pk) -            date = datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%f') -            item.rollback(date) -        except (ObjectDoesNotExist, ValueError, HistoryError): -            return HttpResponse(None, content_type='text/plain') -        return HttpResponse("True", content_type='text/plain') -    return func - -  def autocomplete_organization(request, orga_type=None):      if (not request.user.has_perm('ishtar_common.view_organization',          models.Organization) and @@ -1521,31 +631,6 @@ def autocomplete_author(request):      return HttpResponse(data, content_type='text/plain') -def new_item(model, frm, many=False): -    def func(request, parent_name, limits=''): -        model_name = model._meta.object_name -        if not check_permission(request, 'add_' + model_name.lower()): -            not_permitted_msg = ugettext(u"Operation not permitted.") -            return HttpResponse(not_permitted_msg) -        dct = {'title': unicode(_(u'New %s' % model_name.lower())), -               'many': many} -        if request.method == 'POST': -            dct['form'] = frm(request.POST, limits=limits) -            if dct['form'].is_valid(): -                new_item = dct['form'].save(request.user) -                dct['new_item_label'] = unicode(new_item) -                dct['new_item_pk'] = new_item.pk -                dct['parent_name'] = parent_name -                dct['parent_pk'] = parent_name -                if dct['parent_pk'] and '_select_' in dct['parent_pk']: -                    parents = dct['parent_pk'].split('_') -                    dct['parent_pk'] = "_".join([parents[0]] + parents[2:]) -                return render(request, 'window.html', dct) -        else: -            dct['form'] = frm(limits=limits) -        return render(request, 'window.html', dct) -    return func -  new_person = new_item(models.Person, forms.PersonForm)  new_person_noorga = new_item(models.Person, forms.NoOrgaPersonForm)  new_organization = new_item(models.Organization, forms.OrganizationForm) @@ -2488,6 +1573,7 @@ class OrganizationPersonEdit(LoginRequiredMixin, UpdateView):  show_document = show_item(models.Document, 'document')  get_document = get_item(models.Document, 'get_document', 'document') +display_document = display_item(models.Document)  document_search_wizard = wizards.SearchWizard.as_view( @@ -2497,11 +1583,16 @@ document_search_wizard = wizards.SearchWizard.as_view(  ) -class NewDocumentFormView(IshtarMixin, LoginRequiredMixin, -                          FormView): +class DocumentFormView(IshtarMixin, LoginRequiredMixin, +                       CreateView): +    page_name = _(u"New Document")      form_class = forms.DocumentForm      template_name = 'ishtar/form.html' -    success_url = 'document_search' +    model = models.Document + +    def get_success_url(self): +        return reverse('display-document', args=[self.object.pk]) +  """  class DocumentSelectMixin(IshtarMixin, LoginRequiredMixin, diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py new file mode 100644 index 000000000..eef3440bc --- /dev/null +++ b/ishtar_common/views_item.py @@ -0,0 +1,936 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import csv +import datetime +import json +import logging +import optparse +import re +from copy import copy, deepcopy +from tempfile import NamedTemporaryFile + +from django.conf import settings +from django.contrib.postgres.search import SearchQuery +from django.contrib.staticfiles.templatetags.staticfiles import static +from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse, NoReverseMatch +from django.db.models import Q, ImageField +from django.db.models.fields import FieldDoesNotExist +from django.http import HttpResponse +from django.shortcuts import render +from django.template import loader +from django.utils.translation import ugettext, ugettext_lazy as _ +from tidylib import tidy_document as tidy +from unidecode import unidecode +from weasyprint import HTML, CSS +from weasyprint.fonts import FontConfiguration +from xhtml2odt import xhtml2odt + +from ishtar_common.utils import check_model_access_control, CSV_OPTIONS, \ +    get_all_field_names +from ishtar_common.models import HistoryError, get_current_profile, \ +    PRIVATE_FIELDS +from menus import Menu + +import models +from archaeological_files.models import File +from archaeological_operations.models import Operation +from archaeological_context_records.models import ContextRecord +from archaeological_finds.models import Find, FindBasket, Treatment, \ +    TreatmentFile + +logger = logging.getLogger(__name__) + +ENCODING = settings.ENCODING or 'utf-8' + +CURRENT_ITEM_KEYS = (('file', File), +                     ('operation', Operation), +                     ('contextrecord', ContextRecord), +                     ('find', Find), +                     ('treatmentfile', TreatmentFile), +                     ('treatment', Treatment)) +CURRENT_ITEM_KEYS_DICT = dict(CURRENT_ITEM_KEYS) + + +def check_permission(request, action_slug, obj_id=None): +    MAIN_MENU = Menu(None) +    MAIN_MENU.init() +    if action_slug not in MAIN_MENU.items: +        # TODO +        return True +    if obj_id: +        return MAIN_MENU.items[action_slug].is_available( +            request.user, obj_id, session=request.session) +    return MAIN_MENU.items[action_slug].can_be_available( +        request.user, session=request.session) + + +def new_item(model, frm, many=False): +    def func(request, parent_name, limits=''): +        model_name = model._meta.object_name +        if not check_permission(request, 'add_' + model_name.lower()): +            not_permitted_msg = ugettext(u"Operation not permitted.") +            return HttpResponse(not_permitted_msg) +        dct = {'title': unicode(_(u'New %s' % model_name.lower())), +               'many': many} +        if request.method == 'POST': +            dct['form'] = frm(request.POST, limits=limits) +            if dct['form'].is_valid(): +                new_item = dct['form'].save(request.user) +                dct['new_item_label'] = unicode(new_item) +                dct['new_item_pk'] = new_item.pk +                dct['parent_name'] = parent_name +                dct['parent_pk'] = parent_name +                if dct['parent_pk'] and '_select_' in dct['parent_pk']: +                    parents = dct['parent_pk'].split('_') +                    dct['parent_pk'] = "_".join([parents[0]] + parents[2:]) +                return render(request, 'window.html', dct) +        else: +            dct['form'] = frm(limits=limits) +        return render(request, 'window.html', dct) +    return func + + +def display_item(model, extra_dct=None, show_url=None): +    def func(request, pk, **dct): +        if show_url: +            dct['show_url'] = "/{}{}/".format(show_url, pk) +        else: +            dct['show_url'] = "/show-{}/{}/".format(model.SLUG, pk) +        return render(request, 'ishtar/display_item.html', dct) +    return func + + +def show_item(model, name, extra_dct=None): +    def func(request, pk, **dct): +        allowed, own = check_model_access_control(request, model) +        if not allowed: +            return HttpResponse('', content_type="application/xhtml") +        q = model.objects +        if own: +            query_own = model.get_query_owns(request.user) +            if query_own: +                q = q.filter(query_own) +        try: +            item = q.get(pk=pk) +        except ObjectDoesNotExist: +            return HttpResponse('NOK') +        doc_type = 'type' in dct and dct.pop('type') +        url_name = u"/".join(reverse('show-' + name, args=['0', ''] +                                     ).split('/')[:-2]) + u"/" +        dct['CURRENCY'] = get_current_profile().currency +        dct['ENCODING'] = settings.ENCODING +        dct['DOT_GENERATION'] = settings.DOT_BINARY and True +        dct['current_window_url'] = url_name +        date = None +        if 'date' in dct: +            date = dct.pop('date') +        dct['sheet_id'] = "%s-%d" % (name, item.pk) +        dct['window_id'] = "%s-%d-%s" % ( +            name, item.pk, datetime.datetime.now().strftime('%M%s')) +        if hasattr(item, 'history'): +            if date: +                try: +                    date = datetime.datetime.strptime(date, +                                                      '%Y-%m-%dT%H:%M:%S.%f') +                    item = item.get_previous(date=date) +                    assert item is not None +                except (ValueError, AssertionError): +                    return HttpResponse(None, content_type='text/plain') +                dct['previous'] = item._previous +                dct['next'] = item._next +            else: +                historized = item.history.all() +                if historized: +                    item.history_date = historized[0].history_date +                if len(historized) > 1: +                    dct['previous'] = historized[1].history_date +        dct['item'], dct['item_name'] = item, name +        # add context +        if extra_dct: +            dct.update(extra_dct(request, item)) +        context_instance = deepcopy(dct) +        context_instance['output'] = 'html' +        if hasattr(item, 'history_object'): +            filename = item.history_object.associated_filename +        else: +            filename = item.associated_filename +        if doc_type == "odt" and settings.ODT_TEMPLATE: +            tpl = loader.get_template('ishtar/sheet_%s.html' % name) +            context_instance['output'] = 'ODT' +            content = tpl.render(context_instance, request) +            try: +                tidy_options = {'output-xhtml': 1, 'indent': 1, +                                'tidy-mark': 0, 'doctype': 'auto', +                                'add-xml-decl': 1, 'wrap': 1} +                html, errors = tidy(content, options=tidy_options) +                html = html.encode('utf-8').replace(" ", " ") +                html = re.sub('<pre([^>]*)>\n', '<pre\\1>', html) + +                odt = NamedTemporaryFile() +                options = optparse.Values() +                options.with_network = True +                for k, v in (('input', ''), +                             ('output', odt.name), +                             ('template', settings.ODT_TEMPLATE), +                             ('with_network', True), +                             ('top_header_level', 1), +                             ('img_width', '8cm'), +                             ('img_height', '6cm'), +                             ('verbose', False), +                             ('replace_keyword', 'ODT-INSERT'), +                             ('cut_start', 'ODT-CUT-START'), +                             ('htmlid', None), +                             ('url', "#")): +                    setattr(options, k, v) +                odtfile = xhtml2odt.ODTFile(options) +                odtfile.open() +                odtfile.import_xhtml(html) +                odtfile = odtfile.save() +            except xhtml2odt.ODTExportError: +                return HttpResponse(content, content_type="application/xhtml") +            response = HttpResponse( +                content_type='application/vnd.oasis.opendocument.text') +            response['Content-Disposition'] = 'attachment; filename=%s.odt' % \ +                                              filename +            response.write(odtfile) +            return response +        elif doc_type == 'pdf': +            tpl = loader.get_template('ishtar/sheet_%s_pdf.html' % name) +            context_instance['output'] = 'PDF' +            html = tpl.render(context_instance, request) +            font_config = FontConfiguration() +            css = CSS(string=''' +            @font-face { +                font-family: Gentium; +                src: url(%s); +            } +            body{ +                font-family: Gentium +            } +            ''' % (static("gentium/GentiumPlus-R.ttf"))) +            css2 = CSS(filename=settings.STATIC_ROOT + '/media/style_basic.css') +            pdf = HTML(string=html, base_url=request.build_absolute_uri() +                       ).write_pdf(stylesheets=[css, css2], +                                   font_config=font_config) +            response = HttpResponse(pdf, content_type='application/pdf') +            response['Content-Disposition'] = 'attachment; filename=%s.pdf' % \ +                                              filename +            return response +        else: +            tpl = loader.get_template('ishtar/sheet_%s_window.html' % name) +            content = tpl.render(context_instance, request) +            return HttpResponse(content, content_type="application/xhtml") +    return func + + +def revert_item(model): +    def func(request, pk, date, **dct): +        try: +            item = model.objects.get(pk=pk) +            date = datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%f') +            item.rollback(date) +        except (ObjectDoesNotExist, ValueError, HistoryError): +            return HttpResponse(None, content_type='text/plain') +        return HttpResponse("True", content_type='text/plain') +    return func + + +HIERARCHIC_LEVELS = 5 +HIERARCHIC_FIELDS = ['periods', 'period', 'unit', 'material_types', +                     'material_type', 'conservatory_state', 'object_types'] + + +def _get_values(request, val): +    if hasattr(val, 'all'):  # manage related objects +        vals = list(val.all()) +    else: +        vals = [val] +    new_vals = [] +    for v in vals: +        if callable(v): +            v = v() +        if hasattr(v, 'url'): +            v = request.is_secure() and \ +                'https' or 'http' + '://' + \ +                request.get_host() + v.url +        new_vals.append(v) +    return new_vals + + +def _search_manage_search_vector(dct): +    if 'search_vector' in dct: +        dct['search_vector'] = SearchQuery( +            unidecode(dct['search_vector']), +            config=settings.ISHTAR_SEARCH_LANGUAGE +        ) +    return dct + + +def _format_val(val): +    if val is None: +        return u"" +    if type(val) == bool: +        if val: +            return unicode(_(u"True")) +        else: +            return unicode(_(u"False")) +    if type(val) == str: +        val = val.decode('utf-8') +    return unicode(val) + + +DEFAULT_ROW_NUMBER = 10 +# length is used by ajax DataTables requests +EXCLUDED_FIELDS = ['length'] + + +def get_item(model, func_name, default_name, extra_request_keys=[], +             base_request=None, bool_fields=[], reversed_bool_fields=[], +             dated_fields=[], associated_models=[], relative_session_names=[], +             specific_perms=[], own_table_cols=None, relation_types_prefix={}, +             do_not_deduplicate=False): +    """ +    Generic treatment of tables + +    :param model: model used for query +    :param func_name: name of the function (used for session storage) +    :param default_name: key used for default search in session +    :param extra_request_keys: default query limitation +    :param base_request: +    :param bool_fields: +    :param reversed_bool_fields: +    :param dated_fields: +    :param associated_models: +    :param relative_session_names: +    :param specific_perms: +    :param own_table_cols: +    :param relation_types_prefix: +    :param do_not_deduplicate: duplication of id can occurs on large queryset a +    mecanism of deduplication is used. But duplicate ids can be normal (for +    instance for record_relations view). +    :return: +    """ +    def func(request, data_type='json', full=False, force_own=False, +             col_names=None, **dct): +        available_perms = [] +        if specific_perms: +            available_perms = specific_perms[:] +        EMPTY = '' +        if 'type' in dct: +            data_type = dct.pop('type') +        if not data_type: +            EMPTY = '[]' +            data_type = 'json' + +        allowed, own = check_model_access_control(request, model, +                                                  available_perms) +        if not allowed: +            return HttpResponse(EMPTY, content_type='text/plain') + +        if force_own: +            own = True +        if full == 'shortcut' and 'SHORTCUT_SEARCH' in request.session and \ +                request.session['SHORTCUT_SEARCH'] == 'own': +            own = True + +        # get defaults from model +        if not extra_request_keys and hasattr(model, 'EXTRA_REQUEST_KEYS'): +            my_extra_request_keys = copy(model.EXTRA_REQUEST_KEYS) +        else: +            my_extra_request_keys = copy(extra_request_keys) +        if base_request is None and hasattr(model, 'BASE_REQUEST'): +            my_base_request = copy(model.BASE_REQUEST) +        elif base_request is not None: +            my_base_request = copy(base_request) +        else: +            my_base_request = {} +        if not bool_fields and hasattr(model, 'BOOL_FIELDS'): +            my_bool_fields = model.BOOL_FIELDS[:] +        else: +            my_bool_fields = bool_fields[:] +        if not reversed_bool_fields and hasattr(model, 'REVERSED_BOOL_FIELDS'): +            my_reversed_bool_fields = model.REVERSED_BOOL_FIELDS[:] +        else: +            my_reversed_bool_fields = reversed_bool_fields[:] +        if not dated_fields and hasattr(model, 'DATED_FIELDS'): +            my_dated_fields = model.DATED_FIELDS[:] +        else: +            my_dated_fields = dated_fields[:] +        if not associated_models and hasattr(model, 'ASSOCIATED_MODELS'): +            my_associated_models = model.ASSOCIATED_MODELS[:] +        else: +            my_associated_models = associated_models[:] +        if not relative_session_names and hasattr(model, +                                                  'RELATIVE_SESSION_NAMES'): +            my_relative_session_names = model.RELATIVE_SESSION_NAMES[:] +        else: +            my_relative_session_names = relative_session_names[:] +        if not relation_types_prefix and hasattr(model, +                                                 'RELATION_TYPES_PREFIX'): +            my_relation_types_prefix = copy(model.RELATION_TYPES_PREFIX) +        else: +            my_relation_types_prefix = copy(relation_types_prefix) + +        fields = [model._meta.get_field(k) +                  for k in get_all_field_names(model)] + +        request_keys = dict([ +            (field.name, +             field.name + (hasattr(field, 'rel') and field.rel and '__pk' +                           or '')) +            for field in fields]) +        for associated_model, key in my_associated_models: +            if type(associated_model) in (str, unicode): +                if associated_model not in globals(): +                    continue +                associated_model = globals()[associated_model] +            associated_fields = [ +                associated_model._meta.get_field(k) +                for k in get_all_field_names(associated_model)] +            request_keys.update( +                dict([(key + "__" + field.name, +                       key + "__" + field.name + +                       (hasattr(field, 'rel') and field.rel and '__pk' or '')) +                      for field in associated_fields])) +        request_keys.update(my_extra_request_keys) +        request_items = request.method == 'POST' and request.POST \ +                        or request.GET + +        # pager +        try: +            row_nb = int(request_items.get('length')) +        except (ValueError, TypeError): +            row_nb = DEFAULT_ROW_NUMBER +        dct_request_items = {} + +        # filter requested fields +        for k in request_items: +            if k in EXCLUDED_FIELDS: +                continue +            key = k[:] +            if key.startswith('searchprefix_'): +                key = key[len('searchprefix_'):] +            dct_request_items[key] = request_items[k] +        request_items = dct_request_items + +        dct = my_base_request +        if full == 'shortcut': +            dct['cached_label__icontains'] = request.GET.get('term', None) +        and_reqs, or_reqs = [], [] +        try: +            old = 'old' in request_items and int(request_items['old']) +        except ValueError: +            return HttpResponse('[]', content_type='text/plain') + +        # manage relations types +        if 'relation_types' not in my_relation_types_prefix: +            my_relation_types_prefix['relation_types'] = '' +        relation_types = {} +        for rtype_key in my_relation_types_prefix: +            relation_types[my_relation_types_prefix[rtype_key]] = set() +            for k in request_items: +                if k.startswith(rtype_key): +                    relation_types[my_relation_types_prefix[rtype_key]].add( +                        request_items[k]) +                    continue + +        for k in request_keys: +            val = request_items.get(k) +            if not val: +                continue +            req_keys = request_keys[k] +            if type(req_keys) not in (list, tuple): +                dct[req_keys] = val +                continue +            # multiple choice target +            reqs = Q(**{req_keys[0]: val}) +            for req_key in req_keys[1:]: +                q = Q(**{req_key: val}) +                reqs |= q +            and_reqs.append(reqs) + +        pinned_search = "" +        if 'submited' not in request_items and full != 'shortcut': +            # default search +            # an item is selected in the default menu +            if default_name in request.session and \ +                    request.session[default_name]: +                value = request.session[default_name] +                if 'basket-' in value: +                    try: +                        dct = {"basket__pk": +                                   request.session[default_name].split('-')[-1]} +                        pinned_search = unicode(FindBasket.objects.get( +                            pk=dct["basket__pk"])) +                    except FindBasket.DoesNotExist: +                        pass +                else: +                    try: +                        dct = {"pk": request.session[default_name]} +                        pinned_search = unicode(model._meta.verbose_name) \ +                                        + u" - " + unicode( +                            model.objects.get(pk=dct["pk"])) +                    except model.DoesNotExist: +                        pass +            elif dct == (my_base_request or {}): +                # a parent item may be selected in the default menu +                for name, key in my_relative_session_names: +                    if name in request.session and request.session[name] \ +                            and 'basket-' not in request.session[name] \ +                            and name in CURRENT_ITEM_KEYS_DICT: +                        up_model = CURRENT_ITEM_KEYS_DICT[name] +                        try: +                            dct.update({key: request.session[name]}) +                            pinned_search = unicode(up_model._meta.verbose_name) \ +                                            + u" - " + unicode( +                                up_model.objects.get(pk=dct[key])) +                            break +                        except up_model.DoesNotExist: +                            pass +            if (not dct or data_type == 'csv') \ +                    and func_name in request.session: +                dct = request.session[func_name] +        else: +            request.session[func_name] = dct +        for k in (list(my_bool_fields) + list(my_reversed_bool_fields)): +            if k in dct: +                if dct[k] == u"1": +                    dct.pop(k) +                else: +                    dct[k] = dct[k] == u"2" and True or False +                    if k in my_reversed_bool_fields: +                        dct[k] = not dct[k] +                    # check also for empty value with image field +                    field_name = k.split('__')[0] +                    # TODO: can be improved in later version of Django +                    try: +                        c_field = model._meta.get_field(field_name) +                        if k.endswith('__isnull') and \ +                                isinstance(c_field, ImageField): +                            if dct[k]: +                                or_reqs.append( +                                    (k, {k.split('__')[0] + '__exact': ''})) +                            else: +                                dct[k.split('__')[0] + '__regex'] = '.{1}.*' +                    except FieldDoesNotExist: +                        pass +        for k in my_dated_fields: +            if k in dct: +                if not dct[k]: +                    dct.pop(k) +                try: +                    items = dct[k].split('/') +                    assert len(items) == 3 +                    dct[k] = datetime.date(*map(lambda x: int(x), +                                                reversed(items))) \ +                        .strftime('%Y-%m-%d') +                except AssertionError: +                    dct.pop(k) +        # manage hierarchic conditions +        for req in dct.copy(): +            if req.endswith('town__pk') or req.endswith('towns__pk'): +                val = dct.pop(req) +                reqs = Q(**{req: val}) +                base_req = req[:-2] + '__' +                req = base_req[:] +                for idx in range(HIERARCHIC_LEVELS): +                    req = req[:-2] + 'parents__pk' +                    q = Q(**{req: val}) +                    reqs |= q +                req = base_req[:] +                for idx in range(HIERARCHIC_LEVELS): +                    req = req[:-2] + 'children__pk' +                    q = Q(**{req: val}) +                    reqs |= q +                and_reqs.append(reqs) +                continue + +            for k_hr in HIERARCHIC_FIELDS: +                if type(req) in (list, tuple): +                    val = dct.pop(req) +                    q = None +                    for idx, r in enumerate(req): +                        if not idx: +                            q = Q(**{r: val}) +                        else: +                            q |= Q(**{r: val}) +                    and_reqs.append(q) +                    break +                elif req.endswith(k_hr + '__pk'): +                    val = dct.pop(req) +                    reqs = Q(**{req: val}) +                    req = req[:-2] + '__' +                    for idx in range(HIERARCHIC_LEVELS): +                        req = req[:-2] + 'parent__pk' +                        q = Q(**{req: val}) +                        reqs |= q +                    and_reqs.append(reqs) +                    break +        dct = _search_manage_search_vector(dct) +        query = Q(**dct) +        for k, or_req in or_reqs: +            alt_dct = dct.copy() +            alt_dct.pop(k) +            alt_dct.update(or_req) +            query |= Q(**alt_dct) + +        for rtype_prefix in relation_types: +            vals = list(relation_types[rtype_prefix]) +            if not vals: +                continue +            alt_dct = { +                rtype_prefix + 'right_relations__relation_type__pk__in': vals} +            for k in dct: +                val = dct[k] +                if rtype_prefix: +                    # only get conditions related to the object +                    if rtype_prefix not in k: +                        continue +                    # tricky: reconstruct the key to make sense - remove the +                    # prefix from the key +                    k = k[0:k.index(rtype_prefix)] + k[ +                                                     k.index(rtype_prefix) + len(rtype_prefix):] +                if k.endswith('year'): +                    k += '__exact' +                alt_dct[rtype_prefix + 'right_relations__right_record__' + k] = \ +                    val +            if not dct: +                # fake condition to trick Django (1.4): without it only the +                # alt_dct is managed +                query &= Q(pk__isnull=False) +            query |= Q(**alt_dct) +            for k, or_req in or_reqs: +                altor_dct = alt_dct.copy() +                altor_dct.pop(k) +                for j in or_req: +                    val = or_req[j] +                    if j == 'year': +                        j = 'year__exact' +                    altor_dct[ +                        rtype_prefix + 'right_relations__right_record__' + j] = \ +                        val +                query |= Q(**altor_dct) + +        if own: +            q = models.IshtarUser.objects.filter(user_ptr=request.user) +            if q.count(): +                query = query & model.get_query_owns(q.all()[0]) +            else: +                return HttpResponse(EMPTY, content_type='text/plain') + +        for and_req in and_reqs: +            query = query & and_req + +        # manage hierarchic in shortcut menu +        if full == 'shortcut': +            ASSOCIATED_ITEMS = { +                Operation: (File, 'associated_file__pk'), +                ContextRecord: (Operation, 'operation__pk'), +                Find: (ContextRecord, 'base_finds__context_record__pk'), +            } +            if model in ASSOCIATED_ITEMS: +                upper_model, upper_key = ASSOCIATED_ITEMS[model] +                model_name = upper_model.SLUG +                current = model_name in request.session \ +                          and request.session[model_name] +                if current: +                    dct = {upper_key: current} +                    query &= Q(**dct) + +        items = model.objects.filter(query).distinct() +        # print(items.query) + +        if 'search_vector' in dct:  # for serialization +            dct['search_vector'] = dct['search_vector'].value + +        # table cols +        if own_table_cols: +            table_cols = own_table_cols +        else: +            if full: +                table_cols = [field.name for field in model._meta.fields +                              if field.name not in PRIVATE_FIELDS] +                table_cols += [field.name for field in model._meta.many_to_many +                               if field.name not in PRIVATE_FIELDS] +                if hasattr(model, 'EXTRA_FULL_FIELDS'): +                    table_cols += model.EXTRA_FULL_FIELDS +            else: +                table_cols = model.TABLE_COLS +        query_table_cols = [] +        for cols in table_cols: +            if type(cols) not in (list, tuple): +                cols = [cols] +            for col in cols: +                query_table_cols += col.split('|') + +        # contextual (full, simple, etc.) col +        contxt = full and 'full' or 'simple' +        if hasattr(model, 'CONTEXTUAL_TABLE_COLS') and \ +                contxt in model.CONTEXTUAL_TABLE_COLS: +            for idx, col in enumerate(table_cols): +                if col in model.CONTEXTUAL_TABLE_COLS[contxt]: +                    query_table_cols[idx] = \ +                        model.CONTEXTUAL_TABLE_COLS[contxt][col] +        if full == 'shortcut': +            query_table_cols = ['cached_label'] +            table_cols = ['cached_label'] + +        # manage sort tables +        manual_sort_key = None + +        sorts = {} +        for k in request_items: +            if not k.startswith('order['): +                continue +            num = int(k.split(']')[0][len("order["):]) +            if num not in sorts: +                sorts[num] = ['', '']  # sign, col_num +            if k.endswith('[dir]'): +                order = request_items[k] +                sign = order and order == u'desc' and "-" or '' +                sorts[num][0] = sign +            if k.endswith('[column]'): +                sorts[num][1] = request_items[k] +        sign = "" +        if not sorts and model._meta.ordering: +            orders = [k for k in model._meta.ordering] +            items = items.order_by(*orders) +        else: +            orders = [] +            for idx in sorted(sorts.keys()): +                signe, col_num = sorts[idx] +                k = query_table_cols[int(col_num) - 2]  # remove id and link col +                if k in request_keys: +                    ks = request_keys[k] +                    if type(ks) not in (tuple, list): +                        ks = [ks] +                    for k in ks: +                        if k.endswith("__pk"): +                            k = k[:-len("__pk")] + "__label" +                        if '__' in k: +                            k = k.split('__')[0] +                        orders.append(signe + k) +                else: +                    # not a standard request key +                    if idx:  # not the first - we ignore this sort +                        continue +                    sign = signe +                    manual_sort_key = k +                    logger.warning( +                        "**WARN get_item - {}**: manual sort key '{}'".format( +                            func_name, k)) +                    break +            if not manual_sort_key: +                items = items.order_by(*orders) + +        # pager management +        start, end = 0, None +        page_nb = 1 +        if row_nb and data_type == "json": +            try: +                start = int(request_items.get('start')) +                page_nb = start / row_nb + 1 +                assert page_nb >= 1 +            except (TypeError, ValueError, AssertionError): +                start = 0 +                page_nb = 1 +            end = page_nb * row_nb +        if full == 'shortcut': +            start = 0 +            end = 20 + +        items_nb = items.count() +        if manual_sort_key: +            items = items.all() +        else: +            items = items[start:end] + +        datas = [] +        if old: +            items = [item.get_previous(old) for item in items] +        c_ids = [] +        for item in items: +            # manual deduplicate when distinct is not enough +            if not do_not_deduplicate and item.pk in c_ids: +                continue +            c_ids.append(item.pk) +            data = [item.pk] +            for keys in query_table_cols: +                if type(keys) not in (list, tuple): +                    keys = [keys] +                my_vals = [] +                for k in keys: +                    if hasattr(model, 'EXTRA_REQUEST_KEYS') \ +                            and k in model.EXTRA_REQUEST_KEYS: +                        k = model.EXTRA_REQUEST_KEYS[k] +                        if type(k) in (list, tuple): +                            k = k[0] +                    for filtr in ('__icontains', '__contains'): +                        if k.endswith(filtr): +                            k = k[:len(k) - len(filtr)] +                    vals = [item] +                    # foreign key may be divided by "." or "__" +                    splitted_k = [] +                    for ky in k.split('.'): +                        if '__' in ky: +                            splitted_k += ky.split('__') +                        else: +                            splitted_k.append(ky) +                    for ky in splitted_k: +                        new_vals = [] +                        for val in vals: +                            if hasattr(val, 'all'):  # manage related objects +                                val = list(val.all()) +                                for v in val: +                                    v = getattr(v, ky) +                                    new_vals += _get_values(request, v) +                            elif val: +                                try: +                                    val = getattr(val, ky) +                                    new_vals += _get_values(request, val) +                                except AttributeError: +                                    # must be a query key such as "contains" +                                    pass +                        vals = new_vals +                    # manage last related objects +                    if vals and hasattr(vals[0], 'all'): +                        new_vals = [] +                        for val in vals: +                            new_vals += list(val.all()) +                        vals = new_vals +                    if not my_vals: +                        my_vals = [_format_val(va) for va in vals] +                    else: +                        new_vals = [] +                        if not vals: +                            for idx, my_v in enumerate(my_vals): +                                new_vals.append(u"{}{}{}".format( +                                    my_v, u' - ', '')) +                        else: +                            for idx, v in enumerate(vals): +                                new_vals.append(u"{}{}{}".format( +                                    vals[idx], u' - ', _format_val(v))) +                        my_vals = new_vals[:] +                data.append(u" & ".join(my_vals) or u"") +            datas.append(data) +        if manual_sort_key: +            # +1 because the id is added as a first col +            idx_col = None +            if manual_sort_key in query_table_cols: +                idx_col = query_table_cols.index(manual_sort_key) + 1 +            else: +                for idx, col in enumerate(query_table_cols): +                    if type(col) in (list, tuple) and \ +                            manual_sort_key in col: +                        idx_col = idx + 1 +            if idx_col is not None: +                datas = sorted(datas, key=lambda x: x[idx_col]) +                if sign == '-': +                    datas = reversed(datas) +                datas = list(datas)[start:end] +        link_template = "<a class='display_details' href='#' " \ +                        "onclick='load_window(\"%s\")'>" \ +                        "<i class=\"fa fa-info-circle\" aria-hidden=\"true\"></i></a>" +        link_ext_template = '<a href="{}" target="_blank">{}</a>' +        if data_type == "json": +            rows = [] +            for data in datas: +                try: +                    lnk = link_template % reverse('show-' + default_name, +                                                  args=[data[0], '']) +                except NoReverseMatch: +                    logger.warning( +                        '**WARN "show-' + default_name + '" args (' +                        + unicode(data[0]) + ") url not available") +                    lnk = '' +                res = {'id': data[0], 'link': lnk} +                for idx, value in enumerate(data[1:]): +                    if value: +                        table_col = table_cols[idx] +                        if type(table_col) not in (list, tuple): +                            table_col = [table_col] +                        tab_cols = [] +                        # foreign key may be divided by "." or "__" +                        for tc in table_col: +                            if '.' in tc: +                                tab_cols += tc.split('.') +                            elif '__' in tc: +                                tab_cols += tc.split('__') +                            else: +                                tab_cols.append(tc) +                        k = "__".join(tab_cols) +                        if hasattr(model, 'COL_LINK') and k in model.COL_LINK: +                            value = link_ext_template.format(value, value) +                        res[k] = value +                if full == 'shortcut' and 'cached_label' in res: +                    res['value'] = res.pop('cached_label') +                rows.append(res) +            if full == 'shortcut': +                data = json.dumps(rows) +            else: +                data = json.dumps({ +                    "recordsTotal": items_nb, +                    "recordsFiltered": items_nb, +                    "rows": rows, +                    "pinned-search": pinned_search, +                    "page": page_nb, +                    "total": (items_nb / row_nb + 1) if row_nb else items_nb, +                }) +            return HttpResponse(data, content_type='text/plain') +        elif data_type == "csv": +            response = HttpResponse(content_type='text/csv') +            n = datetime.datetime.now() +            filename = u'%s_%s.csv' % (default_name, +                                       n.strftime('%Y%m%d-%H%M%S')) +            response['Content-Disposition'] = 'attachment; filename=%s' \ +                                              % filename +            writer = csv.writer(response, **CSV_OPTIONS) +            if col_names: +                col_names = [name.encode(ENCODING, errors='replace') +                             for name in col_names] +            else: +                col_names = [] +                for field_name in table_cols: +                    if type(field_name) in (list, tuple): +                        field_name = u" & ".join(field_name) +                    if hasattr(model, 'COL_LABELS') and \ +                            field_name in model.COL_LABELS: +                        field = model.COL_LABELS[field_name] +                        col_names.append(unicode(field).encode(ENCODING)) +                        continue +                    else: +                        try: +                            field = model._meta.get_field(field_name) +                        except: +                            col_names.append(u"".encode(ENCODING)) +                            logger.warning( +                                "**WARN get_item - csv export**: no col name " +                                "for {}\nadd explicit label to " +                                "COL_LABELS attribute of " +                                "{}".format(field_name, model)) +                            continue +                        col_names.append( +                            unicode(field.verbose_name).encode(ENCODING)) +            writer.writerow(col_names) +            for data in datas: +                row, delta = [], 0 +                # regroup cols with join "|" +                for idx, col_name in enumerate(table_cols): +                    if len(data[1:]) <= idx + delta: +                        break +                    val = data[1:][idx + delta].encode( +                        ENCODING, errors='replace') +                    if col_name and "|" in col_name[0]: +                        for delta_idx in range( +                                len(col_name[0].split('|')) - 1): +                            delta += 1 +                            val += data[1:][idx + delta].encode( +                                ENCODING, errors='replace') +                    row.append(val) +                writer.writerow(row) +            return response +        return HttpResponse('{}', content_type='text/plain') + +    return func diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index a20d33fc3..6ec0220eb 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -458,6 +458,58 @@ class SearchWidget(forms.TextInput):      template_name = 'widgets/search_input.html' +class ModelFieldMixin(object): +    def to_python(self, value): +        if not value: +            return +        if not self.multiple: +            value = [value] +        values = [] +        for v in value: +            if not v: +                continue +            try: +                values.append(self.model.objects.get(pk=v)) +            except self.model.DoesNotExist: +                raise ValidationError( +                    unicode( +                        _(u"{} is not a valid key for {}") +                    ).format(v, self.model) +                ) +        if not self.multiple: +            return values[0] +        return values + + +class ModelChoiceField(ModelFieldMixin, forms.ChoiceField): +    def __init__(self, model, multiple=False, *args, **kwargs): +        self.model = model +        self.multiple = multiple +        super(ModelFieldMixin, self).__init__(*args, **kwargs) + +    def valid_value(self, value): +        if value and getattr(value, 'pk', None) in [v for v, l in self.choices]: +            return True +        return super(ModelChoiceField, self).valid_value(value) + + +class ModelJQueryAutocompleteField(ModelFieldMixin, forms.CharField): +    def __init__(self, model, multiple=False, new=False, long_widget=False, +                 *args, **kwargs): +        self.model = model +        self.multiple = multiple +        attrs = {} +        if long_widget: +            attrs['cols'] = True +            attrs['full-width'] = True +        kwargs['widget'] = JQueryAutoComplete( +            reverse_lazy('autocomplete-' + self.model.SLUG), +            associated_model=self.model, new=new, multiple=multiple, +            attrs=attrs +        ) +        super(ModelJQueryAutocompleteField, self).__init__(*args, **kwargs) + +  class JQueryAutoComplete(forms.TextInput):      def __init__(self, source, associated_model=None, options=None, attrs=None,                   new=False, url_new='', multiple=False, limit=None, @@ -619,7 +671,7 @@ class JQueryAutoComplete(forms.TextInput):  class JQueryTown(forms.TextInput):      """ -    Town fields whith state and department pre-selections +    Town fields with state and department pre-selections      """      def __init__(self, source, options={}, | 
