#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2012-2017 É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. import logging from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.http import Http404 from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ from archaeological_files.models import File from archaeological_operations import models from forms import GenerateDocForm from ishtar_common.forms import reverse_lazy from ishtar_common.models import get_current_profile from ishtar_common.wizards import Wizard, ClosingWizard, DeletionWizard, \ SearchWizard logger = logging.getLogger(__name__) class OperationSearch(SearchWizard): model = models.Operation class OperationWizard(Wizard): model = models.Operation object_parcel_type = 'operation' parcel_step_key = 'parcels' relations_step_key = 'relations' # step including the current(s) town(s) town_step_keys = ['towns-', 'townsgeneral-'] town_input_id = 'town' # input id of the current(s) town(s) multi_towns = False # true if current town are multi valued towns_formset = True # true if towns are managed with formset wizard_done_window = reverse_lazy('show-operation') def get_template_names(self): templates = super(OperationWizard, self).get_template_names() current_step = self.steps.current if current_step.startswith(self.parcel_step_key): templates = ['ishtar/wizard/parcels_wizard.html'] + templates elif current_step.startswith(self.relations_step_key): templates = ['ishtar/wizard/relations_wizard.html'] + templates return templates def get_current_file(self): step = self.steps.current if not step: return file_form_key = 'general-' + self.url_name if self.url_name == 'operation_creation': file_form_key = 'filechoice-' + self.url_name file_id = self.session_get_value(file_form_key, "associated_file") try: idx = int(file_id) current_file = File.objects.get(pk=idx) return current_file except(TypeError, ValueError, ObjectDoesNotExist): pass def get_reminder(self): archaeological_file = self.get_current_file() if archaeological_file: return ((_("Archaeological file"), unicode(archaeological_file)),) def get_context_data(self, form, **kwargs): """ Return extra context for templates """ context = super(OperationWizard, self).get_context_data(form, **kwargs) step = self.steps.current if step.startswith('towns'): context['TOWNS'] = self.get_towns() elif step.startswith('parcels-') and self.get_current_file(): # if a file is associated to the operation add the button "Add all" context['add_all'] = True if step.startswith('parcels') and \ hasattr(self, 'automatic_parcel_association'): context['automatic_parcel_association'] = \ self.automatic_parcel_association # reminder of the current file reminder = self.get_reminder() if reminder: context['reminders'] = reminder return context def get_towns(self): """ Get available towns """ towns = [] file = self.get_current_file() if not file: return -1 try: towns = [(town.pk, unicode(town)) for town in file.towns.all()] except (ValueError, ObjectDoesNotExist): pass return sorted(towns, key=lambda x: x[1]) def get_available_parcels(self, file): self.automatic_parcel_association = False parcels = [] current_parcels = [] operation = self.get_current_object() if operation: for parcel in operation.parcels.all(): current_parcels.append((parcel.town, parcel.section, parcel.parcel_number)) parcels.append((parcel.pk, parcel.short_label)) try: for parcel in file.parcels.all(): key = (parcel.town, parcel.section, parcel.parcel_number) if key in current_parcels: current_parcels.pop(current_parcels.index(key)) continue parcels.append((parcel.pk, parcel.short_label)) if current_parcels: # not all operation parcel exist for the file self.automatic_parcel_association = True except (ValueError, ObjectDoesNotExist): pass return sorted(parcels, key=lambda x: x[1]) def get_form(self, step=None, data=None, files=None): """ Manage specifics fields """ if data: data = data.copy() else: data = {} if not step: step = self.steps.current try: form = self.get_form_list()[step] except KeyError: raise Http404() # manage the dynamic choice of towns if step.startswith('towns') and hasattr(form, 'management_form'): data['TOWNS'] = self.get_towns() elif step.startswith(self.parcel_step_key) \ and hasattr(form, 'management_form'): file = self.get_current_file() if file: data['PARCELS'] = self.get_available_parcels(file) else: town_ids = [] for town_step_key in self.town_step_keys: town_form_key = town_step_key + self.url_name town_ids = self.session_get_value( town_form_key, self.town_input_id, multi=self.towns_formset, multi_value=self.multi_towns) or [] if town_ids: towns = [] if type(town_ids) == unicode: town_ids = [town_ids] for ids in town_ids: for d in ids.split(','): if d: towns.append(d) town_ids = towns break if type(town_ids) not in (list, tuple): town_ids = [town_ids] towns = [] for town_id in town_ids: try: town = models.Town.objects.get(pk=int(town_id)) towns.append((town.pk, unicode(town))) except (ValueError, ObjectDoesNotExist): pass data['TOWNS'] = sorted(towns, key=lambda x: x[1]) data = data or None form = super(OperationWizard, self).get_form(step, data, files) return form def get_formated_datas(self, forms): """ Show a specific warning if no archaeological file is provided """ datas = super(OperationWizard, self).get_formated_datas(forms) # if the general town form is used the advertissement is relevant has_no_af = [form.prefix for form in forms if form.prefix == 'townsgeneral-operation'] and True if has_no_af: datas = [[_(u"Warning: No Archaeological File is provided. " u"If you have forget it return to the first step."), []]]\ + datas return datas def get_form_initial(self, step, data=None): initial = super(OperationWizard, self).get_form_initial(step) if step == 'general-operation_creation': initial.update(self._copy_from_associated_field()) return initial def __copy_fields(self, item, keys): initial = {} for orig_keys, dest_key in keys: value, c_value = None, item for orig_key in orig_keys: c_value = getattr(c_value, orig_key) if not c_value: break else: value = c_value if not value: continue initial[dest_key] = value return initial def _copy_from_associated_field(self): initial = {} file = self.get_current_file() if not file: return initial keys = ((('in_charge', 'pk'), 'in_charge'), (('name',), 'common_name'), (('total_surface',), 'surface'), ) initial.update(self.__copy_fields(file, keys)) if file.is_preventive(): return initial keys = ((('scientist', 'pk'), 'scientist'), (('requested_operation_type', 'pk'), 'operation_type'), (('organization', 'pk'), 'operator'), ) initial.update(self.__copy_fields(file, keys)) return initial def post(self, *args, **kwargs): request = self.request post_data = request.POST.copy() # add all parcel from available in the archaeological file if not post_data.get('add_all_parcels', None): return super(OperationWizard, self).post(*args, **kwargs) file = self.get_current_file() if not file: return super(OperationWizard, self).post(*args, **kwargs) parcel_form_key = self.steps.current # parcel_form_key = "parcels-" + self.url_name idx = -1 # remove non relevant deleted keys for k in post_data.keys(): if k.startswith(parcel_form_key) and k.endswith('-DELETE'): post_data.pop(k) for idx, parcel in enumerate(self.get_available_parcels(file)): parcel_pk, parcel_name = parcel post_data["%s-%d-parcel" % (parcel_form_key, idx)] = parcel_pk post_data[parcel_form_key + '-TOTAL_FORMS'] = idx + 2 request.POST = post_data return super(OperationWizard, self).post(*args, **kwargs) class OperationModificationWizard(OperationWizard): modification = True filter_owns = {'selec-operation_modification': ['pk']} def get_form_kwargs(self, step, **kwargs): kwargs = super(OperationModificationWizard, self).get_form_kwargs( step, **kwargs) if step != "relations-operation_modification": return kwargs kwargs["left_record"] = self.get_current_object() return kwargs class OperationClosingWizard(ClosingWizard): model = models.Operation fields = ['year', 'operation_code', 'operation_type', 'associated_file', 'in_charge', 'scientist', 'start_date', 'excavation_end_date', 'comment', 'towns', 'remains'] class OperationDeletionWizard(DeletionWizard): model = models.Operation fields = OperationClosingWizard.fields filter_owns = {'selec-operation_deletion': ['pk']} class OperationAdministrativeActWizard(OperationWizard): edit = False wizard_done_window = reverse_lazy('show-administrativeact') current_obj_slug = 'administrativeactop' ref_object_key = 'operation' def get_reminder(self): form_key = 'selec-' + self.url_name if self.url_name.endswith('_administrativeactop'): # modification and deletion are suffixed with '_modification' # and '_deletion' so it is creation operation_id = self.session_get_value(form_key, "pk") try: return ( (_(u"Operation"), unicode(models.Operation.objects.get(pk=operation_id))), ) except models.Operation.DoesNotExist: return else: admin_id = self.session_get_value(form_key, "pk") try: admin = models.AdministrativeAct.objects.get(pk=admin_id) if not admin.operation: return return ((_(u"Operation"), unicode(admin.operation)),) except models.AdministrativeAct.DoesNotExist: return def get_extra_model(self, dct, form_list): dct['history_modifier'] = self.request.user return dct def get_context_data(self, form, **kwargs): # manage document generation context = super(OperationAdministrativeActWizard, self).get_context_data(form, **kwargs) step = self.steps.current if step.startswith('final-'): general_form_key = 'administrativeact-' + self.url_name act_type = None try: act_type = models.ActType.objects.get( pk=self.session_get_value(general_form_key, "act_type")) except models.ActType.DoesNotExist: pass if act_type and act_type.associated_template.count(): context['extra_form'] = GenerateDocForm( choices=act_type.associated_template.all()) return context def get_associated_item(self, dct): return self.get_current_object() def save_model(self, dct, m2m, whole_associated_models, form_list, return_object): dct['history_modifier'] = self.request.user if 'pk' in dct: dct.pop('pk') if self.edit: admact = self.get_current_object() for k in dct: if hasattr(admact, k): setattr(admact, k, dct[k]) else: associated_item = self.get_associated_item(dct) if not associated_item: logger.warning("Admin act save: no associated model") return self.render(form_list[-1]) dct[self.ref_object_key] = associated_item admact = models.AdministrativeAct(**dct) admact.save() dct['item'] = admact # check if a doc generation is required keys = [self.storage.prefix, 'step_data', 'final-' + self.url_name, 'doc_generation'] r = self.request.session for k in keys: if k in r and r[k]: r = r[k] else: break if k == keys[-1]: # the whole list as been traversed wizard_done_window = unicode(self.wizard_done_window) if wizard_done_window: dct['wizard_done_window'] = wizard_done_window # redirect to the generated doc if r and type(r) in (tuple, list) and r[0]: dct['redirect'] = reverse('generatedoc-administrativeactop', args=[admact.pk, r[0]]) # make the new object a default self.request.session[self.current_obj_slug] = unicode(admact.pk) self.request.session[self.get_object_name(admact)] = unicode(admact.pk) res = render(self.request, 'ishtar/wizard/wizard_done.html', dct) return res class OperationEditAdministrativeActWizard(OperationAdministrativeActWizard): model = models.AdministrativeAct edit = True def get_associated_item(self, dct): return self.get_current_object().operation ######## # Site # ######## class SiteLabel(object): SITE_KEY = "" def get_label(self): return get_current_profile().get_site_label(self.SITE_KEY) class SiteSearch(SiteLabel, SearchWizard): SITE_KEY = "search" model = models.ArchaeologicalSite class SiteWizard(SiteLabel, Wizard): SITE_KEY = 'new' model = models.ArchaeologicalSite wizard_done_window = reverse_lazy('show-site') class SiteModificationWizard(SiteWizard): SITE_KEY = 'modification' modification = True class SiteDeletionWizard(SiteLabel, DeletionWizard): SITE_KEY = 'deletion' model = models.ArchaeologicalSite fields = models.ArchaeologicalSite.TABLE_COLS + ['operations'] class AdministrativeActDeletionWizard(ClosingWizard): wizard_templates = { 'final-operation_administrativeactop_deletion': 'ishtar/wizard/wizard_adminact_deletion.html', 'final-file_administrativeactfile_deletion': 'ishtar/wizard/wizard_adminact_deletion.html'} model = models.AdministrativeAct fields = ['act_type', 'in_charge', 'operator', 'scientist', 'signatory', 'operation', 'associated_file', 'signature_date', 'act_object'] if settings.COUNTRY == 'fr': fields += ['ref_sra'] def done(self, form_list, **kwargs): obj = self.get_current_object() obj.delete() return render( self.request, 'ishtar/wizard/wizard_delete_done.html', {}) def is_preventive(form_name, model, type_key='operation_type', key=''): def func(self): request = self.request storage = self.storage if storage.prefix not in request.session or \ 'step_data' not in request.session[storage.prefix] or \ form_name not in request.session[storage.prefix]['step_data'] or\ form_name + '-' + type_key not in \ request.session[storage.prefix]['step_data'][form_name]: return False try: typ = request.session[storage.prefix][ 'step_data'][form_name][form_name + '-' + type_key] if type(typ) in (list, tuple): typ = typ[0] typ = int(typ) return model.is_preventive(typ, key) except ValueError: return False return func def is_not_preventive(form_name, model, type_key='operation_type', key=''): def func(self): return not is_preventive(form_name, model, type_key, key)(self) return func def is_judiciary(form_name, model, type_key='operation_type'): def func(self): request = self.request storage = self.storage if storage.prefix not in request.session or \ 'step_data' not in request.session[storage.prefix] or \ form_name not in request.session[storage.prefix]['step_data'] \ or form_name + '-' + type_key not in \ request.session[storage.prefix]['step_data'][form_name]: return False try: typ = request.session[storage.prefix][ 'step_data'][form_name][form_name + '-' + type_key] if type(typ) in (list, tuple): typ = typ[0] typ = int(typ) return model.is_judiciary(typ) except ValueError: return False return func def has_associated_file(form_name, file_key='associated_file', negate=False): def func(self): request = self.request storage = self.storage if storage.prefix not in request.session or \ 'step_data' not in request.session[storage.prefix] or \ form_name not in request.session[storage.prefix]['step_data'] or\ form_name + '-' + file_key not in \ request.session[storage.prefix]['step_data'][form_name]: return negate try: file_id = request.session[storage.prefix][ 'step_data'][form_name][form_name + '-' + file_key] if type(file_id) in (list, tuple): file_id = file_id[0] int(file_id) return not negate except ValueError: return negate return func