#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2010-2016 É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 json import re from django.db.models import Q, F from django.http import HttpResponse, Http404, HttpResponseRedirect from django.forms.formsets import formset_factory from django.views.generic.edit import UpdateView from django.shortcuts import redirect, render from django.urls import reverse from ishtar_common.utils import ugettext_lazy as _ from ishtar_common.views import wizard_is_available, OrganizationPersonCreate, \ OrganizationPersonEdit from ishtar_common.views_item import get_item, show_item, revert_item, check_permission from archaeological_operations.wizards import ( AdministrativeActDeletionWizard, is_preventive, is_not_preventive, ) from ishtar_common.wizards import SearchWizard from archaeological_files import wizards from archaeological_files.wizards import ( FileWizard, FileModificationWizard, FileClosingWizard, FileDeletionWizard, FileAdministrativeActWizard, FileEditAdministrativeActWizard, ) from ishtar_common.views import IshtarMixin, LoginRequiredMixin from archaeological_operations.wizards import OperationWizard from archaeological_operations.views import operation_creation_wizard, get_parcel_modify from archaeological_operations.forms import FinalAdministrativeActDeleteForm from ishtar_common.forms import ClosingDateFormSelection from . import forms, models from archaeological_operations.models import Operation, AdministrativeAct RE_YEAR_INDEX = re.compile(r"([1-2][0-9]{3})-([0-9]+)") # eg.: 2014-123 def autocomplete_file(request): 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 request.user.ishtaruser.has_right( "file_search", session=request.session ) ): return HttpResponse(content_type="text/plain") if not request.GET.get("term"): return HttpResponse(content_type="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) | Q(address__icontains=q) | Q(name__icontains=q) ) try: int(q) extra = extra | Q(year=q) | Q(numeric_reference=q) except ValueError: pass m = RE_YEAR_INDEX.match(q) if m: yr, idx = m.groups() extra = extra | Q(year=yr, numeric_reference=idx) query = query & extra limit = 20 files = models.File.objects.filter(query)[:limit] data = json.dumps([{"id": file.pk, "value": str(file)} for file in files]) return HttpResponse(data, content_type="text/plain") get_file = get_item(models.File, "get_file", "file", search_form=forms.FileSelect) revert_file = revert_item(models.File) def extra_file_dct(request, item): dct = {} if request.user.has_perm( "ishtar_common.add_operation", Operation ) or request.user.ishtaruser.has_right("add_operation"): dct["can_add_operation"] = True return dct show_file = show_item(models.File, "file", extra_dct=extra_file_dct) get_administrativeactfile = get_item( AdministrativeAct, "get_administrativeactfile", "administrativeactfile", base_request={"associated_file__pk__isnull": False}, ) def dashboard_file(request, *args, **kwargs): """ Main dashboard """ dct = {"dashboard": models.FileDashboard()} return render(request, "ishtar/dashboards/dashboard_file.html", dct) file_search_wizard = wizards.FileSearch.as_view( [("general-file_search", forms.FileFormSelection)], label=_("File search"), url_name="file_search", ) file_creation_wizard_is_preventive = is_preventive( "general-file_creation", models.FileType, type_key="file_type" ) file_creation_wizard_is_not_preventive = is_not_preventive( "general-file_creation", models.FileType, type_key="file_type" ) file_creation_steps = [ ("general-file_creation", forms.FileFormGeneral), ("preventivetype-file_creation", forms.FileFormPreventiveType), ("preventiveplanning-file_creation", forms.FileFormPlanning), ("researchaddress-file_creation", forms.FileFormResearchAddress), ("generalcontractor-file_creation", forms.FileFormGeneralContractor), ("planningservice-file_creation", forms.FileFormPlanningService), ("research-file_creation", forms.FileFormResearch), ("instruction-file_creation", forms.FileFormInstruction), ("final-file_creation", forms.FinalForm), ] file_creation_wizard = FileWizard.as_view( file_creation_steps, label=_("New file"), condition_dict={ "preventivetype-file_creation": file_creation_wizard_is_preventive, "preventiveplanning-file_creation": file_creation_wizard_is_preventive, "generalcontractor-file_creation": file_creation_wizard_is_preventive, "planningservice-file_creation": file_creation_wizard_is_preventive, "researchaddress-file_creation": file_creation_wizard_is_not_preventive, "research-file_creation": file_creation_wizard_is_not_preventive, }, url_name="file_creation", ) file_modification_wizard_is_preventive = is_preventive( "general-file_modification", models.FileType, type_key="file_type" ) file_modification_wizard_is_not_preventive = is_not_preventive( "general-file_modification", models.FileType, type_key="file_type" ) file_modification_steps = [ ("selec-file_modification", forms.FileFormSelection), ("general-file_modification", forms.FileFormGeneral), ("preventivetype-file_modification", forms.FileFormPreventiveType), ("preventiveplanning-file_modification", forms.FileFormPlanning), ("researchaddress-file_modification", forms.FileFormResearchAddress), ("generalcontractor-file_modification", forms.FileFormGeneralContractor), ("planningservice-file_modification", forms.FileFormPlanningService), ("research-file_modification", forms.FileFormResearch), ("instruction-file_modification", forms.FileFormInstructionEdit), ("final-file_modification", forms.FinalForm), ] file_modification_wizard = FileModificationWizard.as_view( file_modification_steps, label=_("File modification"), condition_dict={ "preventivetype-file_modification": file_modification_wizard_is_preventive, "preventiveplanning-file_modification": file_modification_wizard_is_preventive, "generalcontractor-file_modification": file_modification_wizard_is_preventive, "planningservice-file_modification": file_modification_wizard_is_preventive, "researchaddress-file_modification": file_modification_wizard_is_not_preventive, "research-file_modification": file_modification_wizard_is_not_preventive, }, url_name="file_modification", ) def file_modify(request, pk): if not wizard_is_available(file_modification_wizard, request, models.File, pk): return HttpResponseRedirect("/") FileModificationWizard.session_set_value( request, "selec-file_modification", "pk", pk, reset=True ) return redirect( reverse("file_modification", kwargs={"step": "general-file_modification"}) ) file_closing_steps = [ ("selec-file_closing", forms.FileFormSelection), ("date-file_closing", ClosingDateFormSelection), ("final-file_closing", forms.FinalFileClosingForm), ] file_closing_wizard = wizards.FileClosingWizard.as_view( file_closing_steps, label=_("File closing"), url_name="file_closing", ) file_deletion_steps = [ ("selec-file_deletion", forms.FileFormMultiSelection), ("final-file_deletion", forms.FinalFileDeleteForm), ] file_deletion_wizard = wizards.FileDeletionWizard.as_view( file_deletion_steps, label=_("File deletion"), url_name="file_deletion", ) def file_delete(request, pk): if not wizard_is_available(file_deletion_wizard, request, models.File, pk): return HttpResponseRedirect("/") wizards.FileDeletionWizard.session_set_value( request, "selec-file_deletion", "pks", pk, reset=True ) return redirect(reverse("file_deletion", kwargs={"step": "final-file_deletion"})) class TownPlanningEdit(OrganizationPersonEdit): relative_label = _("File followed by") class TownPlanningCreate(OrganizationPersonCreate): relative_label = _("File followed by") file_administrativeactfile_search_wizard = SearchWizard.as_view( [ ( "selec-file_administrativeactfile_search", forms.AdministrativeActFileFormSelection, ) ], label=_("File: search administrative act"), url_name="file_administrativeactfile_search", ) administrativeact_steps = [ ("selec-file_administrativeactfile", forms.FileFormSelection), ( "administrativeact-file_administrativeactfile", forms.AdministrativeActFileForm, ), ("final-file_administrativeactfile", forms.FinalForm), ] file_administrativeactfile_wizard = wizards.FileAdministrativeActWizard.as_view( administrativeact_steps, label=_("File: new administrative act"), url_name="file_administrativeactfile", ) file_administrativeactfile_modification_wizard = ( wizards.FileEditAdministrativeActWizard.as_view( [ ( "selec-file_administrativeactfile_modification", forms.AdministrativeActFileModifyFormSelection, ), ( "administrativeact-file_administrativeactfile_modification", forms.AdministrativeActFileModifForm, ), ("final-file_administrativeactfile_modification", forms.FinalForm), ], label=_("File: administrative act modification"), url_name="file_administrativeactfile_modification", ) ) def file_administrativeactfile_modify(request, pk): if not wizard_is_available( file_administrativeactfile_modification_wizard, request, AdministrativeAct, pk ): return HttpResponseRedirect("/") wizards.FileEditAdministrativeActWizard.session_set_value( request, "selec-file_administrativeactfile_modification", "pk", pk, reset=True ) return redirect( reverse( "file_administrativeactfile_modification", kwargs={ "step": "administrativeact-file_administrativeactfile_modification" }, ) ) file_administrativeactfile_deletion_wizard = AdministrativeActDeletionWizard.as_view( [ ( "selec-file_administrativeactfile_deletion", forms.AdministrativeActFileFormSelection, ), ("final-file_administrativeactfile_deletion", FinalAdministrativeActDeleteForm), ], label=_("File: administrative act deletion"), url_name="file_administrativeactfile_deletion", ) def file_administrativeact_delete(request, pk): if not wizard_is_available( file_administrativeactfile_deletion_wizard, request, AdministrativeAct, pk ): return HttpResponseRedirect("/") AdministrativeActDeletionWizard.session_set_value( request, "selec-file_administrativeactfile_deletion", "pk", pk, reset=True ) return redirect( reverse( "file_administrativeactfile_deletion", kwargs={"step": "final-file_administrativeactfile_deletion"}, ) ) def file_adminact_add(request, pk, current_right=None): try: models.File.objects.get(pk=pk) except models.File.DoesNotExist: raise Http404() file_administrativeactfile_wizard(request) wizards.FileAdministrativeActWizard.session_set_value( request, "selec-file_administrativeactfile", "pk", pk, reset=True ) return redirect( reverse( "file_administrativeactfile", kwargs={"step": "administrativeact-file_administrativeactfile"}, ) ) def file_add_operation(request, pk, current_right=None): try: models.File.objects.get(pk=pk) except models.File.DoesNotExist: raise Http404() operation_creation_wizard(request) OperationWizard.session_set_value( request, "filechoice-operation_creation", "associated_file", pk, reset=True ) return redirect( reverse("operation_creation", kwargs={"step": "general-operation_creation"}) ) PARCEL_FORMSET_EXTRA_FORM = 3 file_modify_parcels = get_parcel_modify( models.File, "associated_file", "file-parcels-modify" ) def reset_wizards(request): for wizard_class, url_name in ( (FileWizard, "file_creation"), (FileModificationWizard, "file_modification"), (FileClosingWizard, "file_modification"), (FileDeletionWizard, "file_deletion"), (FileAdministrativeActWizard, "file_administrativeactfile"), ( FileEditAdministrativeActWizard, "file_administrativeactfile_modification_wizard", ), (AdministrativeActDeletionWizard, "file_administrativeactfile_deletion_wizard"), ): wizard_class.session_reset(request, url_name) class MixFormFormsetUpdateView(UpdateView): form_inlines_class = [] def get_form_inlines_class(self): return self.form_inlines_class def get(self, request, *args, **kwargs): self.object = self.get_object() self.inline_forms = [ inline(instance=self.object, prefix=getattr(inline, "form_slug", "form")) for inline in self.get_form_inlines_class() ] return super(MixFormFormsetUpdateView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() form = self.form_class(data=request.POST, instance=self.object) self.inline_forms = [ inline( instance=self.object, data=request.POST, prefix=getattr(inline, "form_slug", "form"), ) for inline in self.get_form_inlines_class() ] if form.is_valid() and all((inline.is_valid() for inline in self.inline_forms)): return self.form_valid(form, self.inline_forms) else: return self.form_invalid(form, self.inline_forms) def form_valid(self, form, inline_forms): self.object = form.save() for inline in inline_forms: inline.save() return HttpResponseRedirect(self.get_success_url()) def form_invalid(self, form, inline_forms): return self.render_to_response( self.get_context_data(form=form, inlines=inline_forms) ) def get_context_data(self, *args, **kwargs): data = super(MixFormFormsetUpdateView, self).get_context_data(*args, **kwargs) data["inline_forms"] = self.inline_forms return data class PreventiveSimpleEditView(IshtarMixin, LoginRequiredMixin, UpdateView): page_name = _("Intervention plan") form_class = forms.PreventiveFileSimpleForm template_name = "ishtar/forms/preventive_price_agreement.html" model = models.File def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) info = ' ' info += str(_("Changing the price agreement will delete all associated costs")) context["info"] = info return context def form_valid(self, form): price_agreement_id = self.object.price_agreement_id queries = [ self.object.equipment_costs.exclude( equipment_service_cost__price_agreement_id=price_agreement_id), self.object.ground_jobs.exclude( job__price_agreement_id=price_agreement_id), self.object.jobs.exclude( job__price_agreement_id=price_agreement_id) ] for query in queries: for item in query.all(): item.delete() return super().form_valid(form) def get_success_url(self): url = reverse("file-edit-preventive", args=[self.object.pk]) return url class PreventiveEditView(IshtarMixin, LoginRequiredMixin, MixFormFormsetUpdateView): page_name = _("Intervention plan") form_class = forms.FileFormPreventiveDetail template_name = "ishtar/forms/preventive_detail.html" model = models.File def get(self, request, *args, **kwargs): self.object = self.get_object() if not self.object.price_agreement: return redirect( reverse("file-edit-preventive-price", args=[self.object.pk]) ) return super(PreventiveEditView, self).get(request, *args, **kwargs) def get_form_inlines_class(self): inlines = [ forms.PreventiveFileGroundJobFormSet, forms.PreventiveFileJobFormSet, ] self.inline_types = list( models.GenericEquipmentServiceType.objects.filter(available=True).all() ) for inline_type in self.inline_types: form_class_name = ( "".join( [ t.capitalize() for t in inline_type.txt_idx.replace("-", " ") .replace("_", " ") .split(" ") ] ) + "Formset" ) form = type( form_class_name, (forms.PreventiveFileEquipmentServiceForm,), {"type_filter": inline_type.txt_idx}, ) formset = formset_factory( form, formset=forms.PreventiveFileEquipmentServiceBaseFormSet, can_delete=True, ) formset.form_label = str(inline_type) formset.form_slug = inline_type.txt_idx # formset.dynamic_add = True # todo fix formset.type_filter = inline_type.txt_idx formset.extra = 2 inlines.append(formset) return inlines def get_success_url(self): url = reverse("file-edit-preventive", args=[self.object.pk]) if getattr(self, "current_tab", None): url += "?current_tab=" + self.current_tab return url def get_form_kwargs(self): kwargs = super(PreventiveEditView, self).get_form_kwargs() try: file = models.File.objects.get(pk=self.kwargs.get("pk")) except models.Document.DoesNotExist: raise Http404() if not check_permission(self.request, "file/edit-preventive/", file.pk): raise Http404() initial = {} for k in list(self.form_class.base_fields.keys()): value = getattr(file, k) if hasattr(value, "all"): value = ",".join([str(v.pk) for v in value.all()]) if hasattr(value, "pk"): value = value.pk initial[k] = value kwargs["initial"] = initial kwargs["user"] = self.request.user self.file = file return kwargs def get_context_data(self, **kwargs): context = super(PreventiveEditView, self).get_context_data(**kwargs) unities = {} flat_rates = set() for inline_formset in context["inline_forms"]: if inline_formset.forms and hasattr(inline_formset.forms[0], "unities"): unities.update(inline_formset.forms[0].unities) if inline_formset.forms and hasattr(inline_formset.forms[0], "flat_rates"): for form in inline_formset.forms: flat_rates.update(form.flat_rates) context["form_unities"] = unities.items() context["form_flat_rates"] = flat_rates context["current_tab"] = self.request.GET.get("current_tab", None) or "planned" return context def form_valid(self, form, inline_forms): self.current_tab = self.request.POST.get("current_tab", None) or "planned" return super(PreventiveEditView, self).form_valid(form, inline_forms) def file_edit_preventive_add_default(request, pk, current_right=None): job_attrs = ( ("default_daily_need_on_ground", models.PreventiveFileGroundJob), ("default_daily_need", models.PreventiveFileJob), ) price_agreement_id = None q = models.File.objects.filter(pk=pk).values("price_agreement_id") if q.count(): price_agreement_id = q.all()[0]["price_agreement_id"] for attr, job_model in job_attrs: jobs = models.Job.objects.exclude(**{attr: 0}).filter( price_agreement_id=price_agreement_id) for job in jobs: q = job_model.objects.filter(job=job, file_id=pk) if not q.count(): dct = { "job": job, "file_id": pk, "days_planned": 1, "man_by_day_planned": getattr(job, attr), } job_model.objects.create(**dct) q = models.EquipmentServiceCost.objects.exclude( default_quantity_by_day=0 ).filter( price_agreement_id=price_agreement_id ) for cost in list(q.all()): q = models.PreventiveFileEquipmentServiceCost.objects.filter( equipment_service_cost=cost, file_id=pk ) if not q.count(): models.PreventiveFileEquipmentServiceCost.objects.create( equipment_service_cost=cost, file_id=pk, quantity_by_day_planned=cost.default_quantity_by_day, days_planned=1, ) return redirect(reverse("file-edit-preventive", kwargs={"pk": pk})) def file_edit_preventive_copy_planned(request, pk, current_right=None): job_models = ( models.PreventiveFileGroundJob, models.PreventiveFileJob, ) for job_model in job_models: job_model.objects.filter(file_id=pk).update( man_by_day_worked=F("man_by_day_planned"), days_worked=F("days_planned") ) models.PreventiveFileEquipmentServiceCost.objects.filter(file_id=pk).update( quantity_by_day_worked=F("quantity_by_day_planned"), days_worked=F("days_planned"), ) return redirect( reverse("file-edit-preventive", kwargs={"pk": pk}) + "?current_tab=worked" )