diff options
Diffstat (limited to 'ishtar_common/views_item.py')
| -rw-r--r-- | ishtar_common/views_item.py | 936 | 
1 files changed, 936 insertions, 0 deletions
| 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 | 
