diff options
author | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-10-20 15:25:07 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-10-20 15:25:07 +0200 |
commit | beb45ac4d420034f9aec53eaf7ea783e178d5361 (patch) | |
tree | b26e820671aa6af552a4b03147c44a9d2aa84be8 /archaeological_operations | |
parent | 666747d6371a908e6fe1968e2e802e3065d610c5 (diff) | |
download | Ishtar-beb45ac4d420034f9aec53eaf7ea783e178d5361.tar.bz2 Ishtar-beb45ac4d420034f9aec53eaf7ea783e178d5361.zip |
Djangoization - Major refactoring (step 3)
Reorganization of views, urls, menus, admin, forms.
Changes on models.
Diffstat (limited to 'archaeological_operations')
-rw-r--r-- | archaeological_operations/admin.py | 72 | ||||
-rw-r--r-- | archaeological_operations/forms.py | 804 | ||||
-rw-r--r-- | archaeological_operations/ishtar_menu.py | 98 | ||||
-rw-r--r-- | archaeological_operations/migrations/0001_initial.py | 54 | ||||
-rw-r--r-- | archaeological_operations/models.py | 500 | ||||
-rw-r--r-- | archaeological_operations/urls.py | 75 | ||||
-rw-r--r-- | archaeological_operations/views.py | 97 |
7 files changed, 1654 insertions, 46 deletions
diff --git a/archaeological_operations/admin.py b/archaeological_operations/admin.py new file mode 100644 index 000000000..de8b47edc --- /dev/null +++ b/archaeological_operations/admin.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.conf import settings +from django.contrib import admin + +from ishtar_common.admin import HistorizedObjectAdmin + +import models + +class AdministrativeActAdmin(HistorizedObjectAdmin): + list_display = ('operation', 'act_type', 'signature_date') + list_filter = ('act_type',) + search_fields = ('operation__name',) + model = models.AdministrativeAct + +admin.site.register(models.AdministrativeAct, AdministrativeActAdmin) + +class PeriodAdmin(admin.ModelAdmin): + list_display = ('label', 'start_date', 'end_date', 'parent') + model = models.Period + +admin.site.register(models.Period, PeriodAdmin) + +class OperationAdmin(HistorizedObjectAdmin): + list_display = ['year', 'operation_code', 'start_date', + 'excavation_end_date', 'end_date', + 'operation_type'] + list_filter = ("year", "operation_type",) + search_fields = ['towns__name', 'operation_code'] + if settings.COUNTRY == 'fr': + list_display += ['code_patriarche'] + search_fields += ['code_patriarche'] + model = models.Operation + +admin.site.register(models.Operation, OperationAdmin) + +class OperationSourceAdmin(admin.ModelAdmin): + list_display = ('operation', 'title', 'source_type',) + list_filter = ('source_type',) + search_fields = ('title', 'operation__name') + model = models.OperationSource + +admin.site.register(models.OperationSource, OperationSourceAdmin) + +class ParcelAdmin(HistorizedObjectAdmin): + list_display = ('section', 'parcel_number', 'operation', 'associated_file') + search_fields = ('operation__name',) + model = models.Parcel + +admin.site.register(models.Parcel, ParcelAdmin) + +basic_models = [models.OperationType, models.RemainType, models.ActType, + models.ParcelOwner] +for model in basic_models: + admin.site.register(model) diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py new file mode 100644 index 000000000..d4152d4fa --- /dev/null +++ b/archaeological_operations/forms.py @@ -0,0 +1,804 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +""" +Operations forms definitions +""" +import datetime + +from django import forms +from django.conf import settings +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.core import validators +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Max +from django.utils.translation import ugettext_lazy as _ + +import models +import widgets +from ishtar_common.forms import Wizard, FinalForm, FormSet, SearchWizard, \ + ClosingWizard, ClosingDateFormSelection, DeletionWizard, formset_factory, \ + get_now, reverse_lazy, get_form_selection +from ishtar_common.forms_common import TownForm, TownFormSet, TownFormset, \ + AuthorFormset, SourceForm, SourceWizard, SourceSelect, \ + SourceDeletionForm, get_town_field + +def is_preventive(form_name, model, type_key='operation_type', key=''): + def func(self, request, 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 = int(request.session[storage.prefix]['step_data']\ + [form_name][form_name+'-'+type_key]) + return model.is_preventive(typ, key) + except ValueError: + return False + return func + +class ParcelForm(forms.Form): + form_label = _("Parcels") + base_model = 'parcel' + associated_models = {'parcel':models.Parcel, 'town':models.Town} + town = forms.ChoiceField(label=_("Town"), choices=(), required=False, + validators=[models.valid_id(models.Town)]) + section = forms.CharField(label=_(u"Section"), required=False, + validators=[validators.MaxLengthValidator(4)]) + parcel_number = forms.CharField(label=_(u"Parcel number"), required=False, + validators=[validators.MaxLengthValidator(6)]) + year = forms.IntegerField(label=_("Year"), required=False, + validators=[validators.MinValueValidator(1900), + validators.MaxValueValidator(2100)]) + def __init__(self, *args, **kwargs): + towns = None + if 'data' in kwargs and 'TOWNS' in kwargs['data']: + towns = kwargs['data']['TOWNS'] + # clean data if not "real" data + prefix_value = kwargs['prefix'] + '-town' + if not [k for k in kwargs['data'].keys() + if k.startswith(prefix_value) and kwargs['data'][k]]: + kwargs['data'] = None + if 'files' in kwargs: + kwargs.pop('files') + super(ParcelForm, self).__init__(*args, **kwargs) + if towns: + self.fields['town'].choices = [('', '--')] + towns + + def clean(self): + """Check required fields""" + if any(self.errors): + return + if not self.cleaned_data or DELETION_FIELD_NAME in self.cleaned_data \ + and self.cleaned_data[DELETION_FIELD_NAME]: + return + for key in ('town', 'parcel_number', 'section'): + if not key in self.cleaned_data or not self.cleaned_data[key]: + raise forms.ValidationError(_(u"Town section and parcel number " + u"fields are required.")) + return self.cleaned_data + +class ParcelFormSet(FormSet): + def clean(self): + """Checks that no parcels are duplicated.""" + return self.check_duplicate(('town', 'parcel_number', 'year'), + _(u"There are identical parcels.")) + +ParcelFormSet = formset_factory(ParcelForm, can_delete=True, + formset=ParcelFormSet) +ParcelFormSet.form_label = _(u"Parcels") + +class OperationWizard(Wizard): + model = models.Operation + object_parcel_type = 'operation' + + def get_template(self, request, storage): + templates = super(OperationWizard, self).get_template(request, storage) + current_step = storage.get_current_step() or self.get_first_step( + request, storage) + if current_step.startswith('towns-'): + templates = ['towns_wizard.html'] + templates + return templates + + def get_extra_context(self, request, storage): + """ + Return extra context for templates + """ + context = super(OperationWizard, self).get_extra_context(request, + storage) + step = self.determine_step(request, storage) + if not step.startswith('towns-'): + return context + context['TOWNS'] = self.get_towns(request, storage) + return context + + def get_towns(self, request, storage): + """ + Obtention des villes disponibles + """ + general_form_key = 'general-' + self.url_name + towns = [] + file_id = self.session_get_value(request, storage, general_form_key, + "associated_file") + if file_id: + try: + for town in models.File.objects.get(pk=int(file_id) + ).towns.all(): + towns.append((town.pk, unicode(town))) + except (ValueError, ObjectDoesNotExist): + pass + return sorted(towns, key=lambda x:x[1]) + else: + return -1 + + def get_form(self, request, storage, step=None, data=None, files=None): + """ + Manage specifics fields + """ + if data: + data = data.copy() + else: + data = {} + if not step: + step = self.determine_step(request, storage) + form = self.get_form_list(request, storage)[step] + general_form_key = 'general-' + self.url_name + # manage the dynamic choice of towns + if step.startswith('towns-') and hasattr(form, 'management_form'): + data['TOWNS'] = self.get_towns(request, storage) + elif step.startswith('parcels') and hasattr(form, 'management_form'): + file_id = self.session_get_value(request, storage, general_form_key, + "associated_file") + if file_id: + parcels = [] + try: + for parcel in models.File.objects.get(pk=int(file_id) + ).parcels.all(): + parcels.append((parcel.pk, parcel.short_label())) + except (ValueError, ObjectDoesNotExist): + pass + data['PARCELS'] = sorted(parcels, key=lambda x:x[1]) + else: + town_form_key = step.startswith('parcelsgeneral') \ + and 'townsgeneral-' or 'towns-' + town_form_key += self.url_name + town_ids = self.session_get_value(request, storage, + town_form_key, 'town', multi=True) or [] + 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(request, storage, step, + data, files) + return form + + def get_formated_datas(self, forms): + """ + Show a specific warning if no archaelogical file is provided + """ + datas = super(OperationWizard, self).get_formated_datas(forms) + # if the general town form is used the advertissement is pertinent + has_no_af = [form.prefix for form in forms + if form.prefix == 'townsgeneral-operation'] and True + if has_no_af: + datas = [[_(u"Warning: No Archaelogical File is provided. " + u"If you have forget it return to the first step."), []]]\ + + datas + return datas + +class OperationSelect(forms.Form): + common_name = forms.CharField(label=_(u"Name"), max_length=30) + towns = get_town_field() + operation_type = forms.ChoiceField(label=_(u"Operation type"), + choices=[]) + remains = forms.ChoiceField(label=_(u"Remains"), + choices=models.RemainType.get_types()) + year = forms.IntegerField(label=_("Year")) + end_date = forms.NullBooleanField(label=_(u"Is open?")) + + def __init__(self, *args, **kwargs): + super(OperationSelect, self).__init__(*args, **kwargs) + self.fields['operation_type'].choices = models.OperationType.get_types() + self.fields['operation_type'].help_text = models.OperationType.get_help() + +class OperationFormSelection(forms.Form): + form_label = _(u"Operation search") + associated_models = {'pk':models.Operation} + currents = {'pk':models.Operation} + pk = forms.IntegerField(label="", required=False, + widget=widgets.JQueryJqGrid(reverse_lazy('get-operation'), + OperationSelect(), models.Operation, + source_full=reverse_lazy('get-operation-full')), + validators=[models.valid_id(models.Operation)]) + + def clean(self): + cleaned_data = self.cleaned_data + if 'pk' not in cleaned_data or not cleaned_data['pk']: + raise forms.ValidationError(_(u"You should select an operation.")) + return cleaned_data + +class OperationCodeInput(forms.TextInput): + """Manage auto complete whene changing year in form""" + def render(self, *args, **kwargs): + name, value = args + base_name = '-'.join(name.split('-')[:-1]) + rendered = super(OperationCodeInput, self).render(*args, **kwargs) + js = u"""\n <script type="text/javascript"><!--// + function initialyse_operation_code () { + // if the form is in creation mode + if(!$("#id_%(base_name)s-pk").val()){ + $("#id_%(base_name)s-year").change(function() { + var year = $("#id_%(base_name)s-year").val(); + var url = "%(url)s" + year; + $.getJSON(url, function(data) { + $("#id_%(name)s").val(data.id); + }); + }); + } + } + $(document).ready(initialyse_operation_code()); + //--></script>\n""" % {'base_name':base_name, 'name':name, + 'url':reverse_lazy('get_available_operation_code')} + return rendered + js + +class OperationFormGeneral(forms.Form): + form_label = _(u"General") + associated_models = {'in_charge':models.Person, + 'associated_file':models.File, + 'operation_type':models.OperationType} + currents = {'associated_file':models.File} + pk = forms.IntegerField(required=False, widget=forms.HiddenInput) + in_charge = forms.IntegerField(label=_("Person in charge of the operation"), + widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person', + args=["_".join( + [unicode(models.PersonType.objects.get(txt_idx='head_scientist').pk), + unicode(models.PersonType.objects.get(txt_idx='sra_agent').pk)])]), + associated_model=models.Person, new=True), + validators=[models.valid_id(models.Person)], required=False) + associated_file = forms.IntegerField(label=_(u"Archaelogical file"), + widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-file'), + associated_model=models.File), + validators=[models.valid_id(models.File)], required=False) + operation_type = forms.ChoiceField(label=_(u"Operation type"), + choices=[]) + start_date = forms.DateField(label=_(u"Start date"), required=False, + widget=widgets.JQueryDate) + excavation_end_date = forms.DateField(label=_(u"Excavation end date"), + required=False, widget=widgets.JQueryDate) + surface = forms.IntegerField(required=False, widget=widgets.AreaWidget, + label=_(u"Total surface (m²)"), + validators=[validators.MinValueValidator(0), + validators.MaxValueValidator(999999999)]) + year = forms.IntegerField(label=_(u"Year"), + initial=lambda:datetime.datetime.now().year, + validators=[validators.MinValueValidator(1900), + validators.MaxValueValidator(2100)]) + operation_code = forms.IntegerField(label=_(u"Operation code"), + initial=models.Operation.get_available_operation_code, + widget=OperationCodeInput) + common_name = forms.CharField(label=_(u"Generic name"), required=False, + max_length=120, widget=forms.Textarea) + operator_reference = forms.CharField(label=_(u"Operator reference"), + required=False, max_length=20) + if settings.COUNTRY == 'fr': + code_patriarche = forms.IntegerField(label=u"Code PATRIARCHE", + required=False) + code_dracar = forms.CharField(label=u"Code DRACAR", required=False, + validators=[validators.MaxLengthValidator(10)]) + comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea, + required=False) + + def __init__(self, *args, **kwargs): + super(OperationFormGeneral, self).__init__(*args, **kwargs) + self.fields['operation_type'].choices = models.OperationType.get_types() + self.fields['operation_type'].help_text = models.OperationType.get_help() + + def clean(self): + cleaned_data = self.cleaned_data + # verify the logic between start date and excavation end date + if cleaned_data['excavation_end_date']: + if not self.cleaned_data['start_date']: + raise forms.ValidationError(_(u"If you want to set an " + u"excavation end date you have to provide a start date.")) + if cleaned_data['excavation_end_date'] < cleaned_data['start_date']: + raise forms.ValidationError(_(u"The excavation end date "\ + u"cannot be before the start date.")) + year = self.cleaned_data.get("year") + operation_code = cleaned_data.get("operation_code") + ops = models.Operation.objects.filter(year=year, + operation_code=operation_code) + # manage unique operation ID + if 'pk' in cleaned_data and cleaned_data['pk']: + ops = ops.exclude(pk=cleaned_data['pk']) + if ops.count(): + max_val = models.Operation.objects.filter(year=year).aggregate( + Max('operation_code'))["operation_code__max"] + raise forms.ValidationError(_(u"Operation code already exist for " + u"year: %(year)d - use a value bigger than %(last_val)d") % { + 'year':year, 'last_val':max_val}) + return self.cleaned_data + +class OperationFormPreventive(forms.Form): + form_label = _(u"Preventive informations - excavation") + cost = forms.IntegerField(label=_(u"Cost (€)"), required=False) + scheduled_man_days = forms.IntegerField(label=_(u"Scheduled man-days"), + required=False) + optional_man_days = forms.IntegerField(label=_(u"Optional man-days"), + required=False) + effective_man_days = forms.IntegerField(label=_(u"Effective man-days"), + required=False) + if settings.COUNTRY == 'fr': + fnap_financing = forms.FloatField(required=False, + label=u"Pourcentage de financement FNAP", + validators=[validators.MinValueValidator(0), + validators.MaxValueValidator(100)]) + +class OperationFormPreventiveDiag(forms.Form): + form_label = _("Preventive informations - diagnostic") + if settings.COUNTRY == 'fr': + zoning_prescription = forms.NullBooleanField(required=False, + label=_(u"Prescription on zoning")) + large_area_prescription = forms.NullBooleanField(required=False, + label=_(u"Prescription on large area")) + geoarchaeological_context_prescription = forms.NullBooleanField( + required=False, label=_(u"Prescription on geoarchaeological context")) + +class SelectedTownForm(forms.Form): + form_label = _("Towns") + associated_models = {'town':models.Town} + town = forms.ChoiceField(label=_("Town"), choices=(), + validators=[models.valid_id(models.Town)]) + def __init__(self, *args, **kwargs): + towns = None + if 'data' in kwargs and 'TOWNS' in kwargs['data']: + towns = kwargs['data']['TOWNS'] + # clean data if not "real" data + prefix_value = kwargs['prefix'] + '-town' + if not [k for k in kwargs['data'].keys() + if k.startswith(prefix_value) and kwargs['data'][k]]: + kwargs['data'] = None + if 'files' in kwargs: + kwargs.pop('files') + super(SelectedTownForm, self).__init__(*args, **kwargs) + if towns and towns != -1: + self.fields['town'].choices = [('', '--')] + towns + +SelectedTownFormset = formset_factory(SelectedTownForm, can_delete=True, + formset=TownFormSet) +SelectedTownFormset.form_label = _(u"Towns") + +class SelectedParcelForm(forms.Form): + form_label = _("Parcels") + associated_models = {'parcel':models.Parcel} + parcel = forms.ChoiceField(label=_("Parcel"), choices=(), + validators=[models.valid_id(models.Parcel)]) + def __init__(self, *args, **kwargs): + parcels = None + if 'data' in kwargs and 'PARCELS' in kwargs['data']: + parcels = kwargs['data']['PARCELS'] + # clean data if not "real" data + prefix_value = kwargs['prefix'] + '-parcel' + if not [k for k in kwargs['data'].keys() + if k.startswith(prefix_value) and kwargs['data'][k]]: + kwargs['data'] = None + if 'files' in kwargs: + kwargs.pop('files') + super(SelectedParcelForm, self).__init__(*args, **kwargs) + if parcels: + self.fields['parcel'].choices = [('', '--')] + parcels + +SelectedParcelFormSet = formset_factory(SelectedParcelForm, can_delete=True, + formset=ParcelFormSet) +SelectedParcelFormSet.form_label = _("Parcels") + +SelectedParcelGeneralFormSet = formset_factory(ParcelForm, can_delete=True, + formset=ParcelFormSet) +SelectedParcelGeneralFormSet.form_label = _("Parcels") + +class RemainForm(forms.Form): + form_label = _("Remain types") + base_model = 'remain' + associated_models = {'remain':models.RemainType} + remain = forms.ChoiceField(label=_("Remain type"), required=False, + choices=models.RemainType.get_types()) + +class RemainFormSet(FormSet): + def clean(self): + """Checks that no remain types are duplicated.""" + return self.check_duplicate(['remain_type'], + _(u"There are identical remain types")) + +RemainFormset = formset_factory(RemainForm, can_delete=True, + formset=RemainFormSet) +RemainFormset.form_label = _("Remain types") + +class PeriodForm(forms.Form): + form_label = _("Periods") + base_model = 'period' + associated_models = {'period':models.Period} + period = forms.ChoiceField(label=_("Period"), required=False, + choices=models.Period.get_types()) + +class PeriodFormSet(FormSet): + def clean(self): + """Checks that no period are duplicated.""" + return self.check_duplicate(['period'], + _(u"There are identical periods")) + +PeriodFormset = formset_factory(PeriodForm, can_delete=True, + formset=PeriodFormSet) +PeriodFormset.form_label = _("Periods") + +operation_search_wizard = SearchWizard([ + ('general-operation_search', OperationFormSelection)], + url_name='operation_search',) + +def has_associated_file(form_name, file_key='associated_file', negate=False): + def func(self, request, 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 = int(request.session[storage.prefix]['step_data']\ + [form_name][form_name+'-'+file_key]) + return not negate + except ValueError: + return negate + return func + +operation_creation_wizard = OperationWizard([ + ('general-operation_creation', OperationFormGeneral), + ('preventive-operation_creation', OperationFormPreventive), + ('preventivediag-operation_creation', OperationFormPreventiveDiag), + ('townsgeneral-operation_creation', TownFormset), + ('towns-operation_creation', SelectedTownFormset), + ('parcelsgeneral-operation_creation', SelectedParcelGeneralFormSet), + ('parcels-operation_creation', SelectedParcelFormSet), + ('remains-operation_creation', RemainFormset), + ('periods-operation_creation', PeriodFormset), + ('final-operation_creation', FinalForm)], + condition_list={ +'preventive-operation_creation':is_preventive('general-operation_creation', + models.OperationType, 'operation_type', 'prev_excavation'), +'preventivediag-operation_creation':is_preventive('general-operation_creation', + models.OperationType, 'operation_type', 'arch_diagnostic'), +'townsgeneral-operation_creation':has_associated_file( + 'general-operation_creation', negate=True), +'towns-operation_creation':has_associated_file('general-operation_creation'), +'parcelsgeneral-operation_creation':has_associated_file( + 'general-operation_creation', negate=True), +'parcels-operation_creation':has_associated_file('general-operation_creation'), + }, + url_name='operation_creation',) + +class OperationModificationWizard(OperationWizard): + modification = True + +operation_modification_wizard = OperationModificationWizard([ + ('selec-operation_modification', OperationFormSelection), + ('general-operation_modification', OperationFormGeneral), + ('preventive-operation_modification', OperationFormPreventive), + ('preventivediag-operation_modification', OperationFormPreventiveDiag), + ('towns-operation_modification', SelectedTownFormset), + ('townsgeneral-operation_modification', TownFormset), + ('parcels-operation_modification', SelectedParcelFormSet), + ('parcelsgeneral-operation_modification', SelectedParcelGeneralFormSet), + ('remains-operation_modification', RemainFormset), + ('periods-operation_modification', PeriodFormset), + ('final-operation_modification', FinalForm)], + condition_list={ +'preventive-operation_modification':is_preventive( + 'general-operation_modification', models.OperationType, + 'operation_type', 'prev_excavation'), +'preventivediag-operation_modification':is_preventive( + 'general-operation_modification', models.OperationType, + 'operation_type', 'arch_diagnostic'), +'townsgeneral-operation_modification':has_associated_file( + 'general-operation_modification', negate=True), +'towns-operation_modification':has_associated_file( + 'general-operation_modification'), +'parcelsgeneral-operation_modification':has_associated_file( + 'general-operation_modification', negate=True), +'parcels-operation_modification':has_associated_file( + 'general-operation_modification'), + }, + url_name='operation_modification',) + +class OperationClosingWizard(ClosingWizard): + model = models.Operation + fields = ['year', 'operation_code', 'operation_type', 'associated_file', +'in_charge', 'start_date', 'excavation_end_date', 'comment', 'towns', 'remains'] + +class FinalOperationClosingForm(FinalForm): + confirm_msg = " " + confirm_end_msg = _(u"Would you like to close this operation?") + +operation_closing_wizard = OperationClosingWizard([ + ('selec-operation_closing', OperationFormSelection), + ('date-operation_closing', ClosingDateFormSelection), + ('final-operation_closing', FinalOperationClosingForm)], + url_name='operation_closing',) + +class OperationDeletionWizard(DeletionWizard): + model = models.Operation + fields = OperationClosingWizard.fields + +class OperationDeletionForm(FinalForm): + confirm_msg = " " + confirm_end_msg = _(u"Would you like to delete this operation?") + +operation_deletion_wizard = OperationDeletionWizard([ + ('selec-operation_deletion', OperationFormSelection), + ('final-operation_deletion', OperationDeletionForm)], + url_name='operation_deletion',) + +#################################### +# Source management for operations # +#################################### + +class OperationSourceWizard(SourceWizard): + model = models.OperationSource + def get_form_initial(self, request, storage, step): + initial = super(OperationSourceWizard, self).get_form_initial(request, + storage, step) + # put default index and operation_id field in the main source form + general_form_key = 'selec-' + self.url_name + if step.startswith('source-') \ + and self.session_has_key(request, storage, general_form_key): + gen_storage = request.session[storage.prefix]['step_data']\ + [general_form_key] + if general_form_key+"-operation" in gen_storage: + operation_id = int(gen_storage[general_form_key+"-operation"]) + elif general_form_key+"-pk" in gen_storage: + pk = int(gen_storage[general_form_key+"-pk"]) + try: + source = models.OperationSource.objects.get(pk=pk) + operation_id = source.operation.pk + except ObjectDoesNotExist: + pass + if operation_id: + initial['hidden_operation_id'] = operation_id + if 'index' not in initial: + max_val = models.OperationSource.objects.filter( + operation__pk=operation_id).aggregate( + Max('index'))["index__max"] + initial['index'] = max_val and (max_val + 1) or 1 + return initial + +class OperationSourceForm(SourceForm): + pk = forms.IntegerField(required=False, widget=forms.HiddenInput) + index = forms.IntegerField(label=_(u"Index")) + hidden_operation_id = forms.IntegerField(label="", widget=forms.HiddenInput) + + def __init__(self, *args, **kwargs): + super(OperationSourceForm, self).__init__(*args, **kwargs) + keyOrder = self.fields.keyOrder + keyOrder.pop(keyOrder.index('index')) + keyOrder.insert(keyOrder.index('source_type') + 1, 'index') + + def clean(self): + # manage unique operation ID + cleaned_data = self.cleaned_data + operation_id = cleaned_data.get("hidden_operation_id") + index = cleaned_data.get("index") + srcs = models.OperationSource.objects.filter(index=index, + operation__pk=operation_id) + if 'pk' in cleaned_data and cleaned_data['pk']: + srcs = srcs.exclude(pk=cleaned_data['pk']) + if srcs.count(): + max_val = models.OperationSource.objects.filter( + operation__pk=operation_id + ).aggregate(Max('index'))["index__max"] + operation = models.Operation.objects.get(pk=operation_id) + raise forms.ValidationError(_(u"Index already exist for " +"operation: %(operation)s - use a value bigger than %(last_val)d") % { + "operation":unicode(operation), 'last_val':max_val}) + return cleaned_data + +SourceOperationFormSelection = get_form_selection( + 'SourceOperationFormSelection', _(u"Operation search"), 'operation', + models.Operation, OperationSelect, 'get-operation', + _(u"You should select an operation.")) + +operation_source_creation_wizard = OperationSourceWizard([ + ('selec-operation_source_creation', SourceOperationFormSelection), + ('source-operation_source_creation',OperationSourceForm), + ('authors-operation_source_creation', AuthorFormset), + ('final-operation_source_creation', FinalForm)], + url_name='operation_source_creation',) + +class OperationSourceSelect(SourceSelect): + operation__towns = get_town_field(label=_(u"Operation's town")) + operation__operation_type = forms.ChoiceField(label=_(u"Operation type"), + choices=[]) + operation__year = forms.IntegerField(label=_(u"Operation's year")) + + def __init__(self, *args, **kwargs): + super(OperationSourceSelect, self).__init__(*args, **kwargs) + self.fields['operation__operation_type'].choices = \ + models.OperationType.get_types() + self.fields['operation__operation_type'].help_text = \ + models.OperationType.get_help() + + +OperationSourceFormSelection = get_form_selection( + 'OperationSourceFormSelection', _(u"Documentation search"), 'pk', + models.OperationSource, OperationSourceSelect, 'get-operationsource', + _(u"You should select a document.")) + +operation_source_modification_wizard = OperationSourceWizard([ + ('selec-operation_source_modification', OperationSourceFormSelection), + ('source-operation_source_modification', OperationSourceForm), + ('authors-operation_source_modification', AuthorFormset), + ('final-operation_source_modification', FinalForm)], + url_name='operation_source_modification',) + +class OperationSourceDeletionWizard(DeletionWizard): + model = models.OperationSource + fields = ['operation', 'title', 'source_type', 'authors',] + +operation_source_deletion_wizard = OperationSourceDeletionWizard([ + ('selec-operation_source_deletion', OperationSourceFormSelection), + ('final-operation_source_deletion', SourceDeletionForm)], + url_name='operation_source_deletion',) + +################################################ +# Administrative act management for operations # +################################################ + +class OperationAdministrativeActWizard(OperationWizard): + edit = False + + def get_extra_model(self, dct, request, storage, form_list): + dct['history_modifier'] = request.user + return dct + + def get_associated_item(self, request, storage, dct): + return self.get_current_object(request, storage) + + def save_model(self, dct, m2m, whole_associated_models, request, storage, + form_list, return_object): + associated_item = self.get_associated_item(request, storage, dct) + if not associated_item: + return self.render(request, storage, form_list[-1]) + if isinstance(associated_item, models.File): + dct['associated_file'] = associated_item + elif isinstance(associated_item, models.Operation): + dct['operation'] = associated_item + dct['history_modifier'] = request.user + if 'pk' in dct: + dct.pop('pk') + if self.edit: + admact = self.get_current_object(request, storage) + for k in dct: + if hasattr(admact, k): + setattr(admact, k, dct[k]) + else: + admact = models.AdministrativeAct(**dct) + admact.save() + res = render_to_response('wizard_done.html', {}, + context_instance=RequestContext(request)) + return res + +class OperationEditAdministrativeActWizard(OperationAdministrativeActWizard): + model = models.AdministrativeAct + edit = True + def get_associated_item(self, request, storage, dct): + return self.get_current_object(request, storage).operation + +class AdministrativeActOpeSelect(forms.Form): + operation__towns = get_town_field() + act_type = forms.ChoiceField(label=_("Act type"), choices=[]) + + def __init__(self, *args, **kwargs): + super(AdministrativeActOpeSelect, self).__init__(*args, **kwargs) + self.fields['act_type'].choices = models.ActType.get_types( + dct={'intented_to':'O'}) + self.fields['act_type'].help_text = models.ActType.get_help( + dct={'intented_to':'O'}) + +class AdministrativeActOpeFormSelection(forms.Form): + form_label = _("Administrative act search") + associated_models = {'pk':models.AdministrativeAct} + currents = {'pk':models.AdministrativeAct} + pk = forms.IntegerField(label="", required=False, + widget=widgets.JQueryJqGrid(reverse_lazy('get-administrativeactop'), + AdministrativeActOpeSelect(), models.AdministrativeAct, + table_cols='TABLE_COLS_OPE'), + validators=[models.valid_id(models.AdministrativeAct)]) + + def clean(self): + cleaned_data = self.cleaned_data + if 'pk' not in cleaned_data or not cleaned_data['pk']: + raise forms.ValidationError(_(u"You should select an administrative" + " act.")) + return cleaned_data + +class AdministrativeActOpeForm(forms.Form): + form_label = _("General") + associated_models = {'act_type':models.ActType, + 'signatory':models.Person} + act_type = forms.ChoiceField(label=_("Act type"), choices=[]) + signatory = forms.IntegerField(label=_("Signatory"), + widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person'), + associated_model=models.Person, new=True), + validators=[models.valid_id(models.Person)]) + act_object = forms.CharField(label=_(u"Object"), max_length=200, + widget=forms.Textarea) + signature_date = forms.DateField(label=_(u"Signature date"), + widget=widgets.JQueryDate) + if settings.COUNTRY == 'fr': + ref_sra = forms.CharField(label=u"Référence SRA", max_length=15) + + def __init__(self, *args, **kwargs): + super(AdministrativeActOpeForm, self).__init__(*args, **kwargs) + self.fields['act_type'].choices = models.ActType.get_types( + dct={'intented_to':'O'}) + self.fields['act_type'].help_text = models.ActType.get_help( + dct={'intented_to':'O'}) + +class AdministrativeActDeletionWizard(ClosingWizard): + model = models.AdministrativeAct + fields = ['act_type', 'in_charge', 'operator', 'scientific', 'signatory', + 'operation', 'associated_file', 'signature_date', 'act_object',] + if settings.COUNTRY == 'fr': + fields += ['ref_sra'] + + def done(self, request, storage, form_list, **kwargs): + obj = self.get_current_object(request, storage) + obj.delete() + return render_to_response('wizard_done.html', {}, + context_instance=RequestContext(request)) + +class FinalAdministrativeActDeleteForm(FinalForm): + confirm_msg = " " + confirm_end_msg = _(u"Would you like to delete this administrative act?") + +operation_administrativeactop_wizard = OperationAdministrativeActWizard([ + ('selec-operation_administrativeactop', OperationFormSelection), + ('administrativeact-operation_administrativeactop', AdministrativeActOpeForm), + ('final-operation_administrativeactop', FinalForm)], + url_name='operation_administrativeactop',) + +operation_administrativeactop_modification_wizard = \ + OperationEditAdministrativeActWizard([ + ('selec-operation_administrativeactop_modification', + AdministrativeActOpeFormSelection), + ('administrativeact-operation_administrativeactop_modification', + AdministrativeActOpeForm), + ('final-operation_administrativeactop_modification', FinalForm)], + url_name='operation_administrativeactop_modification',) + +operation_administrativeactop_deletion_wizard = AdministrativeActDeletionWizard([ + ('selec-operation_administrativeactop_deletion', + AdministrativeActOpeFormSelection), + ('final-operation_administrativeactop_deletion', + FinalAdministrativeActDeleteForm)], + url_name='operation_administrativeactop_deletion',) diff --git a/archaeological_operations/ishtar_menu.py b/archaeological_operations/ishtar_menu.py index dfd45a167..faf749480 100644 --- a/archaeological_operations/ishtar_menu.py +++ b/archaeological_operations/ishtar_menu.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010-2012 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2012 É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 @@ -20,53 +20,69 @@ from django.utils.translation import ugettext_lazy as _ from ishtar_common.menu_base import SectionItem, MenuItem -from ishtar_common.models import AdministrativeAct import models -ORDER = 30 - MENU_SECTIONS = [ - SectionItem('operation_management', _(u"Operation"), + (30, SectionItem('operation_management', _(u"Operation"), + childs=[ + MenuItem('operation_search', _(u"Search"), + model=models.Operation, + access_controls=['view_operation', + 'view_own_operation']), + MenuItem('operation_creation', _(u"Creation"), + model=models.Operation, + access_controls=['add_operation', + 'add_own_operation']), + MenuItem('operation_modification', _(u"Modification"), + model=models.Operation, + access_controls=['change_operation', + 'change_own_operation']), + MenuItem('operation_closing', _(u"Closing"), + model=models.Operation, + access_controls=['change_operation', + 'change_own_operation']), + MenuItem('operation_deletion', _(u"Deletion"), + model=models.Operation, + access_controls=['change_operation', + 'change_own_operation']), + SectionItem('admin_act_operations', + _(u"Administrative act"), childs=[ - MenuItem('operation_search', _(u"Search"), - model=models.Operation, - access_controls=['view_operation', - 'view_own_operation']), - MenuItem('operation_creation', _(u"Creation"), - model=models.Operation, - access_controls=['add_operation', - 'add_own_operation']), - MenuItem('operation_modification', _(u"Modification"), - model=models.Operation, - access_controls=['change_operation', - 'change_own_operation']), - MenuItem('operation_closing', _(u"Closing"), - model=models.Operation, - access_controls=['change_operation', - 'change_own_operation']), - MenuItem('operation_deletion', _(u"Deletion"), - model=models.Operation, - access_controls=['change_operation', - 'change_own_operation']), - SectionItem('admin_act_operations', - _(u"Administrative act"), + MenuItem('operation_administrativeactop', + _(u"Add"), + model=models.Operation, + access_controls=['change_operation', + 'change_own_operation']), + MenuItem('operation_administrativeactop_modification', + _(u"Modification"), + model=models.AdministrativeAct, + access_controls=['change_operation', + 'change_own_operation']), + MenuItem('operation_administrativeactop_deletion', + _(u"Deletion"), + model=models.AdministrativeAct, + access_controls=['operation_deletion', + 'delete_own_operation']), + ],), + SectionItem('operation_source', _(u"Documentation"), childs=[ - MenuItem('operation_administrativeactop', + MenuItem('operation_source_creation', _(u"Add"), - model=models.Operation, - access_controls=['change_operation', - 'change_own_operation']), - MenuItem('operation_administrativeactop_modification', + model=models.OperationSource, + access_controls=['change_operation', + 'change_own_operation']), + MenuItem('operation_source_modification', _(u"Modification"), - model=AdministrativeAct, - access_controls=['change_operation', - 'change_own_operation']), - MenuItem('operation_administrativeactop_deletion', + model=models.OperationSource, + access_controls=['change_operation', + 'change_own_operation']), + MenuItem('operation_source_deletion', _(u"Deletion"), - model=AdministrativeAct, - access_controls=['operation_deletion', - 'delete_own_operation']), - ],), - ]), + model=models.OperationSource, + access_controls=['change_operation', + 'change_own_operation']), + ]) + ]), + ) ] diff --git a/archaeological_operations/migrations/0001_initial.py b/archaeological_operations/migrations/0001_initial.py index e472e5e4d..2039268aa 100644 --- a/archaeological_operations/migrations/0001_initial.py +++ b/archaeological_operations/migrations/0001_initial.py @@ -200,6 +200,32 @@ class Migration(SchemaMigration): )) db.send_create_signal('archaeological_operations', ['AdministrativeAct']) + # Adding model 'Parcel' + db.create_table('archaeological_operations_parcel', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('history_modifier', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['auth.User'])), + ('history_date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + ('associated_file', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='parcels', null=True, to=orm['archaeological_files.File'])), + ('operation', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='parcels', null=True, to=orm['archaeological_operations.Operation'])), + ('year', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('town', self.gf('django.db.models.fields.related.ForeignKey')(related_name='parcels', to=orm['ishtar_common.Town'])), + ('section', self.gf('django.db.models.fields.CharField')(max_length=4)), + ('parcel_number', self.gf('django.db.models.fields.CharField')(max_length=6)), + )) + db.send_create_signal('archaeological_operations', ['Parcel']) + + # Adding model 'ParcelOwner' + db.create_table('archaeological_operations_parcelowner', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('history_modifier', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['auth.User'])), + ('history_date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ishtar_common.Person'])), + ('parcel', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archaeological_operations.Parcel'])), + ('start_date', self.gf('django.db.models.fields.DateField')()), + ('end_date', self.gf('django.db.models.fields.DateField')()), + )) + db.send_create_signal('archaeological_operations', ['ParcelOwner']) + def backwards(self, orm): # Deleting model 'OperationType' @@ -241,6 +267,12 @@ class Migration(SchemaMigration): # Deleting model 'AdministrativeAct' db.delete_table('archaeological_operations_administrativeact') + # Deleting model 'Parcel' + db.delete_table('archaeological_operations_parcel') + + # Deleting model 'ParcelOwner' + db.delete_table('archaeological_operations_parcelowner') + models = { 'archaeological_files.file': { @@ -427,6 +459,28 @@ class Migration(SchemaMigration): 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, + 'archaeological_operations.parcel': { + 'Meta': {'object_name': 'Parcel'}, + 'associated_file': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parcels'", 'null': 'True', 'to': "orm['archaeological_files.File']"}), + 'history_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'operation': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parcels'", 'null': 'True', 'to': "orm['archaeological_operations.Operation']"}), + 'parcel_number': ('django.db.models.fields.CharField', [], {'max_length': '6'}), + 'section': ('django.db.models.fields.CharField', [], {'max_length': '4'}), + 'town': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parcels'", 'to': "orm['ishtar_common.Town']"}), + 'year': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'archaeological_operations.parcelowner': { + 'Meta': {'object_name': 'ParcelOwner'}, + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'history_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Person']"}), + 'parcel': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_operations.Parcel']"}), + 'start_date': ('django.db.models.fields.DateField', [], {}) + }, 'archaeological_operations.period': { 'Meta': {'object_name': 'Period'}, 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index 48baa57ba..9b3631114 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -23,7 +23,8 @@ from django.db.models.signals import post_save from django.utils.translation import ugettext_lazy as _, ugettext from ishtar_common.models import GeneralType, BaseHistorizedItem, \ - HistoricalRecords, OwnPerms, Department, Source, Person, Organization, Town + HistoricalRecords, LightHistorizedItem, OwnPerms, Department, Source,\ + Person, Organization, Town, Dashboard FILES_AVAILABLE = 'archaeological_files' in settings.INSTALLED_APPS if FILES_AVAILABLE: from archaeological_files.models import File @@ -288,13 +289,502 @@ related_name='+', verbose_name=_(u"Person in charge of the scientific part")) verbose_name = _(u"Administrative act") verbose_name_plural = _(u"Administrative acts") permissions = ( -("view_own_administrativeact", ugettext(u"Can view own Administrative act")), -("add_own_administrativeact", ugettext(u"Can add own Administrative act")), -("change_own_administrativeact", ugettext(u"Can change own Administrative act")), -("delete_own_administrativeact", ugettext(u"Can delete own Administrative act")), + ("view_own_administrativeact", + ugettext(u"Can view own Administrative act")), + ("add_own_administrativeact", + ugettext(u"Can add own Administrative act")), + ("change_own_administrativeact", + ugettext(u"Can change own Administrative act")), + ("delete_own_administrativeact", + ugettext(u"Can delete own Administrative act")), ) def __unicode__(self): return JOINT.join([unicode(item) for item in [self.operation, self.associated_file, self.act_object] if item]) + +class Parcel(LightHistorizedItem): + if FILES_AVAILABLE: + associated_file = models.ForeignKey(File, related_name='parcels', + blank=True, null=True, verbose_name=_(u"File")) + operation = models.ForeignKey(Operation, related_name='parcels', blank=True, + null=True, verbose_name=_(u"Operation")) + year = models.IntegerField(_(u"Year"), blank=True, null=True) + town = models.ForeignKey(Town, related_name='parcels', + verbose_name=_(u"Town")) + section = models.CharField(_(u"Section"), max_length=4) + parcel_number = models.CharField(_(u"Parcel number"), max_length=6) + + class Meta: + verbose_name = _(u"Parcel") + verbose_name_plural = _(u"Parcels") + + def short_label(self): + return JOINT.join([unicode(item) for item in [self.section, + self.parcel_number] if item]) + + def __unicode__(self): + return self.short_label() + + def long_label(self): + items = [unicode(self.operation or self.associated_file)] + items += [unicode(item) for item in [self.section, self.parcel_number] + if item] + return JOINT.join(items) + +class ParcelOwner(LightHistorizedItem): + owner = models.ForeignKey(Person, verbose_name=_(u"Owner")) + parcel = models.ForeignKey(Parcel, verbose_name=_(u"Parcel")) + start_date = models.DateField(_(u"Start date")) + end_date = models.DateField(_(u"End date")) + + class Meta: + verbose_name = _(u"Parcel owner") + verbose_name_plural = _(u"Parcel owners") + + def __unicode__(self): + return self.owner + JOINT + self.parcel + +class OperationDashboard: + def __init__(self): + main_dashboard = Dashboard(Operation) + + self.total_number = main_dashboard.total_number + + self.filters_keys = ['recorded', 'effective', 'active', 'field', + 'documented', 'closed', 'documented_closed'] + filters = { + 'recorded':{}, + 'effective':{'in_charge__isnull':False}, + 'active':{'in_charge__isnull':False, 'end_date__isnull':True}, + 'field':{'excavation_end_date__isnull':True}, + 'documented':{'source__isnull':False}, + 'documented_closed':{'source__isnull':False, + 'end_date__isnull':False}, + 'closed':{'end_date__isnull':False} + } + filters_label = { + 'recorded':_(u"Recorded"), + 'effective':_(u"Effective"), + 'active':_(u"Active"), + 'field':_(u"Field completed"), + 'documented':_(u"Associated report"), + 'closed':_(u"Closed"), + 'documented_closed':_(u"Documented and closed"), + } + self.filters_label = [filters_label[k] for k in self.filters_keys] + self.total = [] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + nb = Operation.objects.filter(**fltr).count() + self.total.append((lbl, nb)) + + self.surface_by_type = Operation.objects\ + .values('operation_type__label')\ + .annotate(number=Sum('surface'))\ + .order_by('-number','operation_type__label') + + self.by_type = [] + self.types = OperationType.objects.filter(available=True).all() + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + type_res = Operation.objects.filter(**fltr).\ + values('operation_type', 'operation_type__label').\ + annotate(number=Count('pk')).\ + order_by('operation_type') + types_dct = {} + for typ in type_res.all(): + types_dct[typ['operation_type']] = typ["number"] + types = [] + for typ in self.types: + if typ.pk in types_dct: + types.append(types_dct[typ.pk]) + else: + types.append(0) + self.by_type.append((lbl, types)) + + self.by_year = [] + self.years = [res['year'] for res in Operation.objects.values('year')\ + .order_by('-year').distinct()] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + year_res = Operation.objects.filter(**fltr).\ + values('year').\ + annotate(number=Count('pk')).\ + order_by('year') + years_dct = {} + for yr in year_res.all(): + years_dct[yr['year']] = yr["number"] + years = [] + for yr in self.years: + if yr in years_dct: + years.append(years_dct[yr]) + else: + years.append(0) + self.by_year.append((lbl, years)) + + self.by_realisation_year = [] + self.realisation_years = [res['date'] for res in \ + Operation.objects.extra( + {'date':"date_trunc('year', start_date)"}).values('date')\ + .filter(start_date__isnull=False).order_by('-date').distinct()] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + year_res = Operation.objects.filter(**fltr).extra( + {'date':"date_trunc('year', start_date)"}).values('date').\ + values('date').filter(start_date__isnull=False).\ + annotate(number=Count('pk')).\ + order_by('-date') + years_dct = {} + for yr in year_res.all(): + years_dct[yr['date']] = yr["number"] + years = [] + for yr in self.realisation_years: + if yr in years_dct: + years.append(years_dct[yr]) + else: + years.append(0) + self.by_realisation_year.append((lbl, years)) + + self.effective = [] + for typ in self.types: + year_res = Operation.objects.filter(**{'in_charge__isnull':False, + 'operation_type':typ}).\ + values('year').\ + annotate(number=Count('pk')).\ + order_by('-year').distinct() + years_dct = {} + for yr in year_res.all(): + years_dct[yr['year']] = yr["number"] + years = [] + for yr in self.years: + if yr in years_dct: + years.append(years_dct[yr]) + else: + years.append(0) + self.effective.append((typ, years)) + + # TODO: by date + now = datetime.date.today() + limit = datetime.date(now.year, now.month, 1) - datetime.timedelta(365) + by_realisation_month = Operation.objects.filter(start_date__gt=limit, + start_date__isnull=False).extra( + {'date':"date_trunc('month', start_date)"}) + self.last_months = [] + date = datetime.datetime(now.year, now.month, 1) + for mt_idx in xrange(12): + self.last_months.append(date) + if date.month > 1: + date = datetime.datetime(date.year, date.month - 1, 1) + else: + date = datetime.datetime(date.year - 1, 12, 1) + self.by_realisation_month = [] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + month_res = by_realisation_month.filter(**fltr).\ + annotate(number=Count('pk')).\ + order_by('-date') + month_dct = {} + for mt in month_res.all(): + month_dct[mt.date] = mt.number + date = datetime.date(now.year, now.month, 1) + months = [] + for date in self.last_months: + if date in month_dct: + months.append(month_dct[date]) + else: + months.append(0) + self.by_realisation_month.append((lbl, months)) + + # survey and excavations + self.survey, self.excavation = {}, {} + for dct_res, ope_types in ((self.survey, ('arch_diagnostic',)), + (self.excavation, ('prev_excavation', + 'prog_excavation'))): + dct_res['total'] = [] + operation_type = {'operation_type__txt_idx__in':ope_types} + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + fltr.update(operation_type) + nb = Operation.objects.filter(**fltr).count() + dct_res['total'].append((lbl, nb)) + + dct_res['by_year'] = [] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + fltr.update(operation_type) + year_res = Operation.objects.filter(**fltr).\ + values('year').\ + annotate(number=Count('pk')).\ + order_by('year') + years_dct = {} + for yr in year_res.all(): + years_dct[yr['year']] = yr["number"] + years = [] + for yr in self.years: + if yr in years_dct: + years.append(years_dct[yr]) + else: + years.append(0) + dct_res['by_year'].append((lbl, years)) + + dct_res['by_realisation_year'] = [] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + fltr.update(operation_type) + year_res = Operation.objects.filter(**fltr).extra( + {'date':"date_trunc('year', start_date)"}).values('date').\ + filter(start_date__isnull=False).\ + annotate(number=Count('pk')).\ + order_by('-date') + years_dct = {} + for yr in year_res.all(): + years_dct[yr['date']] = yr["number"] + years = [] + for yr in self.realisation_years: + if yr in years_dct: + years.append(years_dct[yr]) + else: + years.append(0) + dct_res['by_realisation_year'].append((lbl, years)) + + current_year_ope = Operation.objects.filter(**operation_type)\ + .filter(year=datetime.date.today().year) + current_realisation_year_ope = Operation.objects\ + .filter(**operation_type)\ + .filter(start_date__year=datetime.date.today().year) + res_keys = [('area_realised', current_realisation_year_ope)] + if dct_res == self.survey: + res_keys.append(('area', + current_year_ope)) + for res_key, base_ope in res_keys: + dct_res[res_key] = [] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + area_res = base_ope.filter(**fltr)\ + .annotate(number=Sum('surface')).all() + val = 0 + if area_res: + val = area_res[0].number + dct_res[res_key].append(val) + # TODO... + res_keys = [('manday_realised', current_realisation_year_ope)] + if dct_res == self.survey: + res_keys.append(('manday', + current_year_ope)) + for res_key, base_ope in res_keys: + dct_res[res_key] = [] + for fltr_key in self.filters_keys: + dct_res[res_key].append('-') + # TODO... + res_keys = [('mandayhect_realised', current_realisation_year_ope)] + if dct_res == self.survey: + res_keys.append(('mandayhect', + current_year_ope)) + for res_key, base_ope in res_keys: + dct_res[res_key] = [] + for fltr_key in self.filters_keys: + dct_res[res_key].append('-') + # TODO... + dct_res['mandayhect_real_effective'] = '-' + if dct_res == self.survey: + dct_res['mandayhect_effective'] = '-' + + + res_keys = [('org_realised', current_realisation_year_ope)] + if dct_res == self.survey: + res_keys.append(('org', current_year_ope)) + for res_key, base_ope in res_keys: + org_res = base_ope.filter(in_charge__attached_to__isnull=False)\ + .values('in_charge__attached_to', + 'in_charge__attached_to__name')\ + .annotate(area=Sum('surface'))\ + .order_by('in_charge__attached_to__name').all() + # TODO: man-days, man-days/hectare + dct_res[res_key] = org_res + + + year_ope = Operation.objects.filter(**operation_type) + res_keys = ['org_by_year'] + if dct_res == self.survey: + res_keys.append('org_by_year_realised') + q = year_ope.values('in_charge__attached_to', + 'in_charge__attached_to__name').\ + filter(in_charge__attached_to__isnull=False).\ + order_by('in_charge__attached_to__name').distinct() + org_list = [(org['in_charge__attached_to'], + org['in_charge__attached_to__name']) for org in q] + org_list_dct = dict(org_list) + for res_key in res_keys: + dct_res[res_key] = [] + years = self.years + if res_key == 'org_by_year_realised': + years = self.realisation_years + for org_id, org_label in org_list: + org_res = year_ope.filter(in_charge__attached_to__pk=org_id) + key_date = '' + if res_key == 'org_by_year': + org_res = org_res.values('year') + key_date = 'year' + else: + org_res = org_res.extra( + {'date':"date_trunc('year', start_date)"}).values('date').\ + filter(start_date__isnull=False) + key_date = 'date' + org_res = org_res.annotate(area=Sum('surface'), + cost=Sum('cost')) + years_dct = {} + for yr in org_res.all(): + area = yr['area'] if yr['area'] else 0 + cost = yr['cost'] if yr['cost'] else 0 + years_dct[yr[key_date]] = (area, cost) + r_years = [] + for yr in years: + if yr in years_dct: + r_years.append(years_dct[yr]) + else: + r_years.append((0, 0)) + dct_res[res_key].append((org_label, r_years)) + area_means, area_sums = [], [] + cost_means, cost_sums = [], [] + for idx, year in enumerate(years): + vals = [r_years[idx] for lbl, r_years in dct_res[res_key]] + sum_area = sum([a for a, c in vals]) + sum_cost = sum([c for a, c in vals]) + area_means.append(sum_area/len(vals)) + area_sums.append(sum_area) + cost_means.append(sum_cost/len(vals)) + cost_sums.append(sum_cost) + dct_res[res_key+'_area_mean'] = area_means + dct_res[res_key+'_area_sum'] = area_sums + dct_res[res_key+'_cost_mean'] = cost_means + dct_res[res_key+'_cost_mean'] = cost_sums + + if dct_res == self.survey: + self.survey['effective'] = [] + for yr in self.years: + year_res = Operation.objects.filter(in_charge__isnull=False, + year=yr).\ + annotate(number=Sum('surface'), + mean=Avg('surface')) + nb = year_res[0].number if year_res.count() else 0 + nb = nb if nb else 0 + mean = year_res[0].mean if year_res.count() else 0 + mean = mean if mean else 0 + self.survey['effective'].append((nb, mean)) + + # TODO:Man-Days/hectare by Year + + # CHECK: month of realisation or month? + dct_res['by_month'] = [] + for fltr_key in self.filters_keys: + fltr, lbl = filters[fltr_key], filters_label[fltr_key] + fltr.update(operation_type) + month_res = by_realisation_month.filter(**fltr).\ + annotate(number=Count('pk')).\ + order_by('-date') + month_dct = {} + for mt in month_res.all(): + month_dct[mt.date] = mt.number + date = datetime.date(now.year, now.month, 1) + months = [] + for date in self.last_months: + if date in month_dct: + months.append(month_dct[date]) + else: + months.append(0) + dct_res['by_month'].append((lbl, months)) + + operation_type = {'operation_type__txt_idx__in':ope_types} + self.departments = [(fd['department__pk'], fd['department__label']) + for fd in OperationByDepartment.objects\ + .filter(department__isnull=False)\ + .values('department__label', 'department__pk')\ + .order_by('department__label').distinct()] + dct_res['by_dpt'] = [] + for dpt_id, dpt_label in self.departments: + vals = OperationByDepartment.objects\ + .filter(department__pk=dpt_id, + operation__operation_type__txt_idx__in=ope_types)\ + .values('department__pk', 'operation__year')\ + .annotate(number=Count('operation'))\ + .order_by('operation__year') + dct_years = {} + for v in vals: + dct_years[v['operation__year']] = v['number'] + years = [] + for y in self.years: + if y in dct_years: + years.append(dct_years[y]) + else: + years.append(0) + years.append(sum(years)) + dct_res['by_dpt'].append((dpt_label, years)) + dct_res['effective_by_dpt'] = [] + for dpt_id, dpt_label in self.departments: + vals = OperationByDepartment.objects\ + .filter(department__pk=dpt_id, + operation__in_charge__isnull=False, + operation__operation_type__txt_idx__in=ope_types)\ + .values('department__pk', 'operation__year')\ + .annotate(number=Count('operation'), + area=Sum('operation__surface'), + fnap=Sum('operation__fnap_cost'), + cost=Sum('operation__cost'))\ + .order_by('operation__year') + dct_years = {} + for v in vals: + values = [] + for value in (v['number'], v['area'], v['cost'], v['fnap']): + values.append(value if value else 0) + dct_years[v['operation__year']] = values + years = [] + for y in self.years: + if y in dct_years: + years.append(dct_years[y]) + else: + years.append((0, 0, 0, 0)) + nbs, areas, costs, fnaps = zip(*years) + years.append((sum(nbs), sum(areas), sum(costs), sum(fnaps))) + dct_res['effective_by_dpt'].append((dpt_label, years)) + + OperationTown = Operation.towns.through + query = OperationTown.objects\ + .filter(operation__in_charge__isnull=False, + operation__operation_type__txt_idx__in=ope_types)\ + .values('town__name', 'town__departement__number')\ + .annotate(nb=Count('operation'))\ + .order_by('-nb', 'town__name')[:10] + dct_res['towns'] = [] + for r in query: + dct_res['towns'].append((u"%s (%s)" % (r['town__name'], + r['town__departement__number']), + r['nb'])) + + if dct_res == self.survey: + query = OperationTown.objects\ + .filter(operation__in_charge__isnull=False, + operation__operation_type__txt_idx__in=ope_types, + operation__surface__isnull=False)\ + .values('town__name', 'town__departement__number')\ + .annotate(nb=Sum('operation__surface'))\ + .order_by('-nb', 'town__name')[:10] + dct_res['towns_surface'] = [] + for r in query: + dct_res['towns_surface'].append((u"%s (%s)" % ( + r['town__name'], r['town__departement__number']), + r['nb'])) + else: + query = OperationTown.objects\ + .filter(operation__in_charge__isnull=False, + operation__operation_type__txt_idx__in=ope_types, + operation__cost__isnull=False)\ + .values('town__name', 'town__departement__number')\ + .annotate(nb=Sum('operation__cost'))\ + .order_by('-nb', 'town__name')[:10] + dct_res['towns_cost'] = [] + for r in query: + dct_res['towns_cost'].append((u"%s (%s)" % (r['town__name'], + r['town__departement__number']), + r['nb'])) diff --git a/archaeological_operations/urls.py b/archaeological_operations/urls.py new file mode 100644 index 000000000..a761b4ccc --- /dev/null +++ b/archaeological_operations/urls.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from django.conf.urls.defaults import * + +""" +import forms + +# forms +urlpatterns = patterns('', + url(r'operation_search/(?P<step>.+)$', + forms.operation_search_wizard, name='operation_search'), + url(r'operation_creation/(?P<step>.+)$', + forms.operation_creation_wizard, name='operation_creation'), + url(r'operation_modification/(?P<step>.+)$', + forms.operation_modification_wizard, + name='operation_modification'), + url(r'operation_closing/(?P<step>.+)$', + forms.operation_closing_wizard, name='operation_closing'), + url(r'operation_deletion/(?P<step>.+)$', + forms.operation_deletion_wizard, name='operation_deletion'), + url(r'operation_administrativeactop/(?P<step>.+)$', + forms.operation_administrativeactop_wizard, + name='operation_administrativeactop'), + url(r'operation_administrativeactop_modification/(?P<step>.+)$', + forms.operation_administrativeactop_modification_wizard, + name='operation_administrativeactop_modification'), + url(r'operation_administrativeactop_deletion/(?P<step>.+)$', + forms.operation_administrativeactop_deletion_wizard, + name='operation_administrativeactop_deletion'), + url(r'operation_source_creation/(?P<step>.+)$', + forms.operation_source_creation_wizard, + name='operation_source_creation'), + url(r'operation_source_modification/(?P<step>.+)$', + forms.operation_source_modification_wizard, + name='operation_source_modification'), + url(r'operation_source_deletion/(?P<step>.+)$', + forms.operation_source_deletion_wizard, + name='operation_source_deletion'), +) + +urlpatterns += patterns('archaeological_operations.views', + url(r'autocomplete-operation/$', 'autocomplete_operation', + name='autocomplete-operation'), + url(r'get-operation/(?P<type>.+)?$', 'get_operation', + name='get-operation'), + url(r'get-operation-full/(?P<type>.+)?$', 'get_operation', + name='get-operation-full', kwargs={'full':True}), + url(r'get-available-operation-code/(?P<year>.+)?$', + 'get_available_operation_code', name='get_available_operation_code'), + url(r'revert-operation/(?P<pk>.+)/(?P<date>.+)$', + 'revert_operation', name='revert-operation'), + url(r'show-operation/(?P<pk>.+)?/(?P<type>.+)?$', + 'show_operation', name='show-operation'), + url(r'get-administrativeactop/(?P<type>.+)?$', + 'get_administrativeactop', name='get-administrativeactop'), + url(r'get-operationsource/(?P<type>.+)?$', + 'get_operationsource', name='get-operationsource'), +)""" diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py new file mode 100644 index 000000000..27ebd60e9 --- /dev/null +++ b/archaeological_operations/views.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +import json + +from django.db.models import Q +from django.http import HttpResponse +from django.shortcuts import render_to_response + +from ishtar_common.views import get_item, show_item, revert_item +import models + +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 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)) |