diff options
author | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-10-18 17:49:57 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-10-18 17:51:30 +0200 |
commit | ade7bd4b74d9ae42c54648cc7390d8c067b5c5e3 (patch) | |
tree | 4136673563f802d6de992512e3c4adde86ef2a4e /ishtar_common/views.py | |
parent | 9a3d3dcbca9395c00e55d6ee4909ba9b9a4752f8 (diff) | |
download | Ishtar-ade7bd4b74d9ae42c54648cc7390d8c067b5c5e3.tar.bz2 Ishtar-ade7bd4b74d9ae42c54648cc7390d8c067b5c5e3.zip |
Djangoization - Major refactoring (step 1)
Diffstat (limited to 'ishtar_common/views.py')
-rw-r--r-- | ishtar_common/views.py | 1333 |
1 files changed, 1333 insertions, 0 deletions
diff --git a/ishtar_common/views.py b/ishtar_common/views.py new file mode 100644 index 000000000..9b06276f8 --- /dev/null +++ b/ishtar_common/views.py @@ -0,0 +1,1333 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2011 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> + +# 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 <http://www.gnu.org/licenses/>. + +# 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 + +from django.conf import settings +from django.contrib.formtools.wizard.views import NamedUrlWizardView +from django.core import serializers +from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse, NoReverseMatch +from django.db.models import Q +from django.http import HttpResponse, Http404 +from django.shortcuts import render_to_response, redirect +from django.template import RequestContext, loader +from django.template.defaultfilters import slugify +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 +import forms_main as ishtar_forms + +from ishtar_common.forms import FinalForm +from ishtar_common.forms_common import PersonForm +import models + +CSV_OPTIONS = {'delimiter':';', 'quotechar':'"', 'quoting':csv.QUOTE_ALL} +ENCODING = settings.ENCODING or 'utf-8' + +def index(request): + """ + Main page + """ + dct = {} + return render_to_response('index.html', dct, + context_instance=RequestContext(request)) + +class Wizard(NamedUrlWizardView): + model = None + modification = None # True when the wizard modify an item + storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage' + + @staticmethod + def _check_right(step, condition=True): + '''Return a method to check the right for a specific step''' + """ + def check_right(self, request, storage): + cond = condition + if callable(condition): + cond = condition(self, request, storage) + if not cond: + return False + person_type = request.user.ishtaruser.person.person_type + if person_type.txt_idx == 'administrator': + return True + if person_type.rights.filter(url_name=step).count(): + return True""" + def check_right(self): + cond = condition + if callable(condition): + cond = condition(self) + if not cond: + return False + person_type = self.request.user.ishtaruser.person.person_type + if person_type.txt_idx == 'administrator': + return True + if person_type.rights.filter(url_name=step).count(): + return True + return check_right + + def __init__(self, *args, **kwargs): + """Check right for each step of the wizard""" + super(Wizard, self).__init__(*args, **kwargs) + for form_key in self.form_list.keys()[:-1]: + condition = True + if form_key in self.condition_dict: + condition = self.condition_dict.get(form_key, True) + cond = self._check_right(form_key, condition) + self.condition_dict[form_key] = cond + """ + for form_key in self.form_list.keys()[:-1]: + condition = True + if form_key in self.condition_list: + condition = self.condition_list.get(form_key, True) + cond = self._check_right(form_key, condition) + self.condition_list[form_key] = cond""" + + def get_wizard_name(self): + """As the class name can interfere when reused, use the url_name""" + return self.url_name + + def get_template_names(self): + templates = ['ishtar/wizard/default_wizard.html'] + current_step = self.steps.current + if current_step == self.steps.last: + templates = ['ishtar/wizard/confirm_wizard.html'] + templates + return templates + + def get_context_data(self, form, **kwargs): + """Add previous, next and current steps to manage the wizard path""" + context = super(Wizard, self).get_context_data(form) + step = self.steps.first + current_step = self.steps.current + context.update({'current_step':self.form_list[current_step]}) + if step == current_step: + return context + previous_steps, next_steps, previous_step_counter = [], [], 0 + while step: + if step == current_step: + break + previous_steps.append(self.form_list[step]) + step = self.steps.next + previous_step_counter += 1 + context.update({'previous_steps':previous_steps, + 'previous_step_counter':previous_step_counter}) + # if modification: show the next steps + if self.modification: + next_step = step + while next_step: + # check if the form is initialized otherwise initialize it + if not storage.get_step_data(next_step): + values = self.get_form_initial(request, storage, next_step) + prefixed_values = {} + if not isinstance(values, list): + for key in values: + form_key = next_step + '-' + key + prefixed_values[form_key] = values[key] + else: + for formset_idx, v in enumerate(values): + prefix = u"-%d-" % formset_idx + for key in v: + form_key = next_step + prefix + key + prefixed_values[form_key] = v[key] + storage.set_step_data(next_step, prefixed_values) + if step != next_step: # if not current step + next_steps.append(self.form_list[next_step]) + next_step = self.get_next_step(request, storage, next_step) + context.update({'next_steps':next_steps}) + # not last step: validation + if current_step != self.steps.last: + return context + final_form_list = [] + for form_key in self.get_form_list().keys(): + form_obj = self.get_form(step=form_key, + data=self.storage.get_step_data(form_key), + files=self.storage.get_step_files(form_key)) + form_obj.is_valid() + final_form_list.append(form_obj) + last_form = final_form_list[-1] + context.update({'datas':self.get_formated_datas(final_form_list)}) + if hasattr(last_form, 'confirm_msg'): + context.update({'confirm_msg':last_form.confirm_msg}) + if hasattr(last_form, 'confirm_end_msg'): + context.update({'confirm_end_msg':last_form.confirm_end_msg}) + return context + + def get_formated_datas(self, forms): + """Get the data to present in the last page""" + datas = [] + for form in forms: + form_datas = [] + base_form = hasattr(form, 'forms') and form.forms[0] or form + associated_models = hasattr(base_form, 'associated_models') and \ + base_form.associated_models or {} + if not hasattr(form, 'cleaned_data') and hasattr(form, 'forms'): + cleaned_datas = [frm.cleaned_data for frm in form.forms + if frm.is_valid()] + if not cleaned_datas: + continue + elif not hasattr(form, 'cleaned_data'): + continue + else: + cleaned_datas = type(form.cleaned_data) == list and \ + form.cleaned_data \ + or [form.cleaned_data] + for cleaned_data in cleaned_datas: + if not cleaned_data: + continue + if form_datas: + form_datas.append(("", "", "spacer")) + items = hasattr(base_form, 'fields') and \ + base_form.fields.keyOrder or cleaned_data.keys() + for key in items: + lbl = None + if key.startswith('hidden_'): + continue + if hasattr(base_form, 'fields') and key in base_form.fields: + lbl = base_form.fields[key].label + if hasattr(base_form, 'associated_labels') \ + and key in base_form.associated_labels: + lbl = base_form.associated_labels[key] + if not lbl: + continue + value = cleaned_data[key] + if not value and value != False: + continue + if type(value) == bool: + if value == True: + value = _(u"Yes") + elif value == False: + value = _(u"No") + elif key in associated_models: + values = [] + if "," in unicode(value): + values = unicode(value).split(",") + else: + values = [value] + rendered_values = [] + for val in values: + item = associated_models[key].objects.get(pk=val) + if hasattr(item, 'short_label'): + value = item.short_label() + else: + value = unicode(item) + rendered_values.append(value) + value = u" ; ".join(rendered_values) + form_datas.append((lbl, value, '')) + if form_datas: + datas.append((form.form_label, form_datas)) + return datas + + def get_extra_model(self, dct, form_list): + dct['history_modifier'] = self.request.user + return dct + + def done(self, form_list, return_object=False, **kwargs): + """Save to the model""" + dct, m2m, whole_associated_models = {}, [], [] + for form in form_list: + if not form.is_valid(): + return self.render(form) + base_form = hasattr(form, 'forms') and form.forms[0] or form + associated_models = hasattr(base_form, 'associated_models') and \ + base_form.associated_models or {} + if hasattr(form, 'forms'): + multi = False + if form.forms: + frm = form.forms[0] + if hasattr(frm, 'base_model') and frm.base_model: + whole_associated_models.append(frm.base_model) + else: + whole_associated_models += associated_models.keys() + fields = frm.fields.copy() + if 'DELETE' in fields: + fields.pop('DELETE') + multi = len(fields) > 1 + if multi: + assert hasattr(frm, 'base_model'), \ + u"Must define a base_model for " + unicode(frm.__class__) + for frm in form.forms: + if not frm.is_valid(): + continue + vals = {} + if "DELETE" in frm.cleaned_data: + if frm.cleaned_data["DELETE"]: + continue + frm.cleaned_data.pop('DELETE') + for key in frm.cleaned_data: + value = frm.cleaned_data[key] + if not value and value != False: + continue + if key in associated_models: + value = associated_models[key].objects.get(pk=value) + if multi: + vals[key] = value + else: + m2m.append((key, value)) + if multi and vals: + m2m.append((frm.base_model, vals)) + elif type(form.cleaned_data) == dict: + for key in form.cleaned_data: + if key.startswith('hidden_'): + continue + value = form.cleaned_data[key] + if key in associated_models: + if value: + model = associated_models[key] + if isinstance(value, unicode) \ + or isinstance(value, str) and "," in value: + value = value.split(",") + if isinstance(value, list) \ + or isinstance(value, tuple): + value = [model.objects.get(pk=val) + for val in value if val] + if len(value) == 1: + value = value[0] + else: + value = model.objects.get(pk=value) + else: + value = None + dct[key] = value + return self.save_model(dct, m2m, whole_associated_models, form_list, + return_object) + + def get_saved_model(self): + """Permit a distinguo when saved model is not the base selected model""" + return self.model + + def get_current_saved_object(self): + """Permit a distinguo when saved model is not the base selected model""" + return self.get_current_object() + + def save_model(self, dct, m2m, whole_associated_models, form_list, + return_object): + dct = self.get_extra_model(dct, form_list) + obj = self.get_current_saved_object() + # manage dependant items + other_objs = {} + for k in dct.keys(): + if '__' not in k: + continue + vals = k.split('__') + assert len(vals) == 2, "Only one level of dependant item is managed" + dependant_item, key = vals + if dependant_item not in other_objs: + other_objs[dependant_item] = {} + other_objs[dependant_item][key] = dct.pop(k) + if obj: + for k in dct: + if k.startswith('pk'): + continue + setattr(obj, k, dct[k]) + try: + obj.full_clean() + except forms.ValidationError, msg: + return self.render(form_list[-1]) + for dependant_item in other_objs: + c_item = getattr(obj, dependant_item) + # manage ManyToMany if only one associated + if hasattr(c_item, "all"): + c_items = c_item.all() + if len(c_items) != 1: + continue + c_item = c_items[0] + if c_item: + # to check # + for k in other_objs[dependant_item]: + setattr(c_item, k, other_objs[dependant_item][k]) + c_item.save() + else: + m = getattr(self.model, dependant_item) + if hasattr(m, 'related'): + c_item = m.related.model(**other_objs[dependant_item]) + setattr(obj, dependant_item, c_item) + obj.save() + obj.save() + else: + adds = {} + for dependant_item in other_objs: + m = getattr(self.model, dependant_item) + model = m.field.rel.to + c_dct = other_objs[dependant_item].copy() + if issubclass(model, models.BaseHistorizedItem): + c_dct['history_modifier'] = self.request.user + c_item = model(**c_dct) + c_item.save() + if hasattr(m, 'through'): + adds[dependant_item] = c_item + elif hasattr(m, 'field'): + dct[dependant_item] = c_item + if 'pk' in dct: + dct.pop('pk') + obj = self.get_saved_model()(**dct) + try: + obj.full_clean() + except forms.ValidationError, msg: + return self.render(form_list[-1]) + obj.save() + for k in adds: + getattr(obj, k).add(adds[k]) + # necessary to manage interaction between models like + # material_index management for baseitems + obj.save() + m2m_items = {} + for model in whole_associated_models: + getattr(obj, model+'s').clear() + for key, value in m2m: + if key not in m2m_items: + if type(key) == dict: + vals = [] + for item in getattr(obj, key+'s').all(): + v = {} + for k in value.keys(): + v[k] = getattr(item, k) + vals.append(v) + m2m_items[key] = vals + else: + m2m_items[key] = getattr(obj, key+'s').all() + if value not in m2m_items[key]: + if type(value) == dict: + model = getattr(obj, key+'s').model + if issubclass(model, models.BaseHistorizedItem): + value['history_modifier'] = self.request.user + value = model.objects.create(**value) + value.save() + getattr(obj, key+'s').add(value) + # necessary to manage interaction between models like + # material_index management for baseitems + obj.save() + res = render_to_response('wizard_done.html', {}, + context_instance=RequestContext(self.request)) + return return_object and (obj, res) or res + + def get_deleted(self, keys): + """Get the deleted and non-deleted items in formsets""" + not_to_delete, to_delete = set(), set() + for key in keys: + items = key.split('-') + if len(items) < 2 or items[-2] in to_delete: + continue + idx = items[-2] + try: + int(idx) + except: + continue + if items[-1] == u'DELETE': + to_delete.add(idx) + if idx in not_to_delete: + not_to_delete.remove(idx) + elif idx not in not_to_delete: + not_to_delete.add(idx) + return (to_delete, not_to_delete) + + def get_form(self, step=None, data=None, files=None): + """Manage formset""" + request = self.request + storage = self.storage + if data: + data = data.copy() + if not step: + step = self.steps.current + form = self.get_form_list()[step] + if hasattr(form, 'management_form'): + # manage deletion + to_delete, not_to_delete = self.get_deleted(data.keys()) + # raz deleted fields + for key in data.keys(): + items = key.split('-') + if len(items) < 2 or items[-2] not in to_delete: + continue + data.pop(key) + if to_delete: + # reorganize + for idx, number in enumerate(sorted(not_to_delete)): + idx = unicode(idx) + if idx == number: + continue + for key in data.keys(): + items = key.split('-') + if len(items) > 2 and number == items[-2]: + items[-2] = unicode(idx) + k = u'-'.join(items) + data[k] = data.pop(key)[0] + # get a form key + base_key = form.form.base_fields.keys()[0] + init = self.get_form_initial(request, storage, step) + total_field = len([key for key in data.keys() + if base_key in key.split('-') + and data[key]]) + if init and not to_delete: + total_field = max((total_field, len(init))) + data[step + u'-INITIAL_FORMS'] = unicode(total_field) + data[step + u'-TOTAL_FORMS'] = unicode(total_field + 1) + data = data or None + form = super(Wizard, self).get_form(step, data, files) + return form + + def render_next_step(self, form, **kwargs): + """ + Manage: + - modify or delete button in formset: next step = current step + - validate and end: nextstep = last step + """ + request = self.request + if request.POST.has_key('formset_modify') \ + and request.POST['formset_modify'] \ + or [key for key in request.POST.keys() + if key.endswith('DELETE') and request.POST[key]]: + return self.render(form) + elif request.POST.has_key('validate_and_end') \ + and request.POST['validate_and_end']: + last_step = self.steps.last + new_form = self.get_form(last_step, + data=self.storage.get_step_data(last_step), + files=self.storage.get_step_files(last_step)) + self.storage.current_step = last_step + return self.render(new_form) + return super(Wizard, self).render_next_step(form, **kwargs) + + def post(self, *args, **kwargs): + """Convert numerical step number to step name""" + request = self.request + post_data = request.POST.copy() + if request.POST.has_key('form_prev_step'): + try: + step_number = int(request.POST['form_prev_step']) + post_data['wizard_goto_step'] = self.get_form_list().keys( + )[step_number] + except ValueError: + pass + request.POST = post_data + return super(Wizard, self).post(*args, **kwargs) + + @classmethod + def session_has_key(cls, request, storage, form_key, key=None, multi=None): + """Check if the session has value of a specific form and (if provided) + of a key + """ + test = storage.prefix in request.session \ + and 'step_data' in request.session[storage.prefix] \ + and form_key in request.session[storage.prefix]['step_data'] + if not key or not test: + return test + key = key.startswith(form_key) and key or \ + not multi and form_key + '-' + key or \ + form_key + '-0-' + key #only check if the first field is available + return key in request.session[storage.prefix]['step_data'][form_key] + + @classmethod + def session_get_value(cls, request, storage, form_key, key, multi=False): + """Get the value of a specific form""" + if not cls.session_has_key(request, storage, form_key, key, multi): + return + if not multi: + key = key.startswith(form_key) and key or form_key + '-' + key + return request.session[storage.prefix]['step_data'][form_key][key] + vals = [] + for k in request.session[storage.prefix]['step_data'][form_key]: + if k.startswith(form_key) and k.endswith(key) and \ + request.session[storage.prefix]['step_data'][form_key][k]: + vals.append(request.session[storage.prefix]['step_data']\ + [form_key][k]) + return vals + + def get_current_object(self): + """Get the current object for an instancied wizard""" + current_obj = None + main_form_key = 'selec-' + self.url_name + try: + idx = int(self.session_get_value(self.request, self.storage, + main_form_key, 'pk')) + current_obj = self.model.objects.get(pk=idx) + except(TypeError, ValueError, ObjectDoesNotExist): + pass + return current_obj + + def get_form_initial(self, step): + current_obj = self.get_current_object() + current_step = self.steps.current + if step.startswith('selec-') and step in self.form_list \ + and 'pk' in self.form_list[step].associated_models: + model_name = self.form_list[step].associated_models['pk' + ].__name__.lower() + if step == current_step: + self.reset_wizard(request, storage) + val = model_name in request.session and request.session[model_name] + if val: + return {'pk':val} + elif current_obj: + return self.get_instanced_init(current_obj, step) + current_form = self.form_list[current_step] + if hasattr(current_form, 'currents'): + initial = {} + for key in current_form.currents: + model_name = current_form.currents[key].__name__.lower() + val = model_name in request.session and \ + request.session[model_name] + if val: + initial[key] = val + if initial: + return initial + return super(Wizard, self).get_form_initial(step) + + def get_instanced_init(self, obj, step=None): + """Get initial data from an init""" + current_step = step or self.steps.current + c_form = self.form_list[current_step] + # make the current object the default item for the session + obj_name = obj.__class__.__name__.lower() + # prefer a specialized name if available + prefixes = self.storage.prefix.split('_') + if len(prefixes) > 1 and prefixes[-2].startswith(obj_name): + obj_name = prefixes[-2] + self.request.session[obj_name] = unicode(obj.pk) + initial = {} + if self.request.POST or \ + (step in self.request.session[self.storage.prefix] and\ + self.request.session[self.storage.prefix]['step_data'][step]): + return {} + if hasattr(c_form, 'base_fields'): + for base_field in c_form.base_fields.keys(): + fields = base_field.split('__') + value = obj + for field in fields: + if not hasattr(value, field) or \ + getattr(value, field) == None: + value = obj + break + value = getattr(value, field) + if value == obj: + continue + if hasattr(value, 'pk'): + value = value.pk + if value in (True, False): + initial[base_field] = value + elif value != None: + initial[base_field] = unicode(value) + elif hasattr(c_form, 'management_form'): + initial = [] + if hasattr(c_form.form, 'base_model'): + key = c_form.form.base_model + 's' + else: + key = current_step.split('-')[0] + if not hasattr(obj, key): + return initial + keys = c_form.form.base_fields.keys() + for child_obj in getattr(obj, key).order_by('pk').all(): + if not keys: + break + vals = {} + if len(keys) == 1: + # only one field: must be the id of the object + vals[keys[0]] = unicode(child_obj.pk) + else: + for field in keys: + if hasattr(child_obj, field): + value = getattr(child_obj, field) + if hasattr(value, 'pk'): + value = value.pk + if value != None: + vals[field] = unicode(value) + if vals: + initial.append(vals) + return initial + +class PersonWizard(Wizard): + model = models.Person + +person_creation_wizard = PersonWizard.as_view([ + ('identity-person_creation', PersonForm), + ('final-person_creation', FinalForm)], + url_name='person_creation_step',) + + +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_type=None): + person_types = request.user.ishtaruser.person.person_type + 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 person_types.rights.filter(wizard__url_name='person_search' + ).count()): + 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)) + if person_type: + try: + typs = [int(tp) for tp in person_type.split('_') if tp] + typ = models.PersonType.objects.filter(pk__in=typs).all() + query = query & Q(person_type__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_town(request): + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + 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') + +def autocomplete_file(request): + person_types = request.user.ishtaruser.person.person_type + if (not request.user.has_perm('ishtar_common.view_file', models.File) and \ + not request.user.has_perm('ishtar_common.view_own_file', models.File) + and not person_types.rights.filter(wizard__url_name='file_search' + ).count()): + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(internal_reference__icontains=q) | \ + Q(towns__name__icontains=q) + try: + value = int(q) + extra = extra | Q(year=q) | Q(numeric_reference=q) + except ValueError: + pass + query = query & extra + limit = 20 + files = models.File.objects.filter(query)[:limit] + data = json.dumps([{'id':file.pk, 'value':unicode(file)} + for file in files]) + 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 = ['period', 'unit', 'material_type'] +PRIVATE_FIELDS = ('id', 'history_modifier', 'order') +def get_item(model, func_name, default_name, extra_request_keys=[], + base_request={}, bool_fields=[]): + """ + Generic treatment of tables + """ + def func(request, data_type='json', full=False, **dct): + if 'type' in dct: + data_type = dct.pop('type') + if not data_type: + data_type = 'json' + 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]) + request_keys.update(extra_request_keys) + request_items = request.method == 'POST' and request.POST or request.GET + dct = base_request.copy() + try: + old = 'old' in request_items and int(request_items['old']) + except ValueError: + return HttpResponse(None, 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]} + 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 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 + + # manage hierarchic conditions + or_reqs = [] + for req in dct.copy(): + for k_hr in HIERARCHIC_FIELDS: + if 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 + or_reqs.append(reqs) + break + query = Q(**dct) + for or_req in or_reqs: + query = query & or_req + items = model.objects.filter(query) + q = request_items.get('sidx') + + # manage sort tables + 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" + q = request_items.get('sord') + sign = q and q == u'desc' and "-" or '' + if '__' in k: + k = k.split('__')[0] + orders.append(sign+k) + items = items.order_by(*orders) + + # 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() + items = items[start:end] + + datas = [] + if old: + items = [item.get_previous(old) for item in items] + table_cols = full and [field.name for field in model._meta.fields + if field.name not in PRIVATE_FIELDS] \ + or model.TABLE_COLS + 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) + link_template = "<a href='#' onclick='load_window(\"%%s\")'>%s</a>" % \ + (unicode(_("Details"))) + if data_type == "json": + rows = [] + for data in datas: + try: + lnk = link_template % reverse('show-'+default_name, + args=[data[0], '']) + except NoReverseMatch: + 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, + }) + 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(None, 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') + date = 'date' in dct and dct.pop('date') + dct['window_id'] = "%s-%d-%s" % (name, item.pk, + datetime.datetime.now().strftime('%M%s')) + 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 = u'%s_%s_%s' % (name, slugify(unicode(item)), + n.strftime('%Y%m%d-%H%M%S')) + if doc_type == "odt" and settings.XHTML2ODT_PATH and \ + settings.ODT_TEMPLATE: + tpl = loader.get_template('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('<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, 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('sheet_%s_pdf.html' % name) + content = tpl.render(context_instance) + result = StringIO.StringIO() + html = content.encode('utf-8') + html = html.replace("<table", "<pdf:nextpage/><table repeat='1'") + pdf = pisa.pisaDocument(StringIO.StringIO(html), result) + response = HttpResponse(result.getvalue(), + mimetype='application/pdf') + response['Content-Disposition'] = 'attachment; filename=%s.pdf' % \ + filename + if not pdf.err: + return response + return HttpResponse(content, content_type="application/xhtml") + else: + tpl = loader.get_template('sheet_%s_window.html' % name) + content = tpl.render(context_instance) + 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, mimetype='text/plain') + return HttpResponse("True", mimetype='text/plain') + return func + + +get_file = get_item(models.File, 'get_file', 'file') +show_file = show_item(models.File, 'file') +revert_file = revert_item(models.File) + +def autocomplete_operation(request, non_closed=True): + person_types = request.user.ishtaruser.person.person_type + if (not request.user.has_perm('ishtar_common.view_operation', models.Operation)\ + and not request.user.has_perm('ishtar_common.view_own_operation', + models.Operation) + and not person_types.rights.filter(wizard__url_name='operation_search' + ).count()): + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(towns__name__icontains=q) + try: + value = int(q) + extra = extra | Q(year=q) | Q(operation_code=q) + except ValueError: + pass + query = query & extra + if non_closed: + query = query & Q(end_date__isnull=True) + limit = 15 + operations = models.Operation.objects.filter(query)[:limit] + data = json.dumps([{'id':operation.pk, 'value':unicode(operation)} + for operation in operations]) + return HttpResponse(data, mimetype='text/plain') + +def get_available_operation_code(request, year=None): + if not request.user.has_perm('ishtar_common.view_operation', models.Operation)\ + and not request.user.has_perm('ishtar_common.view_own_operation', + models.Operation): + return HttpResponse(mimetype='text/plain') + data = json.dumps({'id':models.Operation.get_available_operation_code(year)}) + return HttpResponse(data, mimetype='text/plain') + +get_operation = get_item(models.Operation, 'get_operation', 'operation', + bool_fields = ['end_date__isnull'], + extra_request_keys={'common_name':'common_name__icontains', + 'end_date':'end_date__isnull', + 'year_index':('year', 'operation_code')}) +show_operation = show_item(models.Operation, 'operation') +revert_operation = revert_item(models.Operation) + +get_operationsource = get_item(models.OperationSource, + 'get_operationsource', 'operationsource', + extra_request_keys={'operation__towns':'operation__towns__pk', + 'operation__operation_type':'operation__operation_type__pk', + 'operation__year':'operation__year'}) + +get_administrativeactfile = get_item(models.AdministrativeAct, + 'get_administrativeactfile', 'administrativeactfile', + extra_request_keys={'associated_file__towns':'associated_file__towns__pk', + 'operation__towns':'operation__towns__pk', + 'act_type__intented_to':'act_type__intented_to'}) +get_administrativeactop = get_item(models.AdministrativeAct, + 'get_administrativeactop', 'administrativeactop', + extra_request_keys={'associated_file__towns':'associated_file__towns__pk', + 'operation__towns':'operation__towns__pk', + 'act_type__intented_to':'act_type__intented_to'}) + +def autocomplete_organization(request, orga_type=None): + person_types = request.user.ishtaruser.person.person_type + if (not request.user.has_perm('ishtar_common.view_organization', + models.Organization) and \ + not request.user.has_perm('ishtar_common.view_own_organization', + models.Organization) + and not person_types.rights.filter(wizard__url_name='person_search' + ).count()): + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(name__icontains=q) + query = query & extra + if orga_type: + try: + typs = [int(tp) for tp in orga_type.split('_') if tp] + typ = models.OrganizationType.objects.filter(pk__in=typs).all() + query = query & Q(organization_type__in=typ) + except (ValueError, ObjectDoesNotExist): + pass + limit = 15 + organizations = models.Organization.objects.filter(query)[:limit] + data = json.dumps([{'id':org.pk, 'value':unicode(org)} + for org in organizations]) + return HttpResponse(data, mimetype='text/plain') + +show_contextrecord = show_item(models.ContextRecord, 'contextrecord') +get_contextrecord = get_item(models.ContextRecord, + 'get_contextrecord', 'contextrecord', + extra_request_keys={'parcel__town':'parcel__town__pk', + 'operation__year':'operation__year__contains', + 'datings__period':'datings__period__pk'},) +get_contextrecordsource = get_item(models.ContextRecordSource, + 'get_contextrecordsource', 'contextrecordsource', + extra_request_keys={ + 'context_record__parcel__town':'context_record__parcel__town__pk', + 'context_record__operation__year':'context_record__operation__year', + 'context_record__datings__period':'context_record__datings__period__pk', + 'context_record__unit':'context_record__unit__pk', + }) +get_archaeologicalitem = get_item(models.Item, + 'get_archaeologicalitem', 'item', + bool_fields = ['base_items__is_isolated'], + base_request={'downstream_treatment__isnull':True}, + extra_request_keys={ +'base_items__context_record__parcel__town': + 'base_items__context_record__parcel__town', +'base_items__context_record__operation__year': + 'base_items__context_record__operation__year__contains', +'base_items__context_record__operation__code_patriarche': + 'base_items__context_record__operation__code_patriarche', +'dating__period':'dating__period__pk', +'base_items__item__description':'base_items__item__description__icontains', +'base_items__is_isolated':'base_items__is_isolated'}) +get_itemsource = get_item(models.ItemSource, + 'get_itemsource', 'itemsource', + extra_request_keys={ +'item__context_record__operation__year':'item__context_record__operation__year', +'item__dating__period':'item__dating__period__pk', +'item__description':'item__description__icontains', + }) +get_container = get_item(models.Container, + 'get_container', 'container', + extra_request_keys={ +'location':'location__pk', +'container_type':'container_type__pk', +'reference':'reference__icontains', + }) + +def autocomplete_warehouse(request): + if not request.user.has_perm('ishtar_common.view_warehouse', models.Warehouse)\ + and not request.user.has_perm('ishtar_common.view_own_warehouse', + models.Warehouse) : + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(name__icontains=q) | \ + Q(warehouse_type__label__icontains=q) + query = query & extra + limit = 15 + warehouses = models.Warehouse.objects.filter(query)[:limit] + data = json.dumps([{'id':warehouse.pk, 'value':unicode(warehouse)} + for warehouse in warehouses]) + return HttpResponse(data, mimetype='text/plain') + +def autocomplete_author(request): + if not request.user.has_perm('ishtar_common.view_author', models.Author)\ + and not request.user.has_perm('ishtar_common.view_own_author', + models.Warehouse) : + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(person__name__icontains=q) | \ + Q(person__surname__icontains=q) | \ + Q(person__email__icontains=q) | \ + Q(author_type__label__icontains=q) + query = query & extra + limit = 15 + authors = models.Author.objects.filter(query)[:limit] + data = json.dumps([{'id':author.pk, 'value':unicode(author)} + for author in authors]) + return HttpResponse(data, mimetype='text/plain') + +def autocomplete_container(request): + if not request.user.has_perm('ishtar_common.view_warehouse', + models.Warehouse)\ + and not request.user.has_perm('ishtar_common.view_own_warehouse', + models.Warehouse): + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(container_type__label__icontains=q) | \ + Q(container_type__reference__icontains=q) | \ + Q(reference__icontains=q) | \ + Q(location__name=q) | \ + Q(location__town=q) + query = query & extra + limit = 15 + containers = models.Container.objects.filter(query)[:limit] + data = json.dumps([{'id':container.pk, 'value':unicode(container)} + for container in containers]) + return HttpResponse(data, mimetype='text/plain') + +def new_item(model): + def func(request, parent_name): + 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) + frm = getattr(ishtar_forms, model_name + 'Form') + dct = {'title':unicode(_(u'New %s' % model_name.lower()))} + if request.method == 'POST': + dct['form'] = frm(request.POST) + 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_to_response('window.html', dct, + context_instance=RequestContext(request)) + else: + dct['form'] = frm() + return render_to_response('window.html', dct, + context_instance=RequestContext(request)) + return func + +new_warehouse = new_item(models.Warehouse) +new_person = new_item(models.Person) +new_organization = new_item(models.Organization) +new_author = new_item(models.Author) +new_container = new_item(models.Container) + +def action(request, action_slug, obj_id=None, *args, **kwargs): + """ + Action management + """ + if not check_permission(request, action_slug, obj_id): + not_permitted_msg = ugettext(u"Operation not permitted.") + return HttpResponse(not_permitted_msg) + request.session['CURRENT_ACTION'] = action_slug + associated_wizard = action_slug + '_wizard' + dct = {} + globals_dct = globals() + if action_slug in globals_dct: + return globals_dct[action_slug](request, dct, obj_id, *args, **kwargs) + elif hasattr(ishtar_forms, action_slug + "_wizard"): + return getattr(ishtar_forms, action_slug+"_wizard")(request, *args, + **kwargs) + return render_to_response('index.html', dct, + context_instance=RequestContext(request)) + +def dashboard_main(request, dct, obj_id=None, *args, **kwargs): + """ + Main dashboard + """ + dct = {'items':[ + (_(u"Archaeological files"), models.Dashboard(models.File)), + (_(u"Operations"), models.Dashboard(models.Operation)), + (_(u"Context records"), models.Dashboard(models.ContextRecord)), + (_(u"Archaeological items"), models.Dashboard(models.Item)), + ], + 'ishtar_users':models.UserDashboard()} + return render_to_response('dashboard_main.html', dct, + context_instance=RequestContext(request)) + +def dashboard_file(request, dct, obj_id=None, *args, **kwargs): + """ + Main dashboard + """ + dct = {'dashboard': models.FileDashboard()} + return render_to_response('dashboard_file.html', dct, + context_instance=RequestContext(request)) + +def dashboard_operation(request, dct, obj_id=None, *args, **kwargs): + """ + Operation dashboard + """ + dct = {'dashboard': models.OperationDashboard()} + return render_to_response('dashboard_operation.html', dct, + context_instance=RequestContext(request)) |