#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2010-2014 Étienne Loks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # See the file COPYING for details. try: import tidy except: from tidylib import tidy_document as tidy import re import csv import json import datetime import optparse import cStringIO as StringIO from tempfile import NamedTemporaryFile import ho.pisa as pisa import unicodedata from django.conf import settings from django.contrib.auth import logout from django.core import serializers from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse, NoReverseMatch from django.db.models import Q, ImageField from django.http import HttpResponse, Http404 from django.shortcuts import render_to_response, redirect from django.template import RequestContext, loader from django.utils.translation import ugettext, ugettext_lazy as _ if settings.XHTML2ODT_PATH: import sys sys.path.append(settings.XHTML2ODT_PATH) from xhtml2odt import xhtml2odt from menus import menu from ishtar_common.forms import FinalForm from ishtar_common import forms_common as forms from ishtar_common import wizards import models CSV_OPTIONS = {'delimiter':';', 'quotechar':'"', 'quoting':csv.QUOTE_ALL} ENCODING = settings.ENCODING or 'utf-8' def index(request): """ Main page """ dct = {} try: return render_to_response('index.html', dct, context_instance=RequestContext(request)) except NoReverseMatch: # probably rights exception (rights revoked) logout(request) return render_to_response('index.html', dct, context_instance=RequestContext(request)) person_creation_wizard = wizards.PersonWizard.as_view([ ('identity-person_creation', forms.SimplePersonForm), ('person_type-person_creation', forms.PersonTypeForm), ('final-person_creation', FinalForm)], label=_(u"New person"), url_name='person_creation') person_modification_wizard = wizards.PersonModifWizard.as_view([ ('selec-person_modification', forms.PersonFormSelection), ('identity-person_modification', forms.SimplePersonForm), ('person_type-person_creation', forms.PersonTypeForm), ('final-person_modification', FinalForm)], label=_(u"Person modification"), url_name='person_modification') organization_creation_wizard = wizards.OrganizationWizard.as_view([ ('identity-organization_creation', forms.OrganizationForm), ('final-organization_creation', FinalForm)], label=_(u"New organization"), url_name='organization_creation') organization_modification_wizard = wizards.OrganizationModifWizard.as_view([ ('selec-organization_modification', forms.OrganizationFormSelection), ('identity-organization_modification', forms.OrganizationForm), ('final-organization_modification', FinalForm)], label=_(u"Organization modification"), url_name='organization_modification') account_management_wizard = wizards.AccountWizard.as_view([ ('selec-account_management', forms.PersonFormSelection), ('account-account_management', forms.AccountForm), ('final-account_management', forms.FinalAccountForm)], label=_(u"Account management"), url_name='account_management',) def update_current_item(request): if not request.is_ajax() and not request.method == 'POST': raise Http404 if 'value' in request.POST and 'item' in request.POST: request.session[request.POST['item']] = request.POST['value'] return HttpResponse('ok') def check_permission(request, action_slug, obj_id=None): if action_slug not in menu.items: #! TODO return True if obj_id: return menu.items[action_slug].is_available(request.user, obj_id) return menu.items[action_slug].can_be_available(request.user) def autocomplete_person(request, person_types=None): if not request.user.has_perm('ishtar_common.view_person', models.Person) and \ not request.user.has_perm('ishtar_common.view_own_person', models.Person) \ and not request.user.ishtaruser.has_right('person_search'): return HttpResponse(mimetype='text/plain') if not request.GET.get('term'): return HttpResponse(mimetype='text/plain') q = request.GET.get('term') limit = request.GET.get('limit', 20) try: limit = int(limit) except ValueError: return HttpResponseBadRequest() query = Q() for q in q.split(' '): query = query & (Q(name__icontains=q) | Q(surname__icontains=q) | \ Q(email__icontains=q) | Q(attached_to__name__icontains=q)) if person_types: try: typs = [int(tp) for tp in person_types.split('_') if tp] typ = models.PersonType.objects.filter(pk__in=typs).all() query = query & Q(person_types__in=typ) except (ValueError, ObjectDoesNotExist): pass limit = 20 persons = models.Person.objects.filter(query)[:limit] data = json.dumps([{'id':person.pk, 'value':unicode(person)} for person in persons if person]) return HttpResponse(data, mimetype='text/plain') def autocomplete_department(request): if not request.GET.get('term'): return HttpResponse(mimetype='text/plain') q = request.GET.get('term') q = unicodedata.normalize("NFKD", q).encode('ascii','ignore') query = Q() for q in q.split(' '): extra = (Q(label__icontains=q) | Q(number__istartswith=q)) query = query & extra limit = 20 departments = models.Department.objects.filter(query)[:limit] data = json.dumps([{'id':department.pk, 'value':unicode(department)} for department in departments]) return HttpResponse(data, mimetype='text/plain') def autocomplete_town(request): if not request.GET.get('term'): return HttpResponse(mimetype='text/plain') q = request.GET.get('term') q = unicodedata.normalize("NFKD", q).encode('ascii','ignore') query = Q() for q in q.split(' '): extra = Q(name__icontains=q) if settings.COUNTRY == 'fr': extra = (extra | Q(numero_insee__istartswith=q) | \ Q(departement__label__istartswith=q)) query = query & extra limit = 20 towns = models.Town.objects.filter(query)[:limit] data = json.dumps([{'id':town.pk, 'value':unicode(town)} for town in towns]) return HttpResponse(data, mimetype='text/plain') from types import NoneType def format_val(val): if type(val) == NoneType: return u"" if type(val) == bool: if val: return unicode(_(u"True")) else: return unicode(_(u"False")) return unicode(val) HIERARCHIC_LEVELS = 5 HIERARCHIC_FIELDS = ['periods', 'period', 'unit', 'material_type', 'conservatory_state'] PRIVATE_FIELDS = ('id', 'history_modifier', 'order') def get_item(model, func_name, default_name, extra_request_keys=[], base_request={}, bool_fields=[], reversed_bool_fields=[], dated_fields=[], associated_models=[], relative_session_names={}, specific_perms=[]): """ Generic treatment of tables """ def func(request, data_type='json', full=False, **dct): # check rights own = True # more restrictive by default allowed = False for perm, lbl in model._meta.permissions: # if not specific any perm is relevant (read right) if specific_perms and perm not in specific_perms: continue if request.user.has_perm(perm) \ or (request.user.is_authenticated() and request.user.ishtaruser.has_right(perm)): allowed = True if "_own_" not in perm: own = False break # max right reach EMPTY, mimetype = '', 'text/plain' if 'type' in dct: data_type = dct.pop('type') if data_type == 'csv': mimetype = 'text/csv' if not data_type: EMPTY = '[]' data_type = 'json' if not allowed: return HttpResponse(EMPTY, mimetype='text/plain') fields = [model._meta.get_field_by_name(k)[0] for k in model._meta.get_all_field_names()] 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 associated_models: associated_fields = [associated_model._meta.get_field_by_name(k)[0] for k in associated_model._meta.get_all_field_names()] 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(extra_request_keys) request_items = request.method == 'POST' and request.POST or request.GET dct = base_request.copy() and_reqs, or_reqs = [], [] try: old = 'old' in request_items and int(request_items['old']) except ValueError: return HttpResponse('[]', mimetype='text/plain') for k in request_keys: q = request_items.get(k) if not q: continue dct[request_keys[k]] = q if not dct and 'submited' not in request_items: if default_name in request.session and \ request.session[default_name]: dct = {"pk":request.session[default_name]} else: for name in relative_session_names.keys(): if name in request.session and request.session[name]: k = relative_session_names[name] dct = {k:request.session[name]} break 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(bool_fields) + list(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 reversed_bool_fields: dct[k] = not dct[k] # check also for empty value with image field c_field = model._meta.get_field(k.split('__')[0]) 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}.*' for k in 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(): 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 | 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 xrange(HIERARCHIC_LEVELS): req = req[:-2] + 'parent__pk' q = Q(**{req:val}) reqs = reqs | q and_reqs.append(reqs) break query = Q(**dct) if own: query = query & model.get_query_own(request.user) for k, or_req in or_reqs: alt_dct = dct.copy() alt_dct.pop(k) alt_dct.update(or_req) query = query | Q(**alt_dct) for and_req in and_reqs: query = query & and_req items = model.objects.filter(query).distinct() q = request_items.get('sidx') # table cols table_cols = full and [field.name for field in model._meta.fields if field.name not in PRIVATE_FIELDS] \ or model.TABLE_COLS # manage sort tables manual_sort_key = None order = request_items.get('sord') sign = order and order == u'desc' and "-" or '' if q and q in request_keys: ks = request_keys[q] if type(ks) not in (list, tuple): ks = [ks] orders = [] for k in ks: if k.endswith("__pk"): k = k[:-len("__pk")] + "__label" if '__' in k: k = k.split('__')[0] orders.append(sign+k) items = items.order_by(*orders) elif q: for k in table_cols: if k.endswith(q): manual_sort_key = k break # pager management start, end = 0, None page_nb = 1 try: row_nb = int(request_items.get('rows')) except (ValueError, TypeError): row_nb = None if row_nb: try: page_nb = int(request_items.get('page')) assert page_nb >= 1 except (ValueError, AssertionError): pass start = (page_nb-1)*row_nb end = page_nb*row_nb 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] for item in items: data = [item.pk] for k in table_cols: vals = [item] for ky in k.split('.'): new_vals = [] for val in vals: if hasattr(val, 'all'): # manage related objects val = list(val.all()) for v in val: new_vals.append(getattr(v, ky)) elif val: new_vals.append(getattr(val, ky)) vals = new_vals if vals and hasattr(vals[0], 'all'): # manage last related objects new_vals = [] for val in vals: new_vals += list(val.all()) vals = new_vals data.append(", ".join([format_val(v) for v in vals]) or u"") datas.append(data) if manual_sort_key: # +1 because the id is added as a first col idx_col = table_cols.index(manual_sort_key) + 1 datas = sorted(datas, key=lambda x:x[idx_col]) if sign == '-': datas = reversed(datas) datas = list(datas)[start:end] link_template = "%s" % \ (unicode(_("Details"))) if data_type == "json": rows = [] for data in datas: try: lnk = link_template % reverse('show-'+default_name, args=[data[0], '']) except NoReverseMatch: print '"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: res[table_cols[idx].split('.')[-1]] = value rows.append(res) data = json.dumps({ "records":items_nb, "rows":rows, "page":page_nb, "total":(items_nb/row_nb + 1) if row_nb else items_nb, }) return HttpResponse(data, mimetype='text/plain') elif data_type == "csv": response = HttpResponse(mimetype='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) col_names = [] for field_name in table_cols: try: field = model._meta.get_field(field_name) except: col_names.append(u"".encode(ENCODING)) continue col_names.append(unicode(field.verbose_name).encode(ENCODING)) writer.writerow(col_names) for data in datas: writer.writerow([val.encode(ENCODING) for val in data[1:]]) return response return HttpResponse('{}', mimetype='text/plain') return func def show_item(model, name): def func(request, pk, **dct): try: item = model.objects.get(pk=pk) except ObjectDoesNotExist: return HttpResponse(None) doc_type = 'type' in dct and dct.pop('type') url_name = u"/".join(reverse('show-'+name, args=['0', ''] ).split('/')[:-2]) + u"/" dct['current_window_url'] = url_name date = 'date' in dct and dct.pop('date') 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 != None except (ValueError, AssertionError): return HttpResponse(None, mimetype='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 context_instance = RequestContext(request) context_instance.update(dct) n = datetime.datetime.now() filename = "" if hasattr(item, 'history_object'): filename = item.history_object.associated_filename else: filename = item.associated_filename if doc_type == "odt" and settings.XHTML2ODT_PATH and \ settings.ODT_TEMPLATE: tpl = loader.get_template('ishtar/sheet_%s.html' % name) content = tpl.render(context_instance) try: tidy_options = dict(output_xhtml=1, add_xml_decl=1, indent=1, tidy_mark=0, output_encoding='utf8', doctype='auto', wrap=0, char_encoding='utf8') html = str(tidy.parseString(content.encode('utf-8'), **tidy_options)) html = html.replace(" ", " ") html = re.sub(']*)>\n', '', 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, ex: return HttpResponse(content, content_type="application/xhtml") response = HttpResponse( mimetype='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) content = tpl.render(context_instance) result = StringIO.StringIO() html = content.encode('utf-8') html = html.replace("