From dc7abf75836b59ad33d92da04fe727320400d512 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Sat, 20 Oct 2012 15:25:07 +0200 Subject: Djangoization - Major refactoring (step 3) Reorganization of views, urls, menus, admin, forms. Changes on models. --- archaeological_context_records/admin.py | 56 ++ archaeological_context_records/forms.py | 362 ++++++++++ archaeological_context_records/ishtar_menu.py | 67 ++ .../migrations/0001_initial.py | 70 +- archaeological_context_records/models.py | 49 +- archaeological_context_records/urls.py | 57 ++ archaeological_context_records/views.py | 36 + archaeological_files/admin.py | 42 ++ archaeological_files/forms.py | 389 ++++++++++ archaeological_files/ishtar_menu.py | 8 +- archaeological_files/models.py | 124 +++- archaeological_files/urls.py | 64 ++ archaeological_files/views.py | 66 ++ archaeological_finds/admin.py | 75 ++ archaeological_finds/forms.py | 525 ++++++++++++++ archaeological_finds/ishtar_menu.py | 67 ++ archaeological_finds/migrations/0001_initial.py | 188 ++--- archaeological_finds/models.py | 134 ++-- archaeological_finds/urls.py | 52 ++ archaeological_finds/views.py | 43 ++ archaeological_operations/admin.py | 72 ++ archaeological_operations/forms.py | 804 +++++++++++++++++++++ archaeological_operations/ishtar_menu.py | 98 +-- .../migrations/0001_initial.py | 54 ++ archaeological_operations/models.py | 500 ++++++++++++- archaeological_operations/urls.py | 75 ++ archaeological_operations/views.py | 97 +++ archaeological_warehouse/admin.py | 51 ++ archaeological_warehouse/ishtar_menu.py | 40 + archaeological_warehouse/models.py | 1 - archaeological_warehouse/urls.py | 45 ++ archaeological_warehouse/views.py | 81 +++ example_project/urls.py | 21 +- ishtar_common/admin.py | 158 +--- ishtar_common/context_processors.py | 19 +- ishtar_common/forms_common.py | 52 -- ishtar_common/forms_context_records.py | 362 ---------- ishtar_common/forms_files.py | 387 ---------- ishtar_common/forms_items.py | 525 -------------- ishtar_common/forms_operations.py | 754 ------------------- ishtar_common/menus.py | 132 +--- ishtar_common/models.py | 568 +-------------- ishtar_common/templates/sheet_contextrecord.html | 10 +- ishtar_common/templates/sheet_operation.html | 10 +- ishtar_common/urls.py | 157 +--- ishtar_common/views.py | 199 +---- 46 files changed, 4143 insertions(+), 3603 deletions(-) create mode 100644 archaeological_context_records/admin.py create mode 100644 archaeological_context_records/forms.py create mode 100644 archaeological_context_records/ishtar_menu.py create mode 100644 archaeological_context_records/urls.py create mode 100644 archaeological_context_records/views.py create mode 100644 archaeological_files/admin.py create mode 100644 archaeological_files/forms.py create mode 100644 archaeological_files/urls.py create mode 100644 archaeological_files/views.py create mode 100644 archaeological_finds/admin.py create mode 100644 archaeological_finds/forms.py create mode 100644 archaeological_finds/ishtar_menu.py create mode 100644 archaeological_finds/urls.py create mode 100644 archaeological_finds/views.py create mode 100644 archaeological_operations/admin.py create mode 100644 archaeological_operations/forms.py create mode 100644 archaeological_operations/urls.py create mode 100644 archaeological_operations/views.py create mode 100644 archaeological_warehouse/admin.py create mode 100644 archaeological_warehouse/ishtar_menu.py create mode 100644 archaeological_warehouse/urls.py create mode 100644 archaeological_warehouse/views.py delete mode 100644 ishtar_common/forms_context_records.py delete mode 100644 ishtar_common/forms_files.py delete mode 100644 ishtar_common/forms_items.py delete mode 100644 ishtar_common/forms_operations.py diff --git a/archaeological_context_records/admin.py b/archaeological_context_records/admin.py new file mode 100644 index 000000000..5985f4462 --- /dev/null +++ b/archaeological_context_records/admin.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.conf import settings +from django.contrib import admin + +from ishtar_common.admin import HistorizedObjectAdmin + +import models + +class DatingAdmin(admin.ModelAdmin): + list_display = ('period', 'start_date', 'end_date', 'dating_type', + 'quality') + list_filter = ("period", 'dating_type', 'quality') + model = models.Dating + +admin.site.register(models.Dating, DatingAdmin) + +class ContextRecordAdmin(HistorizedObjectAdmin): + list_display = ('label', 'length', 'width', + 'thickness', 'depth') + list_filter = ('has_furniture',) + search_fields = ('parcel__operation__name', "datings__period__label") + model = models.ContextRecord + +admin.site.register(models.ContextRecord, ContextRecordAdmin) + +class ContextRecordSourceAdmin(admin.ModelAdmin): + list_display = ('context_record', 'title', 'source_type',) + list_filter = ('source_type',) + search_fields = ('title', ) + model = models.ContextRecordSource + +admin.site.register(models.ContextRecordSource, ContextRecordSourceAdmin) + +basic_models = [models.DatingType, models.DatingQuality, + models.Unit, models.ActivityType, models.IdentificationType] + +for model in basic_models: + admin.site.register(model) diff --git a/archaeological_context_records/forms.py b/archaeological_context_records/forms.py new file mode 100644 index 000000000..816782bd8 --- /dev/null +++ b/archaeological_context_records/forms.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2011 É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. + +""" +Context records forms definitions +""" +import datetime +from itertools import groupby + +from django import forms +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 _ + +from ishtar import settings + +import models +import widgets +from forms import Wizard, FinalForm, FormSet, SearchWizard, DeletionWizard, \ + formset_factory, get_now, reverse_lazy, get_form_selection +from forms_common import get_town_field, SourceForm, SourceWizard, \ + SourceSelect, AuthorFormset +from forms_operations import OperationSelect + +class RecordWizard(Wizard): + model = models.ContextRecord + edit = False + + def get_current_operation(self, request, storage): + step = storage.get_current_step() + if not step: + return + if step.endswith('_creation'): # an operation has been selected + main_form_key = 'selec-' + self.url_name + try: + idx = int(self.session_get_value(request, storage, + main_form_key, 'operation_id')) + current_ope = models.Operation.objects.get(pk=idx) + return current_ope + except(TypeError, ValueError, ObjectDoesNotExist): + pass + current_cr = self.get_current_object(request, storage) + if current_cr: + return current_cr.parcel.operation + + def get_template_context(self, request, storage, form=None): + """ + Get the operation "reminder" on top of wizard forms + """ + context = super(RecordWizard, self).get_template_context(request, + storage, form) + operation = self.get_current_operation(request, storage) + if not operation: + return context + items = [] + if hasattr(operation, 'code_patriarche') and operation.code_patriarche: + items.append(unicode(operation.code_patriarche)) + items.append("-".join((unicode(operation.year), + unicode(operation.operation_code)))) + context['reminder'] = _("Current operation: ") + " - ".join(items) + return context + + def get_form(self, request, storage, step=None, data=None, files=None): + """ + Get associated operation + """ + 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 + if step.startswith('general-'): + if step.endswith('_creation'): # an operation has been selected + main_form_key = 'selec-' + self.url_name + try: + idx = int(self.session_get_value(request, storage, + main_form_key, 'operation_id')) + current_obj = models.Operation.objects.get(pk=idx) + data['operation'] = current_obj + except(TypeError, ValueError, ObjectDoesNotExist): + pass + else: + current_object = self.get_current_object(request, storage) + data['context_record'] = current_object + form = super(RecordWizard, self).get_form(request, storage, step, data, + files) + return form + +class RecordModifWizard(RecordWizard): + modification = True + model = models.ContextRecord + +class RecordSelect(forms.Form): + parcel__town = get_town_field() + operation__year = forms.IntegerField(label=_(u"Year")) + datings__period = forms.ChoiceField(label=_(u"Period"), choices=[]) + unit = forms.ChoiceField(label=_(u"Unit type"), choices=[]) + def __init__(self, *args, **kwargs): + super(RecordSelect, self).__init__(*args, **kwargs) + self.fields['datings__period'].choices = \ + models.Period.get_types() + self.fields['datings__period'].help_text = \ + models.Period.get_help() + self.fields['unit'].choices = models.Unit.get_types() + self.fields['unit'].help_text = models.Unit.get_help() + +class RecordFormSelection(forms.Form): + form_label = _("Context record search") + associated_models = {'pk':models.ContextRecord} + currents = {'pk':models.ContextRecord} + pk = forms.IntegerField(label="", required=False, + widget=widgets.JQueryJqGrid(reverse_lazy('get-contextrecord'), + RecordSelect(), models.ContextRecord, + source_full=reverse_lazy('get-contextrecord-full')), + validators=[models.valid_id(models.ContextRecord)]) + + 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 at least select one " + u"context record.")) + return cleaned_data + + +class RecordFormGeneral(forms.Form): + form_label = _("General") + associated_models = {'parcel':models.Parcel, 'unit':models.Unit} + pk = forms.IntegerField(required=False, widget=forms.HiddenInput) + operation_id = forms.IntegerField(widget=forms.HiddenInput) + parcel = forms.ChoiceField(label=_("Parcel"), choices=[]) + label = forms.CharField(label=_(u"ID"), + validators=[validators.MaxLengthValidator(200)]) + description = forms.CharField(label=_(u"Description"), + widget=forms.Textarea, required=False) + length = forms.IntegerField(label=_(u"Length (cm)"), required=False) + width = forms.IntegerField(label=_(u"Width (cm)"), required=False) + thickness = forms.IntegerField(label=_(u"Thickness (cm)"), required=False) + depth = forms.IntegerField(label=_(u"Depth (cm)"), required=False) + unit = forms.ChoiceField(label=_("Unit"), required=False, + choices=models.Unit.get_types()) + location = forms.CharField(label=_(u"Location"), widget=forms.Textarea, + required=False, validators=[validators.MaxLengthValidator(200)]) + + def __init__(self, *args, **kwargs): + operation = None + if 'data' in kwargs and kwargs['data'] and \ + ('operation' in kwargs['data'] or 'context_record' in kwargs['data']): + if 'operation' in kwargs['data']: + operation = kwargs['data']['operation'] + if 'context_record' in kwargs['data'] and \ + kwargs['data']['context_record']: + operation = kwargs['data']['context_record'].operation + # clean data if not "real" data + prefix_value = kwargs['prefix'] + if not [k for k in kwargs['data'].keys() + if k.startswith(kwargs['prefix']) and kwargs['data'][k]]: + kwargs['data'] = None + if 'files' in kwargs: + kwargs.pop('files') + super(RecordFormGeneral, self).__init__(*args, **kwargs) + self.fields['parcel'].choices = [('', '--')] + if operation: + self.fields['operation_id'].initial = operation.pk + parcels = operation.parcels.all() + sort = lambda x: (x.town.name, x.section) + parcels = sorted(parcels, key=sort) + for key, gparcels in groupby(parcels, sort): + self.fields['parcel'].choices.append( + (" - ".join(key), [(parcel.pk, parcel.short_label()) for parcel in gparcels]) + ) + + def clean(self): + # manage unique context record ID + cleaned_data = self.cleaned_data + operation_id = cleaned_data.get("operation_id") + label = cleaned_data.get("label") + cr = models.ContextRecord.objects.filter(label=label, + parcel__operation__pk=operation_id) + if 'pk' in cleaned_data and cleaned_data['pk']: + cr = cr.exclude(pk=cleaned_data['pk']) + if cr.count(): + raise forms.ValidationError(_(u"This ID already exist for " + u"this operation.")) + return cleaned_data + +class DatingForm(forms.Form): + form_label = _("Dating") + base_model = 'dating' + associated_models = {'dating_type':models.DatingType, + 'quality':models.DatingQuality, + 'period':models.Period} + period = forms.ChoiceField(label=_("Period"), + choices=models.Period.get_types()) + start_date = forms.IntegerField(label=_(u"Start date"), required=False) + end_date = forms.IntegerField(label=_(u"End date"), required=False) + quality = forms.ChoiceField(label=_("Quality"), required=False, + choices=models.DatingQuality.get_types()) + dating_type = forms.ChoiceField(label=_("Dating type"), required=False, + choices=[]) + + def __init__(self, *args, **kwargs): + super(DatingForm, self).__init__(*args, **kwargs) + self.fields['dating_type'].choices = models.DatingType.get_types() + self.fields['dating_type'].help_text = models.DatingType.get_help() + + +DatingFormSet = formset_factory(DatingForm, can_delete=True, + formset=FormSet) +DatingFormSet.form_label = _("Dating") + +class RecordFormInterpretation(forms.Form): + form_label = _("Interpretation") + associated_models = {'activity':models.ActivityType, + 'identification':models.IdentificationType,} + has_furniture = forms.NullBooleanField(label=_(u"Has furniture?"), + required=False) + filling = forms.CharField(label=_(u"Filling"), + widget=forms.Textarea, required=False) + interpretation = forms.CharField(label=_(u"Interpretation"), + widget=forms.Textarea, required=False) + activity = forms.ChoiceField(label=_(u"Activity"), required=False, + choices=[]) + identification = forms.ChoiceField(label=_("Identification"), + required=False, choices=[]) + taq = forms.IntegerField(label=_(u"TAQ"), required=False) + taq_estimated = forms.IntegerField(label=_(u"Estimated TAQ"), + required=False) + tpq = forms.IntegerField(label=_(u"TPQ"), required=False) + tpq_estimated = forms.IntegerField(label=_(u"Estimated TPQ"), + required=False) + + def __init__(self, *args, **kwargs): + super(RecordFormInterpretation, self).__init__(*args, **kwargs) + self.fields['activity'].choices = models.ActivityType.get_types() + self.fields['activity'].help_text = models.ActivityType.get_help() + self.fields['identification'].choices = \ + models.IdentificationType.get_types() + self.fields['identification'].help_text = \ + models.IdentificationType.get_help() + +record_search_wizard = SearchWizard([ + ('general-record_search', RecordFormSelection)], + url_name='record_search',) + +OperationRecordFormSelection = get_form_selection( + 'OperationRecordFormSelection', _(u"Operation search"), 'operation_id', + models.Operation, OperationSelect, 'get-operation', + _(u"You should select an operation.")) + + +record_creation_wizard = RecordWizard([ + ('selec-record_creation', OperationRecordFormSelection), + ('general-record_creation', RecordFormGeneral), + ('datings-record_creation', DatingFormSet), + ('interpretation-record_creation', RecordFormInterpretation), + ('final-record_creation', FinalForm)], + url_name='record_creation',) + +record_modification_wizard = RecordModifWizard([ + ('selec-record_modification', RecordFormSelection), + ('general-record_modification', RecordFormGeneral), + ('datings-record_modification', DatingFormSet), + ('interpretation-record_modification', RecordFormInterpretation), + ('final-record_modification', FinalForm)], + url_name='record_modification',) + +class RecordDeletionWizard(DeletionWizard): + model = models.ContextRecord + fields = ['label', 'parcel', 'description', 'length', 'width', 'thickness', + 'depth', 'location', 'datings', 'units', 'has_furniture', + 'filling', 'interpretation', 'taq', 'taq_estimated', 'tpq', + 'tpq_estimated'] + +class RecordDeletionForm(FinalForm): + confirm_msg = " " + confirm_end_msg = _(u"Would you like to delete this context record?") + +record_deletion_wizard = RecordDeletionWizard([ + ('selec-record_deletion', RecordFormSelection), + ('final-record_deletion', RecordDeletionForm)], + url_name='record_deletion',) + +######################################### +# Source management for context records # +######################################### + +class RecordSourceWizard(SourceWizard): + model = models.ContextRecordSource + +SourceRecordFormSelection = get_form_selection( + 'SourceRecordFormSelection', _(u"Context record search"), + 'context_record', models.ContextRecord, RecordSelect, 'get-contextrecord', + _(u"You should select a context record.")) + +record_source_creation_wizard = RecordSourceWizard([ + ('selec-record_source_creation', SourceRecordFormSelection), + ('source-record_source_creation', SourceForm), + ('authors-record_source_creation', AuthorFormset), + ('final-record_source_creation', FinalForm)], + url_name='record_source_creation',) + +class RecordSourceSelect(SourceSelect): + context_record__parcel__town = get_town_field( + label=_(u"Town of the operation")) + context_record__operation__year = forms.IntegerField( + label=_(u"Year of the operation")) + context_record__datings__period = forms.ChoiceField( + label=_(u"Period of the context record"), choices=[]) + context_record__unit = forms.ChoiceField( + label=_(u"Unit type of the context record"), choices=[]) + + def __init__(self, *args, **kwargs): + super(RecordSourceSelect, self).__init__(*args, **kwargs) + self.fields['context_record__datings__period'].choices = \ + models.Period.get_types() + self.fields['context_record__datings__period'].help_text = \ + models.Period.get_help() + self.fields['context_record__unit'].choices = models.Unit.get_types() + self.fields['context_record__unit'].help_text = models.Unit.get_help() + + +RecordSourceFormSelection = get_form_selection( + 'RecordSourceFormSelection', _(u"Documentation search"), 'pk', + models.ContextRecordSource, RecordSourceSelect, 'get-contextrecordsource', + _(u"You should select a document.")) + +record_source_modification_wizard = RecordSourceWizard([ + ('selec-record_source_modification', RecordSourceFormSelection), + ('source-record_source_modification', SourceForm), + ('authors-record_source_modification', AuthorFormset), + ('final-record_source_modification', FinalForm)], + url_name='record_source_modification',) + +class RecordSourceDeletionWizard(DeletionWizard): + model = models.ContextRecordSource + fields = ['context_record', 'title', 'source_type', 'authors',] + +record_source_deletion_wizard = RecordSourceDeletionWizard([ + ('selec-record_source_deletion', RecordSourceFormSelection), + ('final-record_source_deletion', RecordDeletionForm)], + url_name='record_source_deletion',) diff --git a/archaeological_context_records/ishtar_menu.py b/archaeological_context_records/ishtar_menu.py new file mode 100644 index 000000000..c471a75a8 --- /dev/null +++ b/archaeological_context_records/ishtar_menu.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from django.utils.translation import ugettext_lazy as _ + +from archaeological_operations.models import Operation +from ishtar_common.menu_base import SectionItem, MenuItem + +import models + +MENU_SECTIONS = [ + (40, SectionItem('record_management', _(u"Context record"), + childs=[ + MenuItem('record_search', _(u"Search"), + model=models.ContextRecord, + access_controls=['view_contextrecord', + 'view_own_contextrecord']), + MenuItem('record_creation', _(u"Creation"), + model=models.ContextRecord, + access_controls=['add_contextrecord', + 'add_own_contextrecord']), + MenuItem('record_modification', _(u"Modification"), + model=models.ContextRecord, + access_controls=['change_contextrecord', + 'change_own_contextrecord']), + MenuItem('record_deletion', _(u"Deletion"), + model=models.ContextRecord, + access_controls=['delete_contextrecord', + 'delete_own_contextrecord']), + SectionItem('record_source', _(u"Documentation"), + childs=[ + MenuItem('record_source_creation', + _(u"Add"), + model=models.ContextRecordSource, + access_controls=['change_contextrecord', + 'change_own_contextrecord']), + MenuItem('record_source_modification', + _(u"Modification"), + model=models.ContextRecordSource, + access_controls=['change_contextrecord', + 'change_own_contextrecord']), + MenuItem('record_source_deletion', + _(u"Deletion"), + model=models.ContextRecordSource, + access_controls=['change_contextrecord', + 'change_own_contextrecord']), + ]) + ]) + ) +] + diff --git a/archaeological_context_records/migrations/0001_initial.py b/archaeological_context_records/migrations/0001_initial.py index 09ee30efe..599a6d4f7 100644 --- a/archaeological_context_records/migrations/0001_initial.py +++ b/archaeological_context_records/migrations/0001_initial.py @@ -8,32 +8,6 @@ from django.db import models class Migration(SchemaMigration): def forwards(self, orm): - # Adding model 'Parcel' - db.create_table('archaeological_context_records_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_context_records', ['Parcel']) - - # Adding model 'ParcelOwner' - db.create_table('archaeological_context_records_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_context_records.Parcel'])), - ('start_date', self.gf('django.db.models.fields.DateField')()), - ('end_date', self.gf('django.db.models.fields.DateField')()), - )) - db.send_create_signal('archaeological_context_records', ['ParcelOwner']) - # Adding model 'DatingType' db.create_table('archaeological_context_records_datingtype', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), @@ -133,7 +107,7 @@ class Migration(SchemaMigration): db.create_table('archaeological_context_records_contextrecord', ( ('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'])), - ('parcel', self.gf('django.db.models.fields.related.ForeignKey')(related_name='context_record', to=orm['archaeological_context_records.Parcel'])), + ('parcel', self.gf('django.db.models.fields.related.ForeignKey')(related_name='context_record', to=orm['archaeological_operations.Parcel'])), ('operation', self.gf('django.db.models.fields.related.ForeignKey')(related_name='context_record', to=orm['archaeological_operations.Operation'])), ('label', self.gf('django.db.models.fields.CharField')(max_length=200)), ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), @@ -185,12 +159,6 @@ class Migration(SchemaMigration): def backwards(self, orm): - # Deleting model 'Parcel' - db.delete_table('archaeological_context_records_parcel') - - # Deleting model 'ParcelOwner' - db.delete_table('archaeological_context_records_parcelowner') - # Deleting model 'DatingType' db.delete_table('archaeological_context_records_datingtype') @@ -251,7 +219,7 @@ class Migration(SchemaMigration): 'length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'location': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), 'operation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'context_record'", 'to': "orm['archaeological_operations.Operation']"}), - 'parcel': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'context_record'", 'to': "orm['archaeological_context_records.Parcel']"}), + 'parcel': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'context_record'", 'to': "orm['archaeological_operations.Parcel']"}), 'taq': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'taq_estimated': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'thickness': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), @@ -333,28 +301,6 @@ class Migration(SchemaMigration): 'order': ('django.db.models.fields.IntegerField', [], {}), 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, - 'archaeological_context_records.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_context_records.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_context_records.Parcel']"}), - 'start_date': ('django.db.models.fields.DateField', [], {}) - }, 'archaeological_context_records.unit': { 'Meta': {'object_name': 'Unit'}, 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -456,6 +402,18 @@ 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.period': { 'Meta': {'object_name': 'Period'}, 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index c47fd3354..75653e78e 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -22,53 +22,8 @@ from django.contrib.gis.db import models from django.utils.translation import ugettext_lazy as _, ugettext from ishtar_common.models import GeneralType, BaseHistorizedItem, \ - LightHistorizedItem, HistoricalRecords, OwnPerms, Town, Person, Source -FILES_AVAILABLE = 'archaeological_files' in settings.INSTALLED_APPS -if FILES_AVAILABLE: - from archaeological_files.models import File -from archaeological_operations.models import Operation, Period - -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 + HistoricalRecords, OwnPerms, Town, Person, Source +from archaeological_operations.models import Operation, Period, Parcel class DatingType(GeneralType): class Meta: diff --git a/archaeological_context_records/urls.py b/archaeological_context_records/urls.py new file mode 100644 index 000000000..c42ae2b02 --- /dev/null +++ b/archaeological_context_records/urls.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from django.conf.urls.defaults import * +""" +import forms + +# forms +urlpatterns = patterns('', + # Context records + url(r'record_search/(?P.+)$', + forms.record_search_wizard, name='record_search'), + url(r'record_creation/(?P.+)$', + forms.record_creation_wizard, name='record_creation'), + url(r'record_modification/(?P.+)$', + forms.record_modification_wizard, name='record_modification'), + url(r'record_deletion/(?P.+)$', + forms.record_deletion_wizard, name='record_deletion'), + url(r'record_source_creation/(?P.+)$', + forms.record_source_creation_wizard, + name='record_source_creation'), + url(r'record_source_modification/(?P.+)$', + forms.record_source_modification_wizard, + name='record_source_modification'), + url(r'record_source_deletion/(?P.+)$', + forms.record_source_deletion_wizard, + name='record_source_deletion'), +) + +urlpatterns += patterns('archaeological_context_records.views', + url(r'show-contextrecord/(?P.+)?/(?P.+)?$', + 'show_contextrecord', name='show-contextrecord'), + url(r'get-contextrecord/(?P.+)?$', 'get_contextrecord', + name='get-contextrecord'), + url(r'get-contextrecord-full/(?P.+)?$', + 'get_contextrecord', name='get-contextrecord-full', + kwargs={'full':True}), + url(r'get-contexrecordsource/(?P.+)?$', + 'get_contextrecordsource', name='get-contextrecordsource'), +) +""" diff --git a/archaeological_context_records/views.py b/archaeological_context_records/views.py new file mode 100644 index 000000000..89a45482b --- /dev/null +++ b/archaeological_context_records/views.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from ishtar_common.views import get_item, show_item, revert_item +import models + +show_contextrecord = show_item(models.ContextRecord, 'contextrecord') +get_contextrecord = get_item(models.ContextRecord, + 'get_contextrecord', 'contextrecord', + extra_request_keys={'parcel__town':'parcel__town__pk', + 'operation__year':'operation__year__contains', + 'datings__period':'datings__period__pk'},) +get_contextrecordsource = get_item(models.ContextRecordSource, + 'get_contextrecordsource', 'contextrecordsource', + extra_request_keys={ + 'context_record__parcel__town':'context_record__parcel__town__pk', + 'context_record__operation__year':'context_record__operation__year', + 'context_record__datings__period':'context_record__datings__period__pk', + 'context_record__unit':'context_record__unit__pk', + }) diff --git a/archaeological_files/admin.py b/archaeological_files/admin.py new file mode 100644 index 000000000..339b19661 --- /dev/null +++ b/archaeological_files/admin.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.conf import settings +from django.contrib import admin + +from ishtar_common.admin import HistorizedObjectAdmin + +import models + +class FileAdmin(HistorizedObjectAdmin): + list_display = ['year', 'numeric_reference', 'internal_reference', + 'end_date', 'file_type', 'general_contractor',] + if settings.COUNTRY == 'fr': + list_display += ['saisine_type', 'reference_number'] + list_filter = ("file_type", "year",) + search_fields = ('towns__name',) + model = models.File + +admin.site.register(models.File, FileAdmin) + +basic_models = [models.FileType, models.PermitType] +if settings.COUNTRY == 'fr': + basic_models.append(models.SaisineType) +for model in basic_models: + admin.site.register(model) diff --git a/archaeological_files/forms.py b/archaeological_files/forms.py new file mode 100644 index 000000000..368c57843 --- /dev/null +++ b/archaeological_files/forms.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +""" +Files forms definitions +""" +import datetime + +from django import forms +from django.conf import settings +from django.core import validators +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Max +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils.translation import ugettext_lazy as _ + +from ishtar_common.models import Town +import models +from ishtar_common.views import Wizard +from ishtar_common.forms import FinalForm, FormSet, ClosingWizard, \ + ClosingDateFormSelection, SearchWizard, formset_factory, get_now, \ + reverse_lazy +from ishtar_common.forms_common import TownFormset, ParcelFormSet, \ + get_town_field, get_person_field +from archaeological_operations.forms import OperationAdministrativeActWizard, \ + AdministrativeActOpeForm, AdministrativeActOpeFormSelection, \ + AdministrativeActDeletionWizard, FinalAdministrativeActDeleteForm, \ + is_preventive +from ishtar_common import widgets + +class FileWizard(Wizard): + model = models.File + object_parcel_type = 'associated_file' + + def get_form(self, request, storage, step=None, data=None, files=None): + """ + Manage towns + """ + if data: + data = data.copy() + else: + data = {} + # manage the dynamic choice of towns + if not step: + step = self.determine_step(request, storage) + form = self.get_form_list(request, storage)[step] + town_form_key = 'towns-' + self.url_name + if step.startswith('parcels-') and hasattr(form, 'management_form') \ + and self.session_has_key(request, storage, town_form_key): + towns = [] + qdict = request.session[storage.prefix]['step_data'][town_form_key] + for k in qdict.keys(): + if k.endswith("town") and qdict[k]: + try: + town = Town.objects.get(pk=int(qdict[k])) + towns.append((town.pk, unicode(town))) + except (ObjectDoesNotExist, ValueError): + pass + data['TOWNS'] = sorted(towns, key=lambda x:x[1]) + form = super(FileWizard, self).get_form(request, storage, step, data, + files) + return form + + def get_extra_model(self, dct, request, storage, form_list): + dct = super(FileWizard, self).get_extra_model(dct, request, storage, + form_list) + if not dct['numeric_reference']: + current_ref = models.File.objects.filter(year=dct['year'] + ).aggregate(Max('numeric_reference'))["numeric_reference__max"] + dct['numeric_reference'] = current_ref and current_ref + 1 or 1 + return dct + + def done(self, request, storage, form_list, **kwargs): + ''' + Save parcels + ''' + r = super(FileWizard, self).done(request, storage, form_list, + return_object=True, **kwargs) + if type(r) not in (list, tuple) or len(r) != 2: + return r + obj, res = r + obj.parcels.clear() + for form in form_list: + if not hasattr(form, 'prefix') \ + or not form.prefix.startswith('parcels-') \ + or not hasattr(form, 'forms'): + continue + for frm in form.forms: + if not frm.is_valid(): + continue + dct = frm.cleaned_data.copy() + if 'parcel' in dct: + try: + parcel = models.Parcel.objects.get(pk=dct['parcel']) + setattr(parcel, self.object_parcel_type, obj) + parcel.save() + except (ValueError, ObjectDoesNotExist): + continue + continue + try: + dct['town'] = models.Town.objects.get(pk=int(dct['town'])) + except (ValueError, ObjectDoesNotExist): + continue + dct['associated_file'], dct['operation'] = None, None + dct[self.object_parcel_type] = obj + if 'DELETE' in dct: + dct.pop('DELETE') + parcel = models.Parcel.objects.filter(**dct).count() + if not parcel: + dct['history_modifier'] = request.user + parcel = models.Parcel(**dct) + parcel.save() + return res + +class FileSelect(forms.Form): + towns = get_town_field() + in_charge = get_person_field(label=_(u"Person in charge"), + person_type='sra_agent') + file_type = forms.ChoiceField(label=_("File type"), + choices=models.FileType.get_types()) + saisine_type = forms.ChoiceField(label=_("Saisine type"), choices=[]) + year = forms.IntegerField(label=_("Year")) + + def __init__(self, *args, **kwargs): + super(FileSelect, self).__init__(*args, **kwargs) + self.fields['saisine_type'].choices = models.SaisineType.get_types() + self.fields['saisine_type'].help_text = models.SaisineType.get_help() + +class FileFormSelection(forms.Form): + form_label = _("Archaeological file search") + associated_models = {'pk':models.File} + currents = {'pk':models.File} + pk = forms.IntegerField(label="", required=False, + widget=widgets.JQueryJqGrid(reverse_lazy('get-file'), + FileSelect(), models.File, source_full=reverse_lazy('get-file-full')), + validators=[models.valid_id(models.File)]) + + 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 a file.")) + return cleaned_data + +class FileFormGeneral(forms.Form): + form_label = _("General") + associated_models = {'in_charge':models.Person, + 'related_file':models.File, + 'file_type':models.FileType} + in_charge = forms.IntegerField(label=_("Person in charge"), + widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person', + args=[models.PersonType.objects.get(txt_idx='sra_agent').pk]), + associated_model=models.Person, new=True), + validators=[models.valid_id(models.Person)]) + year = forms.IntegerField(label=_("Year"), + initial=lambda:datetime.datetime.now().year, + validators=[validators.MinValueValidator(1900), + validators.MaxValueValidator(2100)]) + numeric_reference = forms.IntegerField(label=_("Numeric reference"), + widget=forms.HiddenInput, required=False) + internal_reference = forms.CharField(label=_(u"Internal reference"), + max_length=60, + validators=[models.is_unique(models.File, 'internal_reference')]) + creation_date = forms.DateField(label=_(u"Creation date"), + initial=get_now, widget=widgets.JQueryDate) + file_type = forms.ChoiceField(label=_("File type"), + choices=models.FileType.get_types()) + related_file = forms.IntegerField(label=_("Related file"), required=False, + widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-file'), + associated_model=models.File), + validators=[models.valid_id(models.File)]) + comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea, + required=False) + +class FileFormGeneralRO(FileFormGeneral): + year = forms.IntegerField(label=_(u"Year"), + widget=forms.TextInput(attrs={'readonly':True})) + numeric_reference = forms.IntegerField(label=_(u"Numeric reference"), + widget=forms.TextInput(attrs={'readonly':True})) + internal_reference = forms.CharField(label=_(u"Internal reference"), + widget=forms.TextInput(attrs={'readonly':True},)) + +class FileFormAddress(forms.Form): + form_label = _(u"Address") + associated_models = {'town':models.Town} + total_surface = forms.IntegerField(required=False, + widget=widgets.AreaWidget, + label=_(u"Total surface (m²)"), + validators=[validators.MinValueValidator(0), + validators.MaxValueValidator(999999999)]) + address = forms.CharField(label=_(u"Main address"), widget=forms.Textarea) + address_complement = forms.CharField(label=_(u"Main address - complement"), + required=False) + postal_code = forms.CharField(label=_(u"Main address - postal code"), + max_length=10) + +class FileFormPreventive(forms.Form): + form_label = _(u"Preventive informations") + associated_models = {'general_contractor':models.Person, + 'saisine_type':models.SaisineType, + 'permit_type':models.PermitType, + 'town_planning_service':models.Organization} + general_contractor = forms.IntegerField(label=_(u"General contractor"), + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-person', + args=[models.PersonType.objects.get(txt_idx='general_contractor').pk]), + associated_model=models.Person, new=True), + validators=[models.valid_id(models.Person)]) + town_planning_service = forms.IntegerField(required=False, + label=_(u"Town planning service"), + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-organization', + args=[models.OrganizationType.objects.get(txt_idx='planning_service').pk]), + associated_model=models.Organization, new=True), + validators=[models.valid_id(models.Organization)]) + permit_type = forms.ChoiceField(label=_(u"Permit type"), required=False, + choices=models.PermitType.get_types()) + permit_reference = forms.CharField(label=_(u"Permit reference"), + required=False, validators=[validators.MaxLengthValidator(60)]) + total_developed_surface = forms.IntegerField(widget=widgets.AreaWidget, + label=_(u"Total developed surface (m²)"), + required=False, validators=[validators.MinValueValidator(0), + validators.MaxValueValidator(999999999)]) + if settings.COUNTRY == 'fr': + saisine_type = forms.ChoiceField(label=_(u"Saisine type"), + choices=[]) + reception_date = forms.DateField(label=_(u"Reception date"), + initial=get_now, widget=widgets.JQueryDate) + def __init__(self, *args, **kwargs): + super(FileFormPreventive, self).__init__(*args, **kwargs) + self.fields['saisine_type'].choices = models.SaisineType.get_types() + self.fields['saisine_type'].help_text = models.SaisineType.get_help() + +file_search_wizard = SearchWizard([('general-file_search', FileFormSelection)], + url_name='file_search',) + +file_creation_wizard = FileWizard([ + ('general-file_creation', FileFormGeneral), + ('address-file_creation', FileFormAddress), + ('towns-file_creation', TownFormset), + ('parcels-file_creation', ParcelFormSet), + ('preventive-file_creation', FileFormPreventive), + ('final-file_creation', FinalForm)], + condition_list={ +'preventive-file_creation':is_preventive('general-file_creation', + models.FileType, type_key='file_type') + }, + url_name='file_creation',) + +class FileModificationWizard(FileWizard): + modification = True + +file_modification_wizard = FileModificationWizard([ + ('selec-file_modification', FileFormSelection), + ('general-file_modification', FileFormGeneralRO), + ('adress-file_modification', FileFormAddress), + ('towns-file_modification', TownFormset), + ('parcels-file_modification', ParcelFormSet), + ('preventive-file_modification', FileFormPreventive), + ('final-file_modification', FinalForm)], + condition_list={ +'preventive-file_modification':is_preventive('general-file_modification', + models.FileType, type_key='file_type') + }, + url_name='file_modification',) + +class FileClosingWizard(ClosingWizard): + model = models.File + fields = ['year', 'numeric_reference', 'internal_reference', + 'file_type', 'in_charge', 'general_contractor', 'creation_date', + 'reception_date', 'total_surface', 'total_developed_surface', + 'address', 'address_complement', 'postal_code', 'comment'] + if settings.COUNTRY == 'fr': + fields += ['saisine_type', 'reference_number'] + fields += ['towns'] + +class FinalFileClosingForm(FinalForm): + confirm_msg = " " + confirm_end_msg = _(u"Would you like to close this archaeological file?") + +file_closing_wizard = FileClosingWizard([ + ('selec-file_closing', FileFormSelection), + ('date-file_closing', ClosingDateFormSelection), + ('final-file_closing', FinalFileClosingForm)], + url_name='file_closing',) + +class FileDeletionWizard(FileClosingWizard): + def get_formated_datas(self, forms): + datas = super(FileDeletionWizard, self).get_formated_datas(forms) + datas.append((_("Associated operations"), [])) + for operation in models.Operation.objects.filter( + associated_file=self.current_obj).all(): + if operation.end_date: + datas[-1][1].append(('', unicode(operation))) + return datas + + def done(self, request, storage, form_list, **kwargs): + obj = self.get_current_object(request, storage) + for operation in models.Operation.objects.filter( + associated_file=obj).all(): + operation.delete() + obj.delete() + return render_to_response('wizard_done.html', {}, + context_instance=RequestContext(request)) + + +class FinalFileDeleteForm(FinalForm): + confirm_msg = " " + confirm_end_msg = _(u"Would you like to delete this archaelogical file ?") + +file_deletion_wizard = FileDeletionWizard([ + ('selec-file_deletion', FileFormSelection), + ('final-file_deletion', FinalFileDeleteForm)], + url_name='file_deletion',) + +class FileAdministrativeActWizard(OperationAdministrativeActWizard): + model = models.File + +class FileEditAdministrativeActWizard(FileAdministrativeActWizard): + model = models.AdministrativeAct + edit = True + def get_associated_item(self, request, storage, dct): + return self.get_current_object(request, storage).associated_file + +class AdministrativeActFileSelect(forms.Form): + associated_file__towns = get_town_field() + act_type = forms.ChoiceField(label=_("Act type"), choices=[]) + + def __init__(self, *args, **kwargs): + super(AdministrativeActFileSelect, self).__init__(*args, **kwargs) + self.fields['act_type'].choices = models.ActType.get_types( + dct={'intented_to':'F'}) + self.fields['act_type'].help_text = models.ActType.get_help( + dct={'intented_to':'F'}) + +class AdministrativeActFileFormSelection(AdministrativeActOpeFormSelection): + pk = forms.IntegerField(label="", required=False, + widget=widgets.JQueryJqGrid(reverse_lazy('get-administrativeactfile'), + AdministrativeActFileSelect(), models.AdministrativeAct, + table_cols='TABLE_COLS_FILE'), + validators=[models.valid_id(models.AdministrativeAct)]) + +class AdministrativeActFileForm(AdministrativeActOpeForm): + act_type = forms.ChoiceField(label=_(u"Act type"), choices=[]) + + def __init__(self, *args, **kwargs): + super(AdministrativeActFileForm, self).__init__(*args, **kwargs) + self.fields['act_type'].choices = models.ActType.get_types( + dct={'intented_to':'F'}) + self.fields['act_type'].help_text = models.ActType.get_help( + dct={'intented_to':'F'}) + +file_administrativeactfile_wizard = FileAdministrativeActWizard([ + ('selec-file_administrativeactfile', FileFormSelection), + ('administrativeact-file_administrativeactfile', AdministrativeActFileForm), + ('final-file_administrativeactfile', FinalForm)], + url_name='file_administrativeactfile',) + +file_administrativeactfile_modification_wizard = FileEditAdministrativeActWizard([ + ('selec-file_administrativeactfile_modification', + AdministrativeActFileFormSelection), + ('administrativeact-file_administrativeactfile_modification', + AdministrativeActFileForm), + ('final-file_administrativeactfile_modification', FinalForm)], + url_name='file_administrativeactfile_modification',) + +file_administrativeactfile_deletion_wizard = AdministrativeActDeletionWizard([ + ('selec-file_administrativeactfile_deletion', + AdministrativeActFileFormSelection), + ('final-file_administrativeactfile_deletion', + FinalAdministrativeActDeleteForm)], + url_name='file_administrativeactfile_deletion',) + diff --git a/archaeological_files/ishtar_menu.py b/archaeological_files/ishtar_menu.py index 370320f69..398b43f4b 100644 --- a/archaeological_files/ishtar_menu.py +++ b/archaeological_files/ishtar_menu.py @@ -21,14 +21,13 @@ from django.utils.translation import ugettext_lazy as _ from archaeological_operations.models import Operation from ishtar_common.menu_base import SectionItem, MenuItem -from ishtar_common.models import AdministrativeAct -import models +from archaeological_operations.models import AdministrativeAct -ORDER = 20 +import models MENU_SECTIONS = [ - SectionItem('file_management', _(u"Archaeological file"), + (20, SectionItem('file_management', _(u"Archaeological file"), childs=[ MenuItem('file_search', _(u"Search"), model=models.File, @@ -62,4 +61,5 @@ MENU_SECTIONS = [ access_controls=['delete_file', 'delete_own_file']), ],), ]), + ) ] diff --git a/archaeological_files/models.py b/archaeological_files/models.py index 68a65f6de..90f60fe64 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -24,7 +24,8 @@ from django.contrib.gis.db import models from django.utils.translation import ugettext_lazy as _, ugettext from ishtar_common.models import GeneralType, BaseHistorizedItem, \ - HistoricalRecords, OwnPerms, Person, Organization, Department, Town + HistoricalRecords, OwnPerms, Person, Organization, Department, Town, \ + Dashboard class FileType(GeneralType): class Meta: @@ -183,3 +184,124 @@ class FileByDepartment(models.Model): class Meta: managed = False db_table = 'file_department' + +class FileDashboard: + def __init__(self): + main_dashboard = Dashboard(File) + + self.total_number = main_dashboard.total_number + + types = File.objects.values('file_type', 'file_type__label') + self.types = types.annotate(number=Count('pk')).order_by('file_type') + + by_year = File.objects.extra( + {'date':"date_trunc('year', creation_date)"}) + self.by_year = by_year.values('date')\ + .annotate(number=Count('pk')).order_by('-date') + + now = datetime.date.today() + limit = datetime.date(now.year, now.month, 1) - datetime.timedelta(365) + by_month = File.objects.filter(creation_date__gt=limit).extra( + {'date':"date_trunc('month', creation_date)"}) + self.by_month = by_month.values('date')\ + .annotate(number=Count('pk')).order_by('-date') + + # research + self.research = {} + prog_type = FileType.objects.get(txt_idx='prog') + researchs = File.objects.filter(file_type=prog_type) + self.research['total_number'] = researchs.count() + by_year = researchs.extra({'date':"date_trunc('year', creation_date)"}) + self.research['by_year'] = by_year.values('date')\ + .annotate(number=Count('pk'))\ + .order_by('-date') + by_month = researchs.filter(creation_date__gt=limit)\ + .extra({'date':"date_trunc('month', creation_date)"}) + self.research['by_month'] = by_month.values('date')\ + .annotate(number=Count('pk'))\ + .order_by('-date') + + self.research['by_dpt'] = FileByDepartment.objects\ + .filter(file__file_type=prog_type, + department__isnull=False)\ + .values('department__label')\ + .annotate(number=Count('file'))\ + .order_by('department__label') + FileTown = File.towns.through + self.research['towns'] = FileTown.objects\ + .filter(file__file_type=prog_type)\ + .values('town__name')\ + .annotate(number=Count('file'))\ + .order_by('-number','town__name')[:10] + + # rescue + rescue_type = FileType.objects.get(txt_idx='preventive') + rescues = File.objects.filter(file_type=rescue_type) + self.rescue = {} + self.rescue['total_number'] = rescues.count() + self.rescue['saisine'] = rescues.values('saisine_type__label')\ + .annotate(number=Count('pk'))\ + .order_by('saisine_type__label') + self.rescue['administrative_act'] = AdministrativeAct.objects\ + .filter(associated_file__isnull=False)\ + .values('act_type__label')\ + .annotate(number=Count('pk'))\ + .order_by('act_type__pk') + + by_year = rescues.extra({'date':"date_trunc('year', creation_date)"}) + self.rescue['by_year'] = by_year.values('date')\ + .annotate(number=Count('pk'))\ + .order_by('-date') + by_month = rescues.filter(creation_date__gt=limit)\ + .extra({'date':"date_trunc('month', creation_date)"}) + self.rescue['by_month'] = by_month.values('date')\ + .annotate(number=Count('pk'))\ + .order_by('-date') + + self.rescue['by_dpt'] = FileByDepartment.objects\ + .filter(file__file_type=rescue_type, + department__isnull=False)\ + .values('department__label')\ + .annotate(number=Count('file'))\ + .order_by('department__label') + self.rescue['towns'] = FileTown.objects\ + .filter(file__file_type=rescue_type)\ + .values('town__name')\ + .annotate(number=Count('file'))\ + .order_by('-number','town__name')[:10] + + self.rescue['with_associated_operation'] = rescues\ + .filter(operations__isnull=False).count() + + self.rescue['with_associated_operation_percent'] = round( + float(self.rescue['with_associated_operation'])\ + /self.rescue['total_number']*100, 2) + + by_year_operationnal = rescues.filter(operations__isnull=False)\ + .extra({'date':"date_trunc('year', creation_date)"}) + by_year_operationnal = by_year_operationnal.values('date')\ + .annotate(number=Count('pk'))\ + .order_by('-date') + percents, idx = [], 0 + for dct in self.rescue['by_year']: + if idx > len(by_year_operationnal): + break + if by_year_operationnal[idx]['date'] != dct['date'] or\ + not dct['number']: + continue + val = round(float(by_year_operationnal[idx]['number'])/\ + dct['number']*100, 2) + percents.append({'date':dct['date'], 'number':val}) + self.rescue['operational_by_year'] = percents + + self.rescue['surface_by_town'] = FileTown.objects\ + .filter(file__file_type=rescue_type)\ + .values('town__name')\ + .annotate(number=Sum('file__total_surface'))\ + .order_by('-number','town__name')[:10] + self.rescue['surface_by_dpt'] = FileByDepartment.objects\ + .filter(file__file_type=rescue_type, + department__isnull=False)\ + .values('department__label')\ + .annotate(number=Sum('file__total_surface'))\ + .order_by('department__label') diff --git a/archaeological_files/urls.py b/archaeological_files/urls.py new file mode 100644 index 000000000..e32ab8294 --- /dev/null +++ b/archaeological_files/urls.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from django.conf.urls.defaults import * + +""" +import forms + +# forms +urlpatterns = patterns('', + url(r'file_search/(?P.+)$', + forms.file_search_wizard, name='file_search'), + url(r'file_creation/(?P.+)$', + forms.file_creation_wizard, name='file_creation'), + url(r'file_modification/(?P.+)$', + forms.file_modification_wizard, name='file_modification'), + url(r'file_closing/(?P.+)$', + forms.file_closing_wizard, name='file_closing'), + url(r'file_deletion/(?P.+)$', + forms.file_deletion_wizard, name='file_deletion'), + url(r'file_administrativeactfile/(?P.+)$', + forms.file_administrativeactfile_wizard, + name='file_administrativeactfile'), + url(r'file_administrativeactfile_modification/(?P.+)$', + forms.file_administrativeactfile_modification_wizard, + name='file_administrativeactfile_modification'), + url(r'file_administrativeactfile_deletion/(?P.+)$', + forms.file_administrativeactfile_deletion_wizard, + name='file_administrativeactfile_deletion'), +) + +urlpatterns += patterns('archaeological_files.views', + url(r'autocomplete-file/$', 'autocomplete_file', + name='autocomplete-file'), + url(r'get-file/(?P.+)?$', 'get_file', + name='get-file'), + url(r'get-file-full/(?P.+)?$', 'get_file', + name='get-file-full', kwargs={'full':True}), + url(r'get-administrativeactfile/(?P.+)?$', + 'get_administrativeactfile', name='get-administrativeactfile'), + url(r'show-file/(?P.+)?/(?P.+)?$', 'show_file', + name='show-file'), + url(r'show-historized-file/(?P.+)?/(?P.+)?$', + 'show_file', name='show-historized-file'), + url(r'revert-file/(?P.+)/(?P.+)$', + 'revert_file', name='revert-file'), +) +""" diff --git a/archaeological_files/views.py b/archaeological_files/views.py new file mode 100644 index 000000000..02332b629 --- /dev/null +++ b/archaeological_files/views.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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 + +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_file(request): + person_types = request.user.ishtaruser.person.person_type + if (not request.user.has_perm('ishtar_common.view_file', models.File) and \ + not request.user.has_perm('ishtar_common.view_own_file', models.File) + and not person_types.rights.filter(wizard__url_name='file_search' + ).count()): + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(internal_reference__icontains=q) | \ + Q(towns__name__icontains=q) + try: + value = int(q) + extra = extra | Q(year=q) | Q(numeric_reference=q) + except ValueError: + pass + query = query & extra + limit = 20 + files = models.File.objects.filter(query)[:limit] + data = json.dumps([{'id':file.pk, 'value':unicode(file)} + for file in files]) + return HttpResponse(data, mimetype='text/plain') + +get_file = get_item(models.File, 'get_file', 'file') +show_file = show_item(models.File, 'file') +revert_file = revert_item(models.File) + +def dashboard_file(request, dct, obj_id=None, *args, **kwargs): + """ + Main dashboard + """ + dct = {'dashboard': models.FileDashboard()} + return render_to_response('dashboard_file.html', dct, + context_instance=RequestContext(request)) + diff --git a/archaeological_finds/admin.py b/archaeological_finds/admin.py new file mode 100644 index 000000000..096f05bf3 --- /dev/null +++ b/archaeological_finds/admin.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.conf import settings +from django.contrib import admin + +from ishtar_common.admin import HistorizedObjectAdmin + +import models + +class BaseFindAdmin(HistorizedObjectAdmin): + list_display = ('label', 'context_record', 'is_isolated') + search_fields = ('label', 'context_record__parcel__operation__name',) + model = models.BaseFind + +admin.site.register(models.BaseFind, BaseFindAdmin) + +class FindAdmin(HistorizedObjectAdmin): + list_display = ('label', 'material_type', 'dating', 'volume', 'weight', + 'find_number',) + list_filter = ('material_type',) + search_fields = ('label', "dating__period__label") + model = models.Find + +admin.site.register(models.Find, FindAdmin) + +class FindSourceAdmin(admin.ModelAdmin): + list_display = ('find', 'title', 'source_type',) + list_filter = ('source_type',) + search_fields = ('title', ) + model = models.FindSource + +admin.site.register(models.FindSource, FindSourceAdmin) + +class PropertyAdmin(admin.ModelAdmin): + list_display = ['find', 'person', 'start_date', 'end_date'] + search_fields = ('find__label', 'person__name') + model = models.Property + +admin.site.register(models.Property, PropertyAdmin) + +class TreatmentAdmin(HistorizedObjectAdmin): + list_display = ('location', 'treatment_type', 'container', 'person') + list_filter = ('treatment_type',) + model = models.Treatment + +admin.site.register(models.Treatment, TreatmentAdmin) + +class TreatmentSourceAdmin(admin.ModelAdmin): + list_display = ('treatment', 'title', 'source_type',) + list_filter = ('source_type',) + search_fields = ('title',) + model = models.TreatmentSource + +admin.site.register(models.TreatmentSource, TreatmentSourceAdmin) + +basic_models = [models.MaterialType, models.TreatmentType] +for model in basic_models: + admin.site.register(model) diff --git a/archaeological_finds/forms.py b/archaeological_finds/forms.py new file mode 100644 index 000000000..7d64214f5 --- /dev/null +++ b/archaeological_finds/forms.py @@ -0,0 +1,525 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2011 É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. + +""" +Items forms definitions +""" +import datetime + +from django import forms +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.utils.safestring import mark_safe +from django.db.models import Max +from django.utils.translation import ugettext_lazy as _ + +from ishtar import settings + +import models +import widgets +from forms import Wizard, FinalForm, FormSet, SearchWizard, DeletionWizard,\ + FloatField, formset_factory, get_now, get_form_selection, reverse_lazy +from forms_common import get_town_field, get_warehouse_field, SourceForm, \ + SourceWizard, SourceSelect, SourceDeletionForm, AuthorFormset +from forms_context_records import RecordFormSelection + +class ItemWizard(Wizard): + model = models.Item + + def get_current_contextrecord(self, request, storage): + step = storage.get_current_step() + if not step: + return + if step.endswith('_creation'): # a context record has been selected + main_form_key = 'selecrecord-' + self.url_name + try: + idx = int(self.session_get_value(request, storage, + main_form_key, 'pk')) + current_cr = models.ContextRecord.objects.get(pk=idx) + return current_cr + except(TypeError, ValueError, ObjectDoesNotExist): + pass + current_item = self.get_current_object(request, storage) + if current_item: + base_finds = current_item.base_finds.all() + if base_finds: + return base_finds[0].context_record + + def get_template_context(self, request, storage, form=None): + """ + Get the operation and context record "reminder" on top of wizard forms + """ + context = super(ItemWizard, self).get_template_context(request, + storage, form) + current_cr = self.get_current_contextrecord(request, storage) + if not current_cr: + return context + operation = current_cr.operation + items = [] + if hasattr(operation, 'code_patriarche') and operation.code_patriarche: + items.append(unicode(operation.code_patriarche)) + items.append("-".join((unicode(operation.year), + unicode(operation.operation_code)))) + reminder = unicode(_("Current operation: ")) + u" - ".join(items) + reminder += u"
" + unicode(_("Current context record: "))\ + + unicode(current_cr.label) + context['reminder'] = mark_safe(reminder) + return context + + def get_extra_model(self, dct, request, storage, form_list): + dct = super(ItemWizard, self).get_extra_model(dct, request, storage, + form_list) + dct['order'] = 1 + if 'pk' in dct and type(dct['pk']) == models.ContextRecord: + dct['base_finds__context_record'] = dct.pop('pk') + return dct + +class ItemForm(forms.Form): + form_label = _("Item") + base_model = 'base_finds' + associated_models = {'material_type':models.MaterialType,} + label = forms.CharField(label=_(u"ID"), + validators=[validators.MaxLengthValidator(60)]) + description = forms.CharField(label=_("Description"), + widget=forms.Textarea) + base_finds__is_isolated = forms.NullBooleanField(label=_(u"Is isolated?"), + required=False) + material_type = forms.ChoiceField(label=_("Material type"), + choices=models.MaterialType.get_types()) + volume = FloatField(label=_(u"Volume (l)"), required=False) + weight = FloatField(label=_(u"Weight (g)"), required=False) + item_number = forms.IntegerField(label=_(u"Item number"), required=False) + +class DateForm(forms.Form): + form_label = _("Dating") + base_model = 'dating' + associated_models = {'dating__dating_type':models.DatingType, + 'dating__quality':models.DatingQuality, + 'dating__period':models.Period} + dating__period = forms.ChoiceField(label=_("Period"), + choices=models.Period.get_types()) + dating__start_date = forms.IntegerField(label=_(u"Start date"), + required=False) + dating__end_date = forms.IntegerField(label=_(u"End date"), required=False) + dating__quality = forms.ChoiceField(label=_("Quality"), required=False, + choices=models.DatingQuality.get_types()) + dating__dating_type = forms.ChoiceField(label=_("Dating type"), + required=False, choices=[]) + + def __init__(self, *args, **kwargs): + super(DateForm, self).__init__(*args, **kwargs) + self.fields['dating__dating_type'].choices = models.DatingType.get_types() + self.fields['dating__dating_type'].help_text = models.DatingType.get_help() + +item_creation_wizard = ItemWizard([ + ('selecrecord-item_creation', RecordFormSelection), + ('item-item_creation', ItemForm), + ('dating-item_creation', DateForm), + ('final-item_creation', FinalForm)], + url_name='item_creation',) + +class ItemSelect(forms.Form): + base_finds__context_record__parcel__town = get_town_field() + base_finds__context_record__operation__year = forms.IntegerField( + label=_(u"Year")) + base_finds__context_record__operation__code_patriarche = \ + forms.IntegerField(label=_(u"Code PATRIARCHE")) + dating__period = forms.ChoiceField(label=_(u"Period"), choices=[]) + # TODO search by warehouse + material_type = forms.ChoiceField(label=_(u"Material type"), choices=[]) + base_finds__item__description = forms.CharField(label=_(u"Description")) + base_finds__is_isolated = forms.NullBooleanField(label=_(u"Is isolated?")) + + def __init__(self, *args, **kwargs): + super(ItemSelect, self).__init__(*args, **kwargs) + self.fields['dating__period'].choices = \ + models.Period.get_types() + self.fields['dating__period'].help_text = \ + models.Period.get_help() + self.fields['material_type'].choices = \ + models.MaterialType.get_types() + self.fields['material_type'].help_text = \ + models.MaterialType.get_help() + +class ItemFormSelection(forms.Form): + form_label = _("Item search") + associated_models = {'pk':models.Item} + currents = {'pk':models.Item} + pk = forms.IntegerField(label="", required=False, + widget=widgets.JQueryJqGrid(reverse_lazy('get-item'), + ItemSelect(), models.Item, source_full=reverse_lazy('get-item-full')), + validators=[models.valid_id(models.Item)]) + +item_search_wizard = SearchWizard([ + ('general-item_search', ItemFormSelection)], + url_name='item_search',) + +class ItemModificationWizard(ItemWizard): + modification = True + +item_modification_wizard = ItemModificationWizard([ + ('selec-item_modification', ItemFormSelection), + ('item-item_modification', ItemForm), + ('dating-item_modification', DateForm), + ('final-item_modification', FinalForm)], + url_name='item_modification',) + +class TreatmentWizard(Wizard): + model = models.Treatment + +class BaseTreatmentForm(forms.Form): + form_label = _(u"Base treatment") + associated_models = {'treatment_type':models.TreatmentType, + 'person':models.Person, + 'location':models.Warehouse} + treatment_type = forms.ChoiceField(label=_(u"Treatment type"), choices=[]) + person = forms.IntegerField(label=_(u"Person"), + widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person'), + associated_model=models.Person, new=True), + validators=[models.valid_id(models.Person)]) + location = forms.IntegerField(label=_(u"Location"), + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-warehouse'), associated_model=models.Warehouse, + new=True), + validators=[models.valid_id(models.Warehouse)]) + description = forms.CharField(label=_(u"Description"), + widget=forms.Textarea, required=False) + start_date = forms.DateField(label=_(u"Start date"), required=False, + widget=widgets.JQueryDate) + end_date = forms.DateField(label=_(u"End date"), required=False, + widget=widgets.JQueryDate) + + def __init__(self, *args, **kwargs): + super(BaseTreatmentForm, self).__init__(*args, **kwargs) + self.fields['treatment_type'].choices = models.TreatmentType.get_types( + exclude=['packaging']) + self.fields['treatment_type'].help_text = models.TreatmentType.get_help( + exclude=['packaging']) + +class ItemMultipleFormSelection(forms.Form): + form_label = _(u"Upstream items") + associated_models = {'items':models.Item} + associated_labels = {'items':_(u"Items")} + items = forms.CharField(label="", required=False, + widget=widgets.JQueryJqGrid(reverse_lazy('get-item'), + ItemSelect(), models.Item, multiple=True, multiple_cols=[2, 3, 4]), + validators=[models.valid_ids(models.Item)]) + + def clean(self): + if not 'items' in self.cleaned_data or not self.cleaned_data['items']: + raise forms.ValidationError(_(u"You should at least select one " + u"archaeological item.")) + return self.cleaned_data + +class ContainerForm(forms.Form): + form_label = _(u"Container") + reference = forms.CharField(label=_(u"Reference")) + container_type = forms.ChoiceField(label=_(u"Container type"), choices=[]) + location = forms.IntegerField(label=_(u"Warehouse"), + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-warehouse'), associated_model=models.Warehouse, + new=True), + validators=[models.valid_id(models.Warehouse)]) + comment = forms.CharField(label=_(u"Comment"), + widget=forms.Textarea, required=False) + + def __init__(self, *args, **kwargs): + super(ContainerForm, self).__init__(*args, **kwargs) + self.fields['container_type'].choices = \ + models.ContainerType.get_types() + self.fields['container_type'].help_text = \ + models.ContainerType.get_help() + + def save(self, user): + dct = self.cleaned_data + dct['history_modifier'] = user + dct['container_type'] = models.ContainerType.objects.get( + pk=dct['container_type']) + dct['location'] = models.Warehouse.objects.get(pk=dct['location']) + new_item = models.Container(**dct) + new_item.save() + return new_item + +def check_treatment(form_name, type_key, type_list=[], not_type_list=[]): + type_list = [models.TreatmentType.objects.get(txt_idx=tpe).pk + for tpe in type_list] + not_type_list = [models.TreatmentType.objects.get(txt_idx=tpe).pk + for tpe in not_type_list] + 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: + type = int(request.session[storage.prefix]['step_data']\ + [form_name][form_name+'-'+type_key]) + return (not type_list or type in type_list) \ + and type not in not_type_list + except ValueError: + return False + return func + +class ResultItemForm(forms.Form): + form_label = _(u"Resulting item") + associated_models = {'material_type':models.MaterialType} + label = forms.CharField(label=_(u"ID"), + validators=[validators.MaxLengthValidator(60)]) + description = forms.CharField(label=_(u"Precise description"), + widget=forms.Textarea) + material_type = forms.ChoiceField(label=_(u"Material type"), + choices=models.MaterialType.get_types()) + volume = forms.IntegerField(label=_(u"Volume (l)")) + weight = forms.IntegerField(label=_(u"Weight (g)")) + item_number = forms.IntegerField(label=_(u"Item number")) + +ResultItemFormSet = formset_factory(ResultItemForm, can_delete=True, + formset=FormSet) +ResultItemFormSet.form_label = _(u"Resulting items") + +class UpstreamItemFormSelection(ItemFormSelection): + form_label = _(u"Upstream item") + +treatment_creation_wizard = TreatmentWizard([ + ('basetreatment-treatment_creation', BaseTreatmentForm), + ('selecitem-treatment_creation', UpstreamItemFormSelection), + ('multiselecitems-treatment_creation', ItemMultipleFormSelection), + ('container-treatment_creation', ContainerForm), + ('resultitem-treatment_creation', ResultItemForm), + ('resultitems-treatment_creation', ResultItemFormSet), + ('final-treatment_creation', FinalForm)], + condition_list={ +'selecitem-treatment_creation': + check_treatment('basetreatment-treatment_creation', 'treatment_type', + not_type_list=['physical_grouping', 'packaging']), +'multiselecitems-treatment_creation': + check_treatment('basetreatment-treatment_creation', 'treatment_type', + ['physical_grouping', 'packaging']), +'resultitems-treatment_creation': + check_treatment('basetreatment-treatment_creation', 'treatment_type', + ['split']), +'resultitem-treatment_creation': + check_treatment('basetreatment-treatment_creation', 'treatment_type', + not_type_list=['split']), +'container-treatment_creation': + check_treatment('basetreatment-treatment_creation', 'treatment_type', + ['packaging']), + }, + url_name='treatment_creation',) + +############# +# Packaging # +############# + +class PackagingWizard(TreatmentWizard): + def save_model(self, dct, m2m, whole_associated_models, request, storage, + form_list, return_object): + dct = self.get_extra_model(dct, request, storage, form_list) + obj = self.get_current_saved_object(request, storage) + dct['location'] = dct['container'].location + items = dct.pop('items') + treatment = models.Treatment(**dct) + treatment.save() + if not hasattr(items, '__iter__'): + items = [items] + for item in items: + new = item.duplicate(request.user) + item.downstream_treatment = treatment + item.save() + new.upstream_treatment = treatment + new.container = dct['container'] + new.save() + res = render_to_response('wizard_done.html', {}, + context_instance=RequestContext(request)) + return return_object and (obj, res) or res + +class ContainerSelect(forms.Form): + location = get_warehouse_field() + container_type = forms.ChoiceField(label=_(u"Container type"), choices=[]) + reference = forms.CharField(label=_(u"Reference")) + + def __init__(self, *args, **kwargs): + super(ContainerSelect, self).__init__(*args, **kwargs) + self.fields['container_type'].choices = \ + models.ContainerType.get_types() + self.fields['container_type'].help_text = \ + models.ContainerType.get_help() + +ContainerFormSelection = get_form_selection( + 'ContainerFormSelection', _(u"Container search"), 'container', + models.Container, ContainerSelect, 'get-container', + _(u"You should select a container."), new=True, + new_message=_(u"Add a new container")) + +class BasePackagingForm(forms.Form): + form_label = _(u"Packaging") + associated_models = {'treatment_type':models.TreatmentType, + 'person':models.Person, + 'location':models.Warehouse} + treatment_type = forms.IntegerField(label="", widget=forms.HiddenInput) + person = forms.IntegerField(label=_(u"Packager"), + widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person'), + associated_model=models.Person, new=True), + validators=[models.valid_id(models.Person)]) + start_date = forms.DateField(label=_(u"Date"), required=False, + widget=widgets.JQueryDate) + + def __init__(self, *args, **kwargs): + super(BasePackagingForm, self).__init__(*args, **kwargs) + self.fields['treatment_type'].initial = \ + models.TreatmentType.objects.get(txt_idx='packaging').pk + +class ItemPackagingFormSelection(ItemMultipleFormSelection): + form_label = _(u"Packaged items") + +warehouse_packaging_wizard = PackagingWizard([ + ('seleccontainer-packaging', ContainerFormSelection), + ('base-packaging', BasePackagingForm), + ('multiselecitems-packaging', ItemPackagingFormSelection), + ('final-packaging', FinalForm)], + url_name='warehouse_packaging',) + +""" +warehouse_packaging_wizard = ItemSourceWizard([ + ('selec-warehouse_packaging', ItemsSelection), + ('final-warehouse_packaging', FinalForm)], + url_name='warehouse_packaging',) +""" +############################################# +# Source management for archaelogical items # +############################################# + +class ItemSourceWizard(SourceWizard): + model = models.ItemSource + +SourceItemFormSelection = get_form_selection( + 'SourceItemFormSelection', _(u"Archaelogical item search"), 'item', + models.Item, ItemSelect, 'get-item', + _(u"You should select an archaelogical item.")) + +item_source_creation_wizard = ItemSourceWizard([ + ('selec-item_source_creation', SourceItemFormSelection), + ('source-item_source_creation', SourceForm), + ('authors-item_source_creation', AuthorFormset), + ('final-item_source_creation', FinalForm)], + url_name='item_source_creation',) + +class ItemSourceSelect(SourceSelect): + item__base_finds__context_record__operation__year = forms.IntegerField( + label=_(u"Year of the operation")) + item__dating__period = forms.ChoiceField( + label=_(u"Period of the archaelogical item"), + choices=[]) + item__material_type = forms.ChoiceField( + label=_("Material type of the archaelogical item"), + choices=models.MaterialType.get_types()) + item__description = forms.CharField( + label=_(u"Description of the archaelogical item")) + + def __init__(self, *args, **kwargs): + super(ItemSourceSelect, self).__init__(*args, **kwargs) + self.fields['item__dating__period'].choices = \ + models.Period.get_types() + self.fields['item__dating__period'].help_text = \ + models.Period.get_help() + self.fields['item__material_type'].choices = \ + models.MaterialType.get_types() + self.fields['item__material_type'].help_text = \ + models.MaterialType.get_help() + +ItemSourceFormSelection = get_form_selection( + 'ItemSourceFormSelection', _(u"Documentation search"), 'pk', + models.ItemSource, ItemSourceSelect, 'get-itemsource', + _(u"You should select a document.")) + +item_source_modification_wizard = ItemSourceWizard([ + ('selec-item_source_modification', ItemSourceFormSelection), + ('source-item_source_modification', SourceForm), + ('authors-item_source_modification', AuthorFormset), + ('final-item_source_modification', FinalForm)], + url_name='item_source_modification',) + +class ItemSourceDeletionWizard(DeletionWizard): + model = models.ItemSource + fields = ['item', 'title', 'source_type', 'authors',] + +item_source_deletion_wizard = ItemSourceDeletionWizard([ + ('selec-item_source_deletion', ItemSourceFormSelection), + ('final-item_source_deletion', SourceDeletionForm)], + url_name='item_source_deletion',) + +""" + +#################################### +# Source management for treatments # +#################################### + +class TreatmentSourceWizard(SourceWizard): + model = models.TreamentSource + +SourceTreatementFormSelection = get_form_selection( + 'SourceTreatmentFormSelection', _(u"Treatment search"), 'operation', + models.Treatment, TreatmentSelect, 'get-treatment', + _(u"You should select a treatment.")) + +treatment_source_creation_wizard = TreatmentSourceWizard([ + ('selec-treatment_source_creation', SourceTreatmentFormSelection), + ('source-treatment_source_creation', SourceForm), + ('authors-treatment_source_creation', AuthorFormset), + ('final-treatment_source_creation', FinalForm)], + url_name='treatment_source_creation',) + +class TreatmentSourceSelect(SourceSelect): + operation__towns = get_town_field(label=_(u"Operation's town")) + treatment__treatment_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', SourceForm), + ('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',) +""" diff --git a/archaeological_finds/ishtar_menu.py b/archaeological_finds/ishtar_menu.py new file mode 100644 index 000000000..55a498184 --- /dev/null +++ b/archaeological_finds/ishtar_menu.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.utils.translation import ugettext_lazy as _ + +from ishtar_common.menu_base import SectionItem, MenuItem + +import models + +MENU_SECTIONS = [ + (50, SectionItem('find_management', _(u"Find"), + childs=[ + MenuItem('find_search', _(u"Search"), + model=models.Find, + access_controls=['view_item', + 'view_own_item']), + MenuItem('find_creation', _(u"Creation"), + model=models.Find, + access_controls=['add_item', + 'add_own_item']), + MenuItem('find_modification', _(u"Modification"), + model=models.Find, + access_controls=['change_item', + 'change_own_item']), + MenuItem('warehouse_packaging', _(u"Packaging"), + model=models.Treatment, + access_controls=['add_treatment', 'add_own_treatment']), + #MenuItem('treatment_creation', _(u"Add a treatment"), + # model=models.Treatment, + # access_controls=['add_treatment', + # 'add_own_treatment']), + SectionItem('find_source', _(u"Documentation"), + childs=[ + MenuItem('find_source_creation', + _(u"Creation"), + model=models.FindSource, + access_controls=['change_item', + 'change_own_item']), + MenuItem('find_source_modification', + _(u"Modification"), + model=models.FindSource, + access_controls=['change_item', + 'change_own_item']), + MenuItem('find_source_deletion', + _(u"Deletion"), + model=models.FindSource, + access_controls=['change_item', + 'change_own_item']), + ]) + ])) +] diff --git a/archaeological_finds/migrations/0001_initial.py b/archaeological_finds/migrations/0001_initial.py index 03a45ed8d..eab75b03c 100644 --- a/archaeological_finds/migrations/0001_initial.py +++ b/archaeological_finds/migrations/0001_initial.py @@ -20,8 +20,8 @@ class Migration(SchemaMigration): )) db.send_create_signal('archaeological_finds', ['MaterialType']) - # Adding model 'HistoricalBaseItem' - db.create_table('archaeological_finds_historicalbaseitem', ( + # Adding model 'HistoricalBaseFind' + db.create_table('archaeological_finds_historicalbasefind', ( ('id', self.gf('django.db.models.fields.IntegerField')(db_index=True, blank=True)), ('history_modifier_id', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), ('label', self.gf('django.db.models.fields.CharField')(max_length=60)), @@ -35,23 +35,23 @@ class Migration(SchemaMigration): ('history_user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)), ('history_type', self.gf('django.db.models.fields.CharField')(max_length=1)), )) - db.send_create_signal('archaeological_finds', ['HistoricalBaseItem']) + db.send_create_signal('archaeological_finds', ['HistoricalBaseFind']) - # Adding model 'BaseItem' - db.create_table('archaeological_finds_baseitem', ( + # Adding model 'BaseFind' + db.create_table('archaeological_finds_basefind', ( ('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'])), ('label', self.gf('django.db.models.fields.CharField')(max_length=60)), ('description', self.gf('django.db.models.fields.TextField')()), - ('context_record', self.gf('django.db.models.fields.related.ForeignKey')(related_name='base_items', to=orm['archaeological_context_records.ContextRecord'])), + ('context_record', self.gf('django.db.models.fields.related.ForeignKey')(related_name='base_finds', to=orm['archaeological_context_records.ContextRecord'])), ('is_isolated', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), ('index', self.gf('django.db.models.fields.IntegerField')(default=0)), ('material_index', self.gf('django.db.models.fields.IntegerField')(default=0)), )) - db.send_create_signal('archaeological_finds', ['BaseItem']) + db.send_create_signal('archaeological_finds', ['BaseFind']) - # Adding model 'HistoricalItem' - db.create_table('archaeological_finds_historicalitem', ( + # Adding model 'HistoricalFind' + db.create_table('archaeological_finds_historicalfind', ( ('id', self.gf('django.db.models.fields.IntegerField')(db_index=True, blank=True)), ('history_modifier_id', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), ('order', self.gf('django.db.models.fields.IntegerField')()), @@ -60,7 +60,7 @@ class Migration(SchemaMigration): ('material_type_id', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), ('volume', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), ('weight', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), - ('item_number', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('find_number', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), ('upstream_treatment_id', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), ('downstream_treatment_id', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), ('dating_id', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), @@ -70,10 +70,10 @@ class Migration(SchemaMigration): ('history_user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)), ('history_type', self.gf('django.db.models.fields.CharField')(max_length=1)), )) - db.send_create_signal('archaeological_finds', ['HistoricalItem']) + db.send_create_signal('archaeological_finds', ['HistoricalFind']) - # Adding model 'Item' - db.create_table('archaeological_finds_item', ( + # Adding model 'Find' + db.create_table('archaeological_finds_find', ( ('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'])), ('order', self.gf('django.db.models.fields.IntegerField')()), @@ -82,41 +82,41 @@ class Migration(SchemaMigration): ('material_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archaeological_finds.MaterialType'])), ('volume', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), ('weight', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), - ('item_number', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('find_number', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), ('upstream_treatment', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='downstream_treatment', null=True, to=orm['archaeological_finds.Treatment'])), ('downstream_treatment', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='upstream_treatment', null=True, to=orm['archaeological_finds.Treatment'])), ('dating', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archaeological_context_records.Dating'])), - ('container', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='items', null=True, to=orm['archaeological_warehouse.Container'])), + ('container', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='finds', null=True, to=orm['archaeological_warehouse.Container'])), )) - db.send_create_signal('archaeological_finds', ['Item']) + db.send_create_signal('archaeological_finds', ['Find']) - # Adding M2M table for field base_items on 'Item' - db.create_table('archaeological_finds_item_base_items', ( + # Adding M2M table for field base_finds on 'Find' + db.create_table('archaeological_finds_find_base_finds', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('item', models.ForeignKey(orm['archaeological_finds.item'], null=False)), - ('baseitem', models.ForeignKey(orm['archaeological_finds.baseitem'], null=False)) + ('find', models.ForeignKey(orm['archaeological_finds.find'], null=False)), + ('basefind', models.ForeignKey(orm['archaeological_finds.basefind'], null=False)) )) - db.create_unique('archaeological_finds_item_base_items', ['item_id', 'baseitem_id']) + db.create_unique('archaeological_finds_find_base_finds', ['find_id', 'basefind_id']) - # Adding model 'ItemSource' - db.create_table('archaeological_finds_itemsource', ( + # Adding model 'FindSource' + db.create_table('archaeological_finds_findsource', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('title', self.gf('django.db.models.fields.CharField')(max_length=200)), ('source_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ishtar_common.SourceType'])), ('associated_url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)), ('receipt_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), ('creation_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), - ('item', self.gf('django.db.models.fields.related.ForeignKey')(related_name='source', to=orm['archaeological_finds.Item'])), + ('find', self.gf('django.db.models.fields.related.ForeignKey')(related_name='source', to=orm['archaeological_finds.Find'])), )) - db.send_create_signal('archaeological_finds', ['ItemSource']) + db.send_create_signal('archaeological_finds', ['FindSource']) - # Adding M2M table for field authors on 'ItemSource' - db.create_table('archaeological_finds_itemsource_authors', ( + # Adding M2M table for field authors on 'FindSource' + db.create_table('archaeological_finds_findsource_authors', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('itemsource', models.ForeignKey(orm['archaeological_finds.itemsource'], null=False)), + ('findsource', models.ForeignKey(orm['archaeological_finds.findsource'], null=False)), ('author', models.ForeignKey(orm['ishtar_common.author'], null=False)) )) - db.create_unique('archaeological_finds_itemsource_authors', ['itemsource_id', 'author_id']) + db.create_unique('archaeological_finds_findsource_authors', ['findsource_id', 'author_id']) # Adding model 'TreatmentType' db.create_table('archaeological_finds_treatmenttype', ( @@ -186,7 +186,7 @@ class Migration(SchemaMigration): ('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)), - ('item', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archaeological_finds.Item'])), + ('find', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archaeological_finds.Find'])), ('administrative_act', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archaeological_operations.AdministrativeAct'])), ('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ishtar_common.Person'])), ('start_date', self.gf('django.db.models.fields.DateField')()), @@ -199,26 +199,26 @@ class Migration(SchemaMigration): # Deleting model 'MaterialType' db.delete_table('archaeological_finds_materialtype') - # Deleting model 'HistoricalBaseItem' - db.delete_table('archaeological_finds_historicalbaseitem') + # Deleting model 'HistoricalBaseFind' + db.delete_table('archaeological_finds_historicalbasefind') - # Deleting model 'BaseItem' - db.delete_table('archaeological_finds_baseitem') + # Deleting model 'BaseFind' + db.delete_table('archaeological_finds_basefind') - # Deleting model 'HistoricalItem' - db.delete_table('archaeological_finds_historicalitem') + # Deleting model 'HistoricalFind' + db.delete_table('archaeological_finds_historicalfind') - # Deleting model 'Item' - db.delete_table('archaeological_finds_item') + # Deleting model 'Find' + db.delete_table('archaeological_finds_find') - # Removing M2M table for field base_items on 'Item' - db.delete_table('archaeological_finds_item_base_items') + # Removing M2M table for field base_finds on 'Find' + db.delete_table('archaeological_finds_find_base_finds') - # Deleting model 'ItemSource' - db.delete_table('archaeological_finds_itemsource') + # Deleting model 'FindSource' + db.delete_table('archaeological_finds_findsource') - # Removing M2M table for field authors on 'ItemSource' - db.delete_table('archaeological_finds_itemsource_authors') + # Removing M2M table for field authors on 'FindSource' + db.delete_table('archaeological_finds_findsource_authors') # Deleting model 'TreatmentType' db.delete_table('archaeological_finds_treatmenttype') @@ -265,7 +265,7 @@ class Migration(SchemaMigration): 'length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'location': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), 'operation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'context_record'", 'to': "orm['archaeological_operations.Operation']"}), - 'parcel': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'context_record'", 'to': "orm['archaeological_context_records.Parcel']"}), + 'parcel': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'context_record'", 'to': "orm['archaeological_operations.Parcel']"}), 'taq': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'taq_estimated': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'thickness': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), @@ -308,18 +308,6 @@ class Migration(SchemaMigration): 'order': ('django.db.models.fields.IntegerField', [], {}), 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, - 'archaeological_context_records.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_context_records.unit': { 'Meta': {'object_name': 'Unit'}, 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -382,9 +370,9 @@ 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_finds.baseitem': { - 'Meta': {'object_name': 'BaseItem'}, - 'context_record': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'base_items'", 'to': "orm['archaeological_context_records.ContextRecord']"}), + 'archaeological_finds.basefind': { + 'Meta': {'object_name': 'BaseFind'}, + 'context_record': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'base_finds'", 'to': "orm['archaeological_context_records.ContextRecord']"}), 'description': ('django.db.models.fields.TextField', [], {}), 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), @@ -393,8 +381,36 @@ class Migration(SchemaMigration): 'label': ('django.db.models.fields.CharField', [], {'max_length': '60'}), 'material_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}) }, - 'archaeological_finds.historicalbaseitem': { - 'Meta': {'ordering': "('-history_date', '-history_id')", 'object_name': 'HistoricalBaseItem'}, + 'archaeological_finds.find': { + 'Meta': {'object_name': 'Find'}, + 'base_finds': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'find'", 'symmetrical': 'False', 'to': "orm['archaeological_finds.BaseFind']"}), + 'container': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'finds'", 'null': 'True', 'to': "orm['archaeological_warehouse.Container']"}), + 'dating': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_context_records.Dating']"}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'downstream_treatment': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'upstream_treatment'", 'null': 'True', 'to': "orm['archaeological_finds.Treatment']"}), + 'find_number': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '60'}), + 'material_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_finds.MaterialType']"}), + 'order': ('django.db.models.fields.IntegerField', [], {}), + 'upstream_treatment': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'downstream_treatment'", 'null': 'True', 'to': "orm['archaeological_finds.Treatment']"}), + 'volume': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'weight': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) + }, + 'archaeological_finds.findsource': { + 'Meta': {'object_name': 'FindSource'}, + 'associated_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ishtar_common.Author']", 'symmetrical': 'False'}), + 'creation_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'find': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source'", 'to': "orm['archaeological_finds.Find']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'receipt_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'source_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.SourceType']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + 'archaeological_finds.historicalbasefind': { + 'Meta': {'ordering': "('-history_date', '-history_id')", 'object_name': 'HistoricalBaseFind'}, 'context_record_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), 'description': ('django.db.models.fields.TextField', [], {}), 'history_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), @@ -408,19 +424,19 @@ class Migration(SchemaMigration): 'label': ('django.db.models.fields.CharField', [], {'max_length': '60'}), 'material_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}) }, - 'archaeological_finds.historicalitem': { - 'Meta': {'ordering': "('-history_date', '-history_id')", 'object_name': 'HistoricalItem'}, + 'archaeological_finds.historicalfind': { + 'Meta': {'ordering': "('-history_date', '-history_id')", 'object_name': 'HistoricalFind'}, 'container_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), 'dating_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 'downstream_treatment_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'find_number': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'history_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'history_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'history_modifier_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), 'history_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), 'history_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), 'id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'blank': 'True'}), - 'item_number': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 'label': ('django.db.models.fields.CharField', [], {'max_length': '60'}), 'material_type_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), 'order': ('django.db.models.fields.IntegerField', [], {}), @@ -444,34 +460,6 @@ class Migration(SchemaMigration): 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), 'treatment_type_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) }, - 'archaeological_finds.item': { - 'Meta': {'object_name': 'Item'}, - 'base_items': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'item'", 'symmetrical': 'False', 'to': "orm['archaeological_finds.BaseItem']"}), - 'container': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'items'", 'null': 'True', 'to': "orm['archaeological_warehouse.Container']"}), - 'dating': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_context_records.Dating']"}), - 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'downstream_treatment': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'upstream_treatment'", 'null': 'True', 'to': "orm['archaeological_finds.Treatment']"}), - 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'item_number': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '60'}), - 'material_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_finds.MaterialType']"}), - 'order': ('django.db.models.fields.IntegerField', [], {}), - 'upstream_treatment': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'downstream_treatment'", 'null': 'True', 'to': "orm['archaeological_finds.Treatment']"}), - 'volume': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), - 'weight': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) - }, - 'archaeological_finds.itemsource': { - 'Meta': {'object_name': 'ItemSource'}, - 'associated_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), - 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ishtar_common.Author']", 'symmetrical': 'False'}), - 'creation_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'item': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source'", 'to': "orm['archaeological_finds.Item']"}), - 'receipt_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'source_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.SourceType']"}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}) - }, 'archaeological_finds.materialtype': { 'Meta': {'object_name': 'MaterialType'}, 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -486,10 +474,10 @@ class Migration(SchemaMigration): 'Meta': {'object_name': 'Property'}, 'administrative_act': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_operations.AdministrativeAct']"}), 'end_date': ('django.db.models.fields.DateField', [], {}), + 'find': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_finds.Find']"}), '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'}), - 'item': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_finds.Item']"}), 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Person']"}), 'start_date': ('django.db.models.fields.DateField', [], {}) }, @@ -588,6 +576,18 @@ 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.period': { 'Meta': {'object_name': 'Period'}, 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), diff --git a/archaeological_finds/models.py b/archaeological_finds/models.py index cea9a35f1..c61e22e68 100644 --- a/archaeological_finds/models.py +++ b/archaeological_finds/models.py @@ -39,11 +39,11 @@ class MaterialType(GeneralType): verbose_name = _(u"Material type") verbose_name_plural = _(u"Material types") -class BaseItem(BaseHistorizedItem, OwnPerms): +class BaseFind(BaseHistorizedItem, OwnPerms): label = models.CharField(_(u"ID"), max_length=60) description = models.TextField(_(u"Description")) context_record = models.ForeignKey(ContextRecord, - related_name='base_items', verbose_name=_(u"Context Record")) + related_name='base_finds', verbose_name=_(u"Context Record")) is_isolated = models.NullBooleanField(_(u"Is isolated?"), blank=True, null=True) index = models.IntegerField(u"Index", default=0) @@ -51,40 +51,40 @@ class BaseItem(BaseHistorizedItem, OwnPerms): history = HistoricalRecords() class Meta: - verbose_name = _(u"Base item") - verbose_name_plural = _(u"Base items") + verbose_name = _(u"Base find") + verbose_name_plural = _(u"Base finds") permissions = ( - ("view_own_baseitem", ugettext(u"Can view own Base item")), - ("add_own_baseitem", ugettext(u"Can add own Base item")), - ("change_own_baseitem", ugettext(u"Can change own Base item")), - ("delete_own_baseitem", ugettext(u"Can delete own Base item")), + ("view_own_basefind", ugettext(u"Can view own Base find")), + ("add_own_basefind", ugettext(u"Can add own Base find")), + ("change_own_basefind", ugettext(u"Can change own Base find")), + ("delete_own_basefind", ugettext(u"Can delete own Base find")), ) def __unicode__(self): return self.label - def get_last_item(self): - #TODO: manage virtuals - property(last_item) ? - items = self.item.filter().order_by("-order").all() - return items and items[0] + def get_last_find(self): + #TODO: manage virtuals - property(last_find) ? + finds = self.find.filter().order_by("-order").all() + return finds and finds[0] def full_label(self): return self._real_label() or self._temp_label() def material_type_label(self): - item = self.get_last_item() - items = [item and unicode(item.material_type) or ''] + find = self.get_last_find() + finds = [find and unicode(find.material_type) or ''] ope = self.context_record.operation - items += [ope.code_patriarche or \ + finds += [ope.code_patriarche or \ (unicode(ope.year) + "-" + unicode(ope.operation_code))] - items += [self.context_record.label, unicode(self.material_index)] - return JOINT.join(items) + finds += [self.context_record.label, unicode(self.material_index)] + return JOINT.join(finds) def _real_label(self): if not self.context_record.parcel.operation.code_patriarche: return - item = self.get_last_item() - lbl = item.label or self.label + find = self.get_last_find() + lbl = find.label or self.label return JOINT.join([unicode(it) for it in ( self.context_record.parcel.operation.code_patriarche, self.context_record.label, @@ -93,25 +93,25 @@ class BaseItem(BaseHistorizedItem, OwnPerms): def _temp_label(self): if self.context_record.parcel.operation.code_patriarche: return - item = self.get_last_item() - lbl = item.label or self.label + find = self.get_last_find() + lbl = find.label or self.label return JOINT.join([unicode(it) for it in ( self.context_record.parcel.year, self.index, self.context_record.label, lbl) if it]) -class Item(BaseHistorizedItem, OwnPerms): +class Find(BaseHistorizedItem, OwnPerms): TABLE_COLS = ['label', 'material_type', 'dating.period', - 'base_items.context_record.parcel.town', - 'base_items.context_record.parcel.operation.year', - 'base_items.context_record.parcel.operation.operation_code', - 'base_items.is_isolated'] + 'base_finds.context_record.parcel.town', + 'base_finds.context_record.parcel.operation.year', + 'base_finds.context_record.parcel.operation.operation_code', + 'base_finds.is_isolated'] if settings.COUNTRY == 'fr': TABLE_COLS.insert(6, - 'base_items.context_record.parcel.operation.code_patriarche') - base_items = models.ManyToManyField(BaseItem, verbose_name=_(u"Base item"), - related_name='item') + 'base_finds.context_record.parcel.operation.code_patriarche') + base_finds = models.ManyToManyField(BaseFind, verbose_name=_(u"Base find"), + related_name='find') order = models.IntegerField(_(u"Order")) label = models.CharField(_(u"ID"), max_length=60) description = models.TextField(_(u"Description"), blank=True, null=True) @@ -119,7 +119,7 @@ class Item(BaseHistorizedItem, OwnPerms): verbose_name = _(u"Material type")) volume = models.FloatField(_(u"Volume (l)"), blank=True, null=True) weight = models.FloatField(_(u"Weight (g)"), blank=True, null=True) - item_number = models.IntegerField(_("Item number"), blank=True, null=True) + find_number = models.IntegerField(_("Find number"), blank=True, null=True) upstream_treatment = models.ForeignKey("Treatment", blank=True, null=True, related_name='downstream_treatment', verbose_name=_("Upstream treatment")) downstream_treatment = models.ForeignKey("Treatment", blank=True, null=True, @@ -127,15 +127,15 @@ class Item(BaseHistorizedItem, OwnPerms): dating = models.ForeignKey(Dating, verbose_name=_(u"Dating")) if WAREHOUSE_AVAILABLE: container = models.ForeignKey(Container, verbose_name=_(u"Container"), - blank=True, null=True, related_name='items') + blank=True, null=True, related_name='finds') history = HistoricalRecords() @classmethod def get_years(cls): years = set() - items = cls.objects.filter(downstream_treatment__isnull=True) - for item in items: - bi = item.base_items.all() + finds = cls.objects.filter(downstream_treatment__isnull=True) + for find in finds: + bi = find.base_finds.all() if not bi: continue bi = bi[0] @@ -146,14 +146,14 @@ class Item(BaseHistorizedItem, OwnPerms): @classmethod def get_by_year(cls, year): return cls.objects.filter(downstream_treatment__isnull=True, - base_items__context_record__operation__start_date__year=year) + base_finds__context_record__operation__start_date__year=year) @classmethod def get_operations(cls): operations = set() - items = cls.objects.filter(downstream_treatment__isnull=True) - for item in items: - bi = item.base_items.all() + finds = cls.objects.filter(downstream_treatment__isnull=True) + for find in finds: + bi = find.base_finds.all() if not bi: continue bi = bi[0] @@ -164,7 +164,7 @@ class Item(BaseHistorizedItem, OwnPerms): @classmethod def get_by_operation(cls, operation_id): return cls.objects.filter(downstream_treatment__isnull=True, - base_items__context_record__operation__pk=operation_id) + base_finds__context_record__operation__pk=operation_id) @classmethod def get_total_number(cls): @@ -173,23 +173,23 @@ class Item(BaseHistorizedItem, OwnPerms): def duplicate(self, user): dct = dict([(attr, getattr(self, attr)) for attr in ('order', 'label', 'description', 'material_type', 'volume', 'weight', - 'item_number', 'dating')]) + 'find_number', 'dating')]) dct['order'] += 1 dct['history_modifier'] = user new = self.__class__(**dct) new.save() - for base_item in self.base_items.all(): - new.base_items.add(base_item) + for base_find in self.base_finds.all(): + new.base_finds.add(base_find) return new class Meta: - verbose_name = _(u"Item") - verbose_name_plural = _(u"Items") + verbose_name = _(u"Find") + verbose_name_plural = _(u"Finds") permissions = ( - ("view_own_item", ugettext(u"Can view own Item")), - ("add_own_item", ugettext(u"Can add own Item")), - ("change_own_item", ugettext(u"Can change own Item")), - ("delete_own_item", ugettext(u"Can delete own Item")), + ("view_own_find", ugettext(u"Can view own Find")), + ("add_own_find", ugettext(u"Can add own Find")), + ("change_own_find", ugettext(u"Can change own Find")), + ("delete_own_find", ugettext(u"Can delete own Find")), ) def __unicode__(self): @@ -197,27 +197,27 @@ class Item(BaseHistorizedItem, OwnPerms): def save(self, *args, **kwargs): if not self.pk: - super(Item, self).save(*args, **kwargs) - for base_item in self.base_items.all(): - if not base_item.index: - idx = BaseItem.objects.filter(context_record=\ - base_item.context_record).aggregate(Max('index')) - base_item.index = idx and idx['index__max'] + 1 or 1 - if not base_item.material_index: - idx = BaseItem.objects.filter(context_record=\ - base_item.context_record, - item__material_type=self.material_type).aggregate( + super(Find, self).save(*args, **kwargs) + for base_find in self.base_finds.all(): + if not base_find.index: + idx = BaseFind.objects.filter(context_record=\ + base_find.context_record).aggregate(Max('index')) + base_find.index = idx and idx['index__max'] + 1 or 1 + if not base_find.material_index: + idx = BaseFind.objects.filter(context_record=\ + base_find.context_record, + find__material_type=self.material_type).aggregate( Max('material_index')) - base_item.material_index = idx and \ + base_find.material_index = idx and \ idx['material_index__max'] + 1 or 1 - base_item.save() - super(Item, self).save(*args, **kwargs) + base_find.save() + super(Find, self).save(*args, **kwargs) -class ItemSource(Source): +class FindSource(Source): class Meta: - verbose_name = _(u"Item documentation") - verbose_name_plural = _(u"Item documentations") - item = models.ForeignKey(Item, verbose_name=_(u"Item"), + verbose_name = _(u"Find documentation") + verbose_name_plural = _(u"Find documentations") + find = models.ForeignKey(Find, verbose_name=_(u"Find"), related_name="source") class TreatmentType(GeneralType): @@ -265,7 +265,7 @@ class TreatmentSource(Source): related_name="source") class Property(LightHistorizedItem): - item = models.ForeignKey(Item, verbose_name=_(u"Item")) + find = models.ForeignKey(Find, verbose_name=_(u"Find")) administrative_act = models.ForeignKey(AdministrativeAct, verbose_name=_(u"Administrative act")) person = models.ForeignKey(Person, verbose_name=_(u"Person")) @@ -277,5 +277,5 @@ class Property(LightHistorizedItem): verbose_name_plural = _(u"Properties") def __unicode__(self): - return self.person + JOINT + self.item + return self.person + JOINT + self.find diff --git a/archaeological_finds/urls.py b/archaeological_finds/urls.py new file mode 100644 index 000000000..4c733436d --- /dev/null +++ b/archaeological_finds/urls.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from django.conf.urls.defaults import * + +""" +import forms + +# forms +urlpatterns = patterns('', + url(r'item_search/(?P.+)$', + forms.item_search_wizard, name='item_search'), + url(r'item_creation/(?P.+)$', + forms.item_creation_wizard, name='item_creation'), + url(r'item_modification/(?P.+)$', + forms.item_modification_wizard, name='item_modification'), + url(r'item_source_creation/(?P.+)$', + forms.item_source_creation_wizard, + name='item_source_creation'), + url(r'item_source_modification/(?P.+)$', + forms.item_source_modification_wizard, + name='item_source_modification'), + url(r'item_source_deletion/(?P.+)$', + forms.item_source_deletion_wizard, + name='item_source_deletion'), +) + +urlpatterns += patterns('archaeological_finds.views', + url(r'get-find/(?P.+)?$', 'get_find', + name='get-find'), + url(r'get-find-full/(?P.+)?$', 'get_find', + name='get-find-full', kwargs={'full':True}), + url(r'get-findsource/(?P.+)?$', + 'get_findsource', name='get-findsource'), +) +""" diff --git a/archaeological_finds/views.py b/archaeological_finds/views.py new file mode 100644 index 000000000..af428d59e --- /dev/null +++ b/archaeological_finds/views.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from ishtar_common.views import get_item, show_item, revert_item +import models + +get_find = get_item(models.Find, + 'get_find', 'find', + bool_fields = ['base_finds__is_isolated'], + base_request={'downstream_treatment__isnull':True}, + extra_request_keys={ +'base_finds__context_record__parcel__town': + 'base_finds__context_record__parcel__town', +'base_finds__context_record__operation__year': + 'base_finds__context_record__operation__year__contains', +'base_finds__context_record__operation__code_patriarche': + 'base_finds__context_record__operation__code_patriarche', +'dating__period':'dating__period__pk', +'base_finds__find__description':'base_finds__find__description__icontains', +'base_finds__is_isolated':'base_finds__is_isolated'}) +get_findsource = get_item(models.FindSource, + 'get_findsource', 'findsource', + extra_request_keys={ +'find__context_record__operation__year':'find__context_record__operation__year', +'find__dating__period':'find__dating__period__pk', +'find__description':'find__description__icontains', + }) 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 + +# 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. + +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 + +# 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. + +""" +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 \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 +# Copyright (C) 2012 É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 @@ -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 + +# 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. + +from django.conf.urls.defaults import * + +""" +import forms + +# forms +urlpatterns = patterns('', + url(r'operation_search/(?P.+)$', + forms.operation_search_wizard, name='operation_search'), + url(r'operation_creation/(?P.+)$', + forms.operation_creation_wizard, name='operation_creation'), + url(r'operation_modification/(?P.+)$', + forms.operation_modification_wizard, + name='operation_modification'), + url(r'operation_closing/(?P.+)$', + forms.operation_closing_wizard, name='operation_closing'), + url(r'operation_deletion/(?P.+)$', + forms.operation_deletion_wizard, name='operation_deletion'), + url(r'operation_administrativeactop/(?P.+)$', + forms.operation_administrativeactop_wizard, + name='operation_administrativeactop'), + url(r'operation_administrativeactop_modification/(?P.+)$', + forms.operation_administrativeactop_modification_wizard, + name='operation_administrativeactop_modification'), + url(r'operation_administrativeactop_deletion/(?P.+)$', + forms.operation_administrativeactop_deletion_wizard, + name='operation_administrativeactop_deletion'), + url(r'operation_source_creation/(?P.+)$', + forms.operation_source_creation_wizard, + name='operation_source_creation'), + url(r'operation_source_modification/(?P.+)$', + forms.operation_source_modification_wizard, + name='operation_source_modification'), + url(r'operation_source_deletion/(?P.+)$', + 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.+)?$', 'get_operation', + name='get-operation'), + url(r'get-operation-full/(?P.+)?$', 'get_operation', + name='get-operation-full', kwargs={'full':True}), + url(r'get-available-operation-code/(?P.+)?$', + 'get_available_operation_code', name='get_available_operation_code'), + url(r'revert-operation/(?P.+)/(?P.+)$', + 'revert_operation', name='revert-operation'), + url(r'show-operation/(?P.+)?/(?P.+)?$', + 'show_operation', name='show-operation'), + url(r'get-administrativeactop/(?P.+)?$', + 'get_administrativeactop', name='get-administrativeactop'), + url(r'get-operationsource/(?P.+)?$', + '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 + +# 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 + +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)) diff --git a/archaeological_warehouse/admin.py b/archaeological_warehouse/admin.py new file mode 100644 index 000000000..cf026e86c --- /dev/null +++ b/archaeological_warehouse/admin.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.conf import settings +from django.contrib import admin + +from ishtar_common.admin import HistorizedObjectAdmin + +import models + +class WarehouseAdmin(HistorizedObjectAdmin): + list_display = ('name', 'warehouse_type', 'town') + list_filter = ('warehouse_type',) + search_fields = ('name', 'town') + model = models.Warehouse + +admin.site.register(models.Warehouse, WarehouseAdmin) + +class ContainerTypeAdmin(admin.ModelAdmin): + list_display = ('label', 'reference', 'length', 'width', 'height', + 'volume') + model = models.ContainerType + +admin.site.register(models.ContainerType, ContainerTypeAdmin) + +class ContainerAdmin(admin.ModelAdmin): + list_display = ('reference', 'location', 'container_type',) + list_filter = ("container_type",) + model = models.Container + +admin.site.register(models.Container, ContainerAdmin) + +basic_models = [models.WarehouseType] +for model in basic_models: + admin.site.register(model) diff --git a/archaeological_warehouse/ishtar_menu.py b/archaeological_warehouse/ishtar_menu.py new file mode 100644 index 000000000..1a9d57aaa --- /dev/null +++ b/archaeological_warehouse/ishtar_menu.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.utils.translation import ugettext_lazy as _ + +from ishtar_common.menu_base import SectionItem, MenuItem + +from archaeological_finds.models import Treatment +import models + +MENU_SECTIONS = [ + (60, SectionItem('warehouse', _(u"Warehouse"), + childs=[ + MenuItem('warehouse_inventory', _(u"Inventory"), + model=models.Warehouse, + access_controls=['change_warehouse',]), + MenuItem('warehouse_recording', _(u"Recording"), + model=Treatment, + access_controls=['add_treatment', 'add_own_treatment']), + MenuItem('warehouse_lend', _(u"Lending"), + model=Treatment, + access_controls=['add_treatment', 'add_own_treatment']), + ])) +] diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index fe381e521..877d16487 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -51,7 +51,6 @@ class Warehouse(Address, OwnPerms): def __unicode__(self): return u"%s (%s)" % (self.name, unicode(self.warehouse_type)) - class ContainerType(GeneralType): length = models.IntegerField(_(u"Length (mm)"), blank=True, null=True) width = models.IntegerField(_(u"Width (mm)"), blank=True, null=True) diff --git a/archaeological_warehouse/urls.py b/archaeological_warehouse/urls.py new file mode 100644 index 000000000..914ee2533 --- /dev/null +++ b/archaeological_warehouse/urls.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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. + +from django.conf.urls.defaults import * + +""" +import forms + +# forms +urlpatterns = patterns('', + url(r'treatment_creation/(?P.+)$', + forms.treatment_creation_wizard, name='treatment_creation'), + url(r'warehouse_packaging/(?P.+)$', + forms.warehouse_packaging_wizard, name='warehouse_packaging'), +) + +urlpatterns += patterns('archaeological_warehouse.views', + url(r'new-warehouse/(?P.+)?/$', + 'new_warehouse', name='new-warehouse'), + url(r'autocomplete-warehouse/$', 'autocomplete_warehouse', + name='autocomplete-warehouse'), + url(r'new-container/(?P.+)?/$', + 'new_container', name='new-container'), + url(r'get-container/$', 'get_container', + name='get-container'), + url(r'autocomplete-container/?$', + 'autocomplete_container', name='autocomplete-container'), +) +""" diff --git a/archaeological_warehouse/views.py b/archaeological_warehouse/views.py new file mode 100644 index 000000000..16a8e19f5 --- /dev/null +++ b/archaeological_warehouse/views.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 É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 + +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 + +get_container = get_item(models.Container, + 'get_container', 'container', + extra_request_keys={ + 'location':'location__pk', + 'container_type':'container_type__pk', + 'reference':'reference__icontains', + }) + +new_warehouse = new_item(models.Warehouse) +new_container = new_item(models.Container) + +def autocomplete_warehouse(request): + if not request.user.has_perm('ishtar_common.view_warehouse', + models.Warehouse)\ + and not request.user.has_perm('ishtar_common.view_own_warehouse', + models.Warehouse) : + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(name__icontains=q) | \ + Q(warehouse_type__label__icontains=q) + query = query & extra + limit = 15 + warehouses = models.Warehouse.objects.filter(query)[:limit] + data = json.dumps([{'id':warehouse.pk, 'value':unicode(warehouse)} + for warehouse in warehouses]) + return HttpResponse(data, mimetype='text/plain') + +def autocomplete_container(request): + if not request.user.has_perm('ishtar_common.view_warehouse', + models.Warehouse)\ + and not request.user.has_perm('ishtar_common.view_own_warehouse', + models.Warehouse): + return HttpResponse(mimetype='text/plain') + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + query = Q() + for q in q.split(' '): + extra = Q(container_type__label__icontains=q) | \ + Q(container_type__reference__icontains=q) | \ + Q(reference__icontains=q) | \ + Q(location__name=q) | \ + Q(location__town=q) + query = query & extra + limit = 15 + containers = models.Container.objects.filter(query)[:limit] + data = json.dumps([{'id':container.pk, 'value':unicode(container)} + for container in containers]) + return HttpResponse(data, mimetype='text/plain') diff --git a/example_project/urls.py b/example_project/urls.py index ea54a61e1..02936e6df 100644 --- a/example_project/urls.py +++ b/example_project/urls.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.conf.urls.defaults import * from django.contrib.auth.models import User @@ -5,15 +6,23 @@ from django.contrib import admin admin.autodiscover() #admin.site.unregister(User) -from settings import URL_PATH - -BASE_URL = r'^' + URL_PATH urlpatterns = patterns('', - (BASE_URL + 'accounts/', include('registration.urls')), - (BASE_URL + r'admin/', include(admin.site.urls)), + (r'^accounts/', include('registration.urls')), + (r'^admin/', include(admin.site.urls)), ('', include('ishtar_common.urls')), ) + +""" +for app in ['archaeological_files', 'archaeological_operations', + 'archaeological_context_records', 'archaeological_warehouse', + 'archaeological_finds']: + if app in settings.INSTALLED_APPS: + urlpatterns = patterns('', + ('', include(app+'.urls')), + ) +""" + urlpatterns += patterns('ishtar_common.views', - url(BASE_URL + '$', 'index', name='start'), + url(r'$', 'index', name='start'), ) diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index c4e5fa7f2..8f6a07f88 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 Étienne Loks +# Copyright (C) 2010-2012 É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 @@ -57,130 +57,6 @@ class PersonAdmin(HistorizedObjectAdmin): admin.site.register(models.Person, PersonAdmin) -class FileAdmin(HistorizedObjectAdmin): - list_display = ['year', 'numeric_reference', 'internal_reference', - 'end_date', 'file_type', 'general_contractor',] - if settings.COUNTRY == 'fr': - list_display += ['saisine_type', 'reference_number'] - list_filter = ("file_type", "year",) - search_fields = ('towns__name',) - model = models.File - -admin.site.register(models.File, FileAdmin) - -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) - -class PeriodAdmin(admin.ModelAdmin): - list_display = ('label', 'start_date', 'end_date', 'parent') - model = models.Period - -admin.site.register(models.Period, PeriodAdmin) - -class DatingAdmin(admin.ModelAdmin): - list_display = ('period', 'start_date', 'end_date', 'dating_type', - 'quality') - list_filter = ("period", 'dating_type', 'quality') - model = models.Dating - -admin.site.register(models.Dating, DatingAdmin) - -class ContextRecordAdmin(HistorizedObjectAdmin): - list_display = ('label', 'length', 'width', - 'thickness', 'depth') - list_filter = ('has_furniture',) - search_fields = ('parcel__operation__name', "datings__period__label") - model = models.ContextRecord - -admin.site.register(models.ContextRecord, ContextRecordAdmin) - -class ContextRecordSourceAdmin(admin.ModelAdmin): - list_display = ('context_record', 'title', 'source_type',) - list_filter = ('source_type',) - search_fields = ('title', ) - model = models.ContextRecordSource - -admin.site.register(models.ContextRecordSource, ContextRecordSourceAdmin) - -class BaseItemAdmin(HistorizedObjectAdmin): - list_display = ('label', 'context_record', 'is_isolated') - search_fields = ('label', 'context_record__parcel__operation__name',) - model = models.BaseItem - -admin.site.register(models.BaseItem, BaseItemAdmin) - -class ItemAdmin(HistorizedObjectAdmin): - list_display = ('label', 'material_type', 'dating', 'volume', 'weight', - 'item_number',) - list_filter = ('material_type',) - search_fields = ('label', "dating__period__label") - model = models.Item - -admin.site.register(models.Item, ItemAdmin) - -class ItemSourceAdmin(admin.ModelAdmin): - list_display = ('item', 'title', 'source_type',) - list_filter = ('source_type',) - search_fields = ('title', ) - model = models.ItemSource - -admin.site.register(models.ItemSource, ItemSourceAdmin) - -class WarehouseAdmin(HistorizedObjectAdmin): - list_display = ('name', 'warehouse_type', 'town') - list_filter = ('warehouse_type',) - search_fields = ('name', 'town') - model = models.Warehouse - -admin.site.register(models.Warehouse, WarehouseAdmin) - -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 ContainerTypeAdmin(admin.ModelAdmin): - list_display = ('label', 'reference', 'length', 'width', 'height', - 'volume') - model = models.ContainerType - -admin.site.register(models.ContainerType, ContainerTypeAdmin) - -class ContainerAdmin(admin.ModelAdmin): - list_display = ('reference', 'location', 'container_type',) - list_filter = ("container_type",) - model = models.Container - -admin.site.register(models.Container, ContainerAdmin) - class TownAdmin(admin.ModelAdmin): list_display = ['name',] search_fields = ['name'] @@ -199,42 +75,16 @@ class AuthorAdmin(admin.ModelAdmin): admin.site.register(models.Author, AuthorAdmin) -class PropertyAdmin(admin.ModelAdmin): - list_display = ['item', 'person', 'start_date', 'end_date'] - search_fields = ('item__label', 'person__name') - model = models.Property - -admin.site.register(models.Property, PropertyAdmin) - -class TreatmentAdmin(HistorizedObjectAdmin): - list_display = ('location', 'treatment_type', 'container', 'person') - list_filter = ('treatment_type',) - model = models.Treatment - -admin.site.register(models.Treatment, TreatmentAdmin) - -class TreatmentSourceAdmin(admin.ModelAdmin): - list_display = ('treatment', 'title', 'source_type',) - list_filter = ('source_type',) - search_fields = ('title',) - model = models.TreatmentSource - -admin.site.register(models.TreatmentSource, TreatmentSourceAdmin) - class PersonTypeAdmin(admin.ModelAdmin): model = models.PersonType filter_vertical = ('rights',) admin.site.register(models.PersonType, PersonTypeAdmin) -basic_models = [models.IshtarUser, models.FileType, models.OperationType, - models.DatingType, models.DatingQuality, models.SourceType, - models.MaterialType, models.ParcelOwner, models.WarehouseType, - models.ActType, models.AuthorType, models.OrganizationType, - models.TreatmentType, models.RemainType, models.PermitType, - models.Unit, models.ActivityType, models.IdentificationType] +basic_models = [models.IshtarUser, models.SourceType, models.AuthorType, + models.OrganizationType] if settings.COUNTRY == 'fr': - basic_models += [models.Arrondissement, models.Canton, models.SaisineType] + basic_models += [models.Arrondissement, models.Canton] for model in basic_models: admin.site.register(model) diff --git a/ishtar_common/context_processors.py b/ishtar_common/context_processors.py index 5de5a6afd..884ec19b4 100644 --- a/ishtar_common/context_processors.py +++ b/ishtar_common/context_processors.py @@ -23,6 +23,19 @@ from django.utils.translation import ugettext, ugettext_lazy as _ from menus import Menu import models +from archaeological_operations.models import Operation + +CURRENT_ITEMS = [] +if 'archaeological_files' in settings.INSTALLED_APPS: + from archaeological_files.models import File + CURRENT_ITEMS.append((_(u"Archaeological file"), File)) +CURRENT_ITEMS.append((_(u"Operation"), Operation)) +if 'archaeological_context_records' in settings.INSTALLED_APPS: + from archaeological_context_records.models import ContextRecord + CURRENT_ITEMS.append((_(u"Context record"), ContextRecord)) +if 'archaeological_finds' in settings.INSTALLED_APPS: + from archaeological_finds.models import Find + CURRENT_ITEMS.append((_(u"Find"), Find)) def get_base_context(request): dct = {'URL_PATH':settings.URL_PATH} @@ -39,11 +52,7 @@ def get_base_context(request): dct['JQUERY_URL'] = settings.JQUERY_URL dct['JQUERY_UI_URL'] = settings.JQUERY_UI_URL dct['current_menu'] = [] - for lbl, model in ((_(u"Archaeological file"), models.File), - (_(u"Operation"), models.Operation), - (_(u"Context record"), models.ContextRecord), - (_(u"Archaeological item"), models.Item), - ): + for lbl, model in CURRENT_ITEMS: model_name = model.__name__.lower() current = model_name in request.session and request.session[model_name] items = [] diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index e5f23e620..d3ae65d55 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -265,58 +265,6 @@ class TownFormSet(FormSet): TownFormset = formset_factory(TownForm, can_delete=True, formset=TownFormSet) TownFormset.form_label = _("Towns") -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") - ###################### # Sources management # ###################### diff --git a/ishtar_common/forms_context_records.py b/ishtar_common/forms_context_records.py deleted file mode 100644 index 816782bd8..000000000 --- a/ishtar_common/forms_context_records.py +++ /dev/null @@ -1,362 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 É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. - -""" -Context records forms definitions -""" -import datetime -from itertools import groupby - -from django import forms -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 _ - -from ishtar import settings - -import models -import widgets -from forms import Wizard, FinalForm, FormSet, SearchWizard, DeletionWizard, \ - formset_factory, get_now, reverse_lazy, get_form_selection -from forms_common import get_town_field, SourceForm, SourceWizard, \ - SourceSelect, AuthorFormset -from forms_operations import OperationSelect - -class RecordWizard(Wizard): - model = models.ContextRecord - edit = False - - def get_current_operation(self, request, storage): - step = storage.get_current_step() - if not step: - return - if step.endswith('_creation'): # an operation has been selected - main_form_key = 'selec-' + self.url_name - try: - idx = int(self.session_get_value(request, storage, - main_form_key, 'operation_id')) - current_ope = models.Operation.objects.get(pk=idx) - return current_ope - except(TypeError, ValueError, ObjectDoesNotExist): - pass - current_cr = self.get_current_object(request, storage) - if current_cr: - return current_cr.parcel.operation - - def get_template_context(self, request, storage, form=None): - """ - Get the operation "reminder" on top of wizard forms - """ - context = super(RecordWizard, self).get_template_context(request, - storage, form) - operation = self.get_current_operation(request, storage) - if not operation: - return context - items = [] - if hasattr(operation, 'code_patriarche') and operation.code_patriarche: - items.append(unicode(operation.code_patriarche)) - items.append("-".join((unicode(operation.year), - unicode(operation.operation_code)))) - context['reminder'] = _("Current operation: ") + " - ".join(items) - return context - - def get_form(self, request, storage, step=None, data=None, files=None): - """ - Get associated operation - """ - 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 - if step.startswith('general-'): - if step.endswith('_creation'): # an operation has been selected - main_form_key = 'selec-' + self.url_name - try: - idx = int(self.session_get_value(request, storage, - main_form_key, 'operation_id')) - current_obj = models.Operation.objects.get(pk=idx) - data['operation'] = current_obj - except(TypeError, ValueError, ObjectDoesNotExist): - pass - else: - current_object = self.get_current_object(request, storage) - data['context_record'] = current_object - form = super(RecordWizard, self).get_form(request, storage, step, data, - files) - return form - -class RecordModifWizard(RecordWizard): - modification = True - model = models.ContextRecord - -class RecordSelect(forms.Form): - parcel__town = get_town_field() - operation__year = forms.IntegerField(label=_(u"Year")) - datings__period = forms.ChoiceField(label=_(u"Period"), choices=[]) - unit = forms.ChoiceField(label=_(u"Unit type"), choices=[]) - def __init__(self, *args, **kwargs): - super(RecordSelect, self).__init__(*args, **kwargs) - self.fields['datings__period'].choices = \ - models.Period.get_types() - self.fields['datings__period'].help_text = \ - models.Period.get_help() - self.fields['unit'].choices = models.Unit.get_types() - self.fields['unit'].help_text = models.Unit.get_help() - -class RecordFormSelection(forms.Form): - form_label = _("Context record search") - associated_models = {'pk':models.ContextRecord} - currents = {'pk':models.ContextRecord} - pk = forms.IntegerField(label="", required=False, - widget=widgets.JQueryJqGrid(reverse_lazy('get-contextrecord'), - RecordSelect(), models.ContextRecord, - source_full=reverse_lazy('get-contextrecord-full')), - validators=[models.valid_id(models.ContextRecord)]) - - 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 at least select one " - u"context record.")) - return cleaned_data - - -class RecordFormGeneral(forms.Form): - form_label = _("General") - associated_models = {'parcel':models.Parcel, 'unit':models.Unit} - pk = forms.IntegerField(required=False, widget=forms.HiddenInput) - operation_id = forms.IntegerField(widget=forms.HiddenInput) - parcel = forms.ChoiceField(label=_("Parcel"), choices=[]) - label = forms.CharField(label=_(u"ID"), - validators=[validators.MaxLengthValidator(200)]) - description = forms.CharField(label=_(u"Description"), - widget=forms.Textarea, required=False) - length = forms.IntegerField(label=_(u"Length (cm)"), required=False) - width = forms.IntegerField(label=_(u"Width (cm)"), required=False) - thickness = forms.IntegerField(label=_(u"Thickness (cm)"), required=False) - depth = forms.IntegerField(label=_(u"Depth (cm)"), required=False) - unit = forms.ChoiceField(label=_("Unit"), required=False, - choices=models.Unit.get_types()) - location = forms.CharField(label=_(u"Location"), widget=forms.Textarea, - required=False, validators=[validators.MaxLengthValidator(200)]) - - def __init__(self, *args, **kwargs): - operation = None - if 'data' in kwargs and kwargs['data'] and \ - ('operation' in kwargs['data'] or 'context_record' in kwargs['data']): - if 'operation' in kwargs['data']: - operation = kwargs['data']['operation'] - if 'context_record' in kwargs['data'] and \ - kwargs['data']['context_record']: - operation = kwargs['data']['context_record'].operation - # clean data if not "real" data - prefix_value = kwargs['prefix'] - if not [k for k in kwargs['data'].keys() - if k.startswith(kwargs['prefix']) and kwargs['data'][k]]: - kwargs['data'] = None - if 'files' in kwargs: - kwargs.pop('files') - super(RecordFormGeneral, self).__init__(*args, **kwargs) - self.fields['parcel'].choices = [('', '--')] - if operation: - self.fields['operation_id'].initial = operation.pk - parcels = operation.parcels.all() - sort = lambda x: (x.town.name, x.section) - parcels = sorted(parcels, key=sort) - for key, gparcels in groupby(parcels, sort): - self.fields['parcel'].choices.append( - (" - ".join(key), [(parcel.pk, parcel.short_label()) for parcel in gparcels]) - ) - - def clean(self): - # manage unique context record ID - cleaned_data = self.cleaned_data - operation_id = cleaned_data.get("operation_id") - label = cleaned_data.get("label") - cr = models.ContextRecord.objects.filter(label=label, - parcel__operation__pk=operation_id) - if 'pk' in cleaned_data and cleaned_data['pk']: - cr = cr.exclude(pk=cleaned_data['pk']) - if cr.count(): - raise forms.ValidationError(_(u"This ID already exist for " - u"this operation.")) - return cleaned_data - -class DatingForm(forms.Form): - form_label = _("Dating") - base_model = 'dating' - associated_models = {'dating_type':models.DatingType, - 'quality':models.DatingQuality, - 'period':models.Period} - period = forms.ChoiceField(label=_("Period"), - choices=models.Period.get_types()) - start_date = forms.IntegerField(label=_(u"Start date"), required=False) - end_date = forms.IntegerField(label=_(u"End date"), required=False) - quality = forms.ChoiceField(label=_("Quality"), required=False, - choices=models.DatingQuality.get_types()) - dating_type = forms.ChoiceField(label=_("Dating type"), required=False, - choices=[]) - - def __init__(self, *args, **kwargs): - super(DatingForm, self).__init__(*args, **kwargs) - self.fields['dating_type'].choices = models.DatingType.get_types() - self.fields['dating_type'].help_text = models.DatingType.get_help() - - -DatingFormSet = formset_factory(DatingForm, can_delete=True, - formset=FormSet) -DatingFormSet.form_label = _("Dating") - -class RecordFormInterpretation(forms.Form): - form_label = _("Interpretation") - associated_models = {'activity':models.ActivityType, - 'identification':models.IdentificationType,} - has_furniture = forms.NullBooleanField(label=_(u"Has furniture?"), - required=False) - filling = forms.CharField(label=_(u"Filling"), - widget=forms.Textarea, required=False) - interpretation = forms.CharField(label=_(u"Interpretation"), - widget=forms.Textarea, required=False) - activity = forms.ChoiceField(label=_(u"Activity"), required=False, - choices=[]) - identification = forms.ChoiceField(label=_("Identification"), - required=False, choices=[]) - taq = forms.IntegerField(label=_(u"TAQ"), required=False) - taq_estimated = forms.IntegerField(label=_(u"Estimated TAQ"), - required=False) - tpq = forms.IntegerField(label=_(u"TPQ"), required=False) - tpq_estimated = forms.IntegerField(label=_(u"Estimated TPQ"), - required=False) - - def __init__(self, *args, **kwargs): - super(RecordFormInterpretation, self).__init__(*args, **kwargs) - self.fields['activity'].choices = models.ActivityType.get_types() - self.fields['activity'].help_text = models.ActivityType.get_help() - self.fields['identification'].choices = \ - models.IdentificationType.get_types() - self.fields['identification'].help_text = \ - models.IdentificationType.get_help() - -record_search_wizard = SearchWizard([ - ('general-record_search', RecordFormSelection)], - url_name='record_search',) - -OperationRecordFormSelection = get_form_selection( - 'OperationRecordFormSelection', _(u"Operation search"), 'operation_id', - models.Operation, OperationSelect, 'get-operation', - _(u"You should select an operation.")) - - -record_creation_wizard = RecordWizard([ - ('selec-record_creation', OperationRecordFormSelection), - ('general-record_creation', RecordFormGeneral), - ('datings-record_creation', DatingFormSet), - ('interpretation-record_creation', RecordFormInterpretation), - ('final-record_creation', FinalForm)], - url_name='record_creation',) - -record_modification_wizard = RecordModifWizard([ - ('selec-record_modification', RecordFormSelection), - ('general-record_modification', RecordFormGeneral), - ('datings-record_modification', DatingFormSet), - ('interpretation-record_modification', RecordFormInterpretation), - ('final-record_modification', FinalForm)], - url_name='record_modification',) - -class RecordDeletionWizard(DeletionWizard): - model = models.ContextRecord - fields = ['label', 'parcel', 'description', 'length', 'width', 'thickness', - 'depth', 'location', 'datings', 'units', 'has_furniture', - 'filling', 'interpretation', 'taq', 'taq_estimated', 'tpq', - 'tpq_estimated'] - -class RecordDeletionForm(FinalForm): - confirm_msg = " " - confirm_end_msg = _(u"Would you like to delete this context record?") - -record_deletion_wizard = RecordDeletionWizard([ - ('selec-record_deletion', RecordFormSelection), - ('final-record_deletion', RecordDeletionForm)], - url_name='record_deletion',) - -######################################### -# Source management for context records # -######################################### - -class RecordSourceWizard(SourceWizard): - model = models.ContextRecordSource - -SourceRecordFormSelection = get_form_selection( - 'SourceRecordFormSelection', _(u"Context record search"), - 'context_record', models.ContextRecord, RecordSelect, 'get-contextrecord', - _(u"You should select a context record.")) - -record_source_creation_wizard = RecordSourceWizard([ - ('selec-record_source_creation', SourceRecordFormSelection), - ('source-record_source_creation', SourceForm), - ('authors-record_source_creation', AuthorFormset), - ('final-record_source_creation', FinalForm)], - url_name='record_source_creation',) - -class RecordSourceSelect(SourceSelect): - context_record__parcel__town = get_town_field( - label=_(u"Town of the operation")) - context_record__operation__year = forms.IntegerField( - label=_(u"Year of the operation")) - context_record__datings__period = forms.ChoiceField( - label=_(u"Period of the context record"), choices=[]) - context_record__unit = forms.ChoiceField( - label=_(u"Unit type of the context record"), choices=[]) - - def __init__(self, *args, **kwargs): - super(RecordSourceSelect, self).__init__(*args, **kwargs) - self.fields['context_record__datings__period'].choices = \ - models.Period.get_types() - self.fields['context_record__datings__period'].help_text = \ - models.Period.get_help() - self.fields['context_record__unit'].choices = models.Unit.get_types() - self.fields['context_record__unit'].help_text = models.Unit.get_help() - - -RecordSourceFormSelection = get_form_selection( - 'RecordSourceFormSelection', _(u"Documentation search"), 'pk', - models.ContextRecordSource, RecordSourceSelect, 'get-contextrecordsource', - _(u"You should select a document.")) - -record_source_modification_wizard = RecordSourceWizard([ - ('selec-record_source_modification', RecordSourceFormSelection), - ('source-record_source_modification', SourceForm), - ('authors-record_source_modification', AuthorFormset), - ('final-record_source_modification', FinalForm)], - url_name='record_source_modification',) - -class RecordSourceDeletionWizard(DeletionWizard): - model = models.ContextRecordSource - fields = ['context_record', 'title', 'source_type', 'authors',] - -record_source_deletion_wizard = RecordSourceDeletionWizard([ - ('selec-record_source_deletion', RecordSourceFormSelection), - ('final-record_source_deletion', RecordDeletionForm)], - url_name='record_source_deletion',) diff --git a/ishtar_common/forms_files.py b/ishtar_common/forms_files.py deleted file mode 100644 index c4c460cee..000000000 --- a/ishtar_common/forms_files.py +++ /dev/null @@ -1,387 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 É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. - -""" -Files forms definitions -""" -import datetime - -from django import forms -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 _ - -from ishtar import settings - -import models -import widgets -from forms import Wizard, FinalForm, FormSet, ClosingWizard, \ - ClosingDateFormSelection, SearchWizard, formset_factory, get_now, \ - reverse_lazy -from forms_common import TownFormset, ParcelFormSet, \ - get_town_field, get_person_field -from forms_operations import OperationAdministrativeActWizard, \ -AdministrativeActOpeForm, AdministrativeActOpeFormSelection, \ -AdministrativeActDeletionWizard, FinalAdministrativeActDeleteForm, is_preventive - -class FileWizard(Wizard): - model = models.File - object_parcel_type = 'associated_file' - - def get_form(self, request, storage, step=None, data=None, files=None): - """ - Manage towns - """ - if data: - data = data.copy() - else: - data = {} - # manage the dynamic choice of towns - if not step: - step = self.determine_step(request, storage) - form = self.get_form_list(request, storage)[step] - town_form_key = 'towns-' + self.url_name - if step.startswith('parcels-') and hasattr(form, 'management_form') \ - and self.session_has_key(request, storage, town_form_key): - towns = [] - qdict = request.session[storage.prefix]['step_data'][town_form_key] - for k in qdict.keys(): - if k.endswith("town") and qdict[k]: - try: - town = models.Town.objects.get(pk=int(qdict[k])) - towns.append((town.pk, unicode(town))) - except (ObjectDoesNotExist, ValueError): - pass - data['TOWNS'] = sorted(towns, key=lambda x:x[1]) - form = super(FileWizard, self).get_form(request, storage, step, data, - files) - return form - - def get_extra_model(self, dct, request, storage, form_list): - dct = super(FileWizard, self).get_extra_model(dct, request, storage, - form_list) - if not dct['numeric_reference']: - current_ref = models.File.objects.filter(year=dct['year'] - ).aggregate(Max('numeric_reference'))["numeric_reference__max"] - dct['numeric_reference'] = current_ref and current_ref + 1 or 1 - return dct - - def done(self, request, storage, form_list, **kwargs): - ''' - Save parcels - ''' - r = super(FileWizard, self).done(request, storage, form_list, - return_object=True, **kwargs) - if type(r) not in (list, tuple) or len(r) != 2: - return r - obj, res = r - obj.parcels.clear() - for form in form_list: - if not hasattr(form, 'prefix') \ - or not form.prefix.startswith('parcels-') \ - or not hasattr(form, 'forms'): - continue - for frm in form.forms: - if not frm.is_valid(): - continue - dct = frm.cleaned_data.copy() - if 'parcel' in dct: - try: - parcel = models.Parcel.objects.get(pk=dct['parcel']) - setattr(parcel, self.object_parcel_type, obj) - parcel.save() - except (ValueError, ObjectDoesNotExist): - continue - continue - try: - dct['town'] = models.Town.objects.get(pk=int(dct['town'])) - except (ValueError, ObjectDoesNotExist): - continue - dct['associated_file'], dct['operation'] = None, None - dct[self.object_parcel_type] = obj - if 'DELETE' in dct: - dct.pop('DELETE') - parcel = models.Parcel.objects.filter(**dct).count() - if not parcel: - dct['history_modifier'] = request.user - parcel = models.Parcel(**dct) - parcel.save() - return res - -class FileSelect(forms.Form): - towns = get_town_field() - in_charge = get_person_field(label=_(u"Person in charge"), - person_type='sra_agent') - file_type = forms.ChoiceField(label=_("File type"), - choices=models.FileType.get_types()) - saisine_type = forms.ChoiceField(label=_("Saisine type"), choices=[]) - year = forms.IntegerField(label=_("Year")) - - def __init__(self, *args, **kwargs): - super(FileSelect, self).__init__(*args, **kwargs) - self.fields['saisine_type'].choices = models.SaisineType.get_types() - self.fields['saisine_type'].help_text = models.SaisineType.get_help() - -class FileFormSelection(forms.Form): - form_label = _("Archaeological file search") - associated_models = {'pk':models.File} - currents = {'pk':models.File} - pk = forms.IntegerField(label="", required=False, - widget=widgets.JQueryJqGrid(reverse_lazy('get-file'), - FileSelect(), models.File, source_full=reverse_lazy('get-file-full')), - validators=[models.valid_id(models.File)]) - - 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 a file.")) - return cleaned_data - -class FileFormGeneral(forms.Form): - form_label = _("General") - associated_models = {'in_charge':models.Person, - 'related_file':models.File, - 'file_type':models.FileType} - in_charge = forms.IntegerField(label=_("Person in charge"), - widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person', - args=[models.PersonType.objects.get(txt_idx='sra_agent').pk]), - associated_model=models.Person, new=True), - validators=[models.valid_id(models.Person)]) - year = forms.IntegerField(label=_("Year"), - initial=lambda:datetime.datetime.now().year, - validators=[validators.MinValueValidator(1900), - validators.MaxValueValidator(2100)]) - numeric_reference = forms.IntegerField(label=_("Numeric reference"), - widget=forms.HiddenInput, required=False) - internal_reference = forms.CharField(label=_(u"Internal reference"), - max_length=60, - validators=[models.is_unique(models.File, 'internal_reference')]) - creation_date = forms.DateField(label=_(u"Creation date"), - initial=get_now, widget=widgets.JQueryDate) - file_type = forms.ChoiceField(label=_("File type"), - choices=models.FileType.get_types()) - related_file = forms.IntegerField(label=_("Related file"), required=False, - widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-file'), - associated_model=models.File), - validators=[models.valid_id(models.File)]) - comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea, - required=False) - -class FileFormGeneralRO(FileFormGeneral): - year = forms.IntegerField(label=_(u"Year"), - widget=forms.TextInput(attrs={'readonly':True})) - numeric_reference = forms.IntegerField(label=_(u"Numeric reference"), - widget=forms.TextInput(attrs={'readonly':True})) - internal_reference = forms.CharField(label=_(u"Internal reference"), - widget=forms.TextInput(attrs={'readonly':True},)) - -class FileFormAddress(forms.Form): - form_label = _(u"Address") - associated_models = {'town':models.Town} - total_surface = forms.IntegerField(required=False, - widget=widgets.AreaWidget, - label=_(u"Total surface (m²)"), - validators=[validators.MinValueValidator(0), - validators.MaxValueValidator(999999999)]) - address = forms.CharField(label=_(u"Main address"), widget=forms.Textarea) - address_complement = forms.CharField(label=_(u"Main address - complement"), - required=False) - postal_code = forms.CharField(label=_(u"Main address - postal code"), - max_length=10) - -class FileFormPreventive(forms.Form): - form_label = _(u"Preventive informations") - associated_models = {'general_contractor':models.Person, - 'saisine_type':models.SaisineType, - 'permit_type':models.PermitType, - 'town_planning_service':models.Organization} - general_contractor = forms.IntegerField(label=_(u"General contractor"), - widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-person', - args=[models.PersonType.objects.get(txt_idx='general_contractor').pk]), - associated_model=models.Person, new=True), - validators=[models.valid_id(models.Person)]) - town_planning_service = forms.IntegerField(required=False, - label=_(u"Town planning service"), - widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-organization', - args=[models.OrganizationType.objects.get(txt_idx='planning_service').pk]), - associated_model=models.Organization, new=True), - validators=[models.valid_id(models.Organization)]) - permit_type = forms.ChoiceField(label=_(u"Permit type"), required=False, - choices=models.PermitType.get_types()) - permit_reference = forms.CharField(label=_(u"Permit reference"), - required=False, validators=[validators.MaxLengthValidator(60)]) - total_developed_surface = forms.IntegerField(widget=widgets.AreaWidget, - label=_(u"Total developed surface (m²)"), - required=False, validators=[validators.MinValueValidator(0), - validators.MaxValueValidator(999999999)]) - if settings.COUNTRY == 'fr': - saisine_type = forms.ChoiceField(label=_(u"Saisine type"), - choices=[]) - reception_date = forms.DateField(label=_(u"Reception date"), - initial=get_now, widget=widgets.JQueryDate) - def __init__(self, *args, **kwargs): - super(FileFormPreventive, self).__init__(*args, **kwargs) - self.fields['saisine_type'].choices = models.SaisineType.get_types() - self.fields['saisine_type'].help_text = models.SaisineType.get_help() - -file_search_wizard = SearchWizard([('general-file_search', FileFormSelection)], - url_name='file_search',) - -file_creation_wizard = FileWizard([ - ('general-file_creation', FileFormGeneral), - ('address-file_creation', FileFormAddress), - ('towns-file_creation', TownFormset), - ('parcels-file_creation', ParcelFormSet), - ('preventive-file_creation', FileFormPreventive), - ('final-file_creation', FinalForm)], - condition_list={ -'preventive-file_creation':is_preventive('general-file_creation', - models.FileType, type_key='file_type') - }, - url_name='file_creation',) - -class FileModificationWizard(FileWizard): - modification = True - -file_modification_wizard = FileModificationWizard([ - ('selec-file_modification', FileFormSelection), - ('general-file_modification', FileFormGeneralRO), - ('adress-file_modification', FileFormAddress), - ('towns-file_modification', TownFormset), - ('parcels-file_modification', ParcelFormSet), - ('preventive-file_modification', FileFormPreventive), - ('final-file_modification', FinalForm)], - condition_list={ -'preventive-file_modification':is_preventive('general-file_modification', - models.FileType, type_key='file_type') - }, - url_name='file_modification',) - -class FileClosingWizard(ClosingWizard): - model = models.File - fields = ['year', 'numeric_reference', 'internal_reference', - 'file_type', 'in_charge', 'general_contractor', 'creation_date', - 'reception_date', 'total_surface', 'total_developed_surface', - 'address', 'address_complement', 'postal_code', 'comment'] - if settings.COUNTRY == 'fr': - fields += ['saisine_type', 'reference_number'] - fields += ['towns'] - -class FinalFileClosingForm(FinalForm): - confirm_msg = " " - confirm_end_msg = _(u"Would you like to close this archaeological file?") - -file_closing_wizard = FileClosingWizard([ - ('selec-file_closing', FileFormSelection), - ('date-file_closing', ClosingDateFormSelection), - ('final-file_closing', FinalFileClosingForm)], - url_name='file_closing',) - -class FileDeletionWizard(FileClosingWizard): - def get_formated_datas(self, forms): - datas = super(FileDeletionWizard, self).get_formated_datas(forms) - datas.append((_("Associated operations"), [])) - for operation in models.Operation.objects.filter( - associated_file=self.current_obj).all(): - if operation.end_date: - datas[-1][1].append(('', unicode(operation))) - return datas - - def done(self, request, storage, form_list, **kwargs): - obj = self.get_current_object(request, storage) - for operation in models.Operation.objects.filter( - associated_file=obj).all(): - operation.delete() - obj.delete() - return render_to_response('wizard_done.html', {}, - context_instance=RequestContext(request)) - - -class FinalFileDeleteForm(FinalForm): - confirm_msg = " " - confirm_end_msg = _(u"Would you like to delete this archaelogical file ?") - -file_deletion_wizard = FileDeletionWizard([ - ('selec-file_deletion', FileFormSelection), - ('final-file_deletion', FinalFileDeleteForm)], - url_name='file_deletion',) - -class FileAdministrativeActWizard(OperationAdministrativeActWizard): - model = models.File - -class FileEditAdministrativeActWizard(FileAdministrativeActWizard): - model = models.AdministrativeAct - edit = True - def get_associated_item(self, request, storage, dct): - return self.get_current_object(request, storage).associated_file - -class AdministrativeActFileSelect(forms.Form): - associated_file__towns = get_town_field() - act_type = forms.ChoiceField(label=_("Act type"), choices=[]) - - def __init__(self, *args, **kwargs): - super(AdministrativeActFileSelect, self).__init__(*args, **kwargs) - self.fields['act_type'].choices = models.ActType.get_types( - dct={'intented_to':'F'}) - self.fields['act_type'].help_text = models.ActType.get_help( - dct={'intented_to':'F'}) - -class AdministrativeActFileFormSelection(AdministrativeActOpeFormSelection): - pk = forms.IntegerField(label="", required=False, - widget=widgets.JQueryJqGrid(reverse_lazy('get-administrativeactfile'), - AdministrativeActFileSelect(), models.AdministrativeAct, - table_cols='TABLE_COLS_FILE'), - validators=[models.valid_id(models.AdministrativeAct)]) - -class AdministrativeActFileForm(AdministrativeActOpeForm): - act_type = forms.ChoiceField(label=_(u"Act type"), choices=[]) - - def __init__(self, *args, **kwargs): - super(AdministrativeActFileForm, self).__init__(*args, **kwargs) - self.fields['act_type'].choices = models.ActType.get_types( - dct={'intented_to':'F'}) - self.fields['act_type'].help_text = models.ActType.get_help( - dct={'intented_to':'F'}) - -file_administrativeactfile_wizard = FileAdministrativeActWizard([ - ('selec-file_administrativeactfile', FileFormSelection), - ('administrativeact-file_administrativeactfile', AdministrativeActFileForm), - ('final-file_administrativeactfile', FinalForm)], - url_name='file_administrativeactfile',) - -file_administrativeactfile_modification_wizard = FileEditAdministrativeActWizard([ - ('selec-file_administrativeactfile_modification', - AdministrativeActFileFormSelection), - ('administrativeact-file_administrativeactfile_modification', - AdministrativeActFileForm), - ('final-file_administrativeactfile_modification', FinalForm)], - url_name='file_administrativeactfile_modification',) - -file_administrativeactfile_deletion_wizard = AdministrativeActDeletionWizard([ - ('selec-file_administrativeactfile_deletion', - AdministrativeActFileFormSelection), - ('final-file_administrativeactfile_deletion', - FinalAdministrativeActDeleteForm)], - url_name='file_administrativeactfile_deletion',) - diff --git a/ishtar_common/forms_items.py b/ishtar_common/forms_items.py deleted file mode 100644 index b763d94b2..000000000 --- a/ishtar_common/forms_items.py +++ /dev/null @@ -1,525 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 É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. - -""" -Items forms definitions -""" -import datetime - -from django import forms -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.utils.safestring import mark_safe -from django.db.models import Max -from django.utils.translation import ugettext_lazy as _ - -from ishtar import settings - -import models -import widgets -from forms import Wizard, FinalForm, FormSet, SearchWizard, DeletionWizard,\ - FloatField, formset_factory, get_now, get_form_selection, reverse_lazy -from forms_common import get_town_field, get_warehouse_field, SourceForm, \ - SourceWizard, SourceSelect, SourceDeletionForm, AuthorFormset -from forms_context_records import RecordFormSelection - -class ItemWizard(Wizard): - model = models.Item - - def get_current_contextrecord(self, request, storage): - step = storage.get_current_step() - if not step: - return - if step.endswith('_creation'): # a context record has been selected - main_form_key = 'selecrecord-' + self.url_name - try: - idx = int(self.session_get_value(request, storage, - main_form_key, 'pk')) - current_cr = models.ContextRecord.objects.get(pk=idx) - return current_cr - except(TypeError, ValueError, ObjectDoesNotExist): - pass - current_item = self.get_current_object(request, storage) - if current_item: - base_items = current_item.base_items.all() - if base_items: - return base_items[0].context_record - - def get_template_context(self, request, storage, form=None): - """ - Get the operation and context record "reminder" on top of wizard forms - """ - context = super(ItemWizard, self).get_template_context(request, - storage, form) - current_cr = self.get_current_contextrecord(request, storage) - if not current_cr: - return context - operation = current_cr.operation - items = [] - if hasattr(operation, 'code_patriarche') and operation.code_patriarche: - items.append(unicode(operation.code_patriarche)) - items.append("-".join((unicode(operation.year), - unicode(operation.operation_code)))) - reminder = unicode(_("Current operation: ")) + u" - ".join(items) - reminder += u"
" + unicode(_("Current context record: "))\ - + unicode(current_cr.label) - context['reminder'] = mark_safe(reminder) - return context - - def get_extra_model(self, dct, request, storage, form_list): - dct = super(ItemWizard, self).get_extra_model(dct, request, storage, - form_list) - dct['order'] = 1 - if 'pk' in dct and type(dct['pk']) == models.ContextRecord: - dct['base_items__context_record'] = dct.pop('pk') - return dct - -class ItemForm(forms.Form): - form_label = _("Item") - base_model = 'base_items' - associated_models = {'material_type':models.MaterialType,} - label = forms.CharField(label=_(u"ID"), - validators=[validators.MaxLengthValidator(60)]) - description = forms.CharField(label=_("Description"), - widget=forms.Textarea) - base_items__is_isolated = forms.NullBooleanField(label=_(u"Is isolated?"), - required=False) - material_type = forms.ChoiceField(label=_("Material type"), - choices=models.MaterialType.get_types()) - volume = FloatField(label=_(u"Volume (l)"), required=False) - weight = FloatField(label=_(u"Weight (g)"), required=False) - item_number = forms.IntegerField(label=_(u"Item number"), required=False) - -class DateForm(forms.Form): - form_label = _("Dating") - base_model = 'dating' - associated_models = {'dating__dating_type':models.DatingType, - 'dating__quality':models.DatingQuality, - 'dating__period':models.Period} - dating__period = forms.ChoiceField(label=_("Period"), - choices=models.Period.get_types()) - dating__start_date = forms.IntegerField(label=_(u"Start date"), - required=False) - dating__end_date = forms.IntegerField(label=_(u"End date"), required=False) - dating__quality = forms.ChoiceField(label=_("Quality"), required=False, - choices=models.DatingQuality.get_types()) - dating__dating_type = forms.ChoiceField(label=_("Dating type"), - required=False, choices=[]) - - def __init__(self, *args, **kwargs): - super(DateForm, self).__init__(*args, **kwargs) - self.fields['dating__dating_type'].choices = models.DatingType.get_types() - self.fields['dating__dating_type'].help_text = models.DatingType.get_help() - -item_creation_wizard = ItemWizard([ - ('selecrecord-item_creation', RecordFormSelection), - ('item-item_creation', ItemForm), - ('dating-item_creation', DateForm), - ('final-item_creation', FinalForm)], - url_name='item_creation',) - -class ItemSelect(forms.Form): - base_items__context_record__parcel__town = get_town_field() - base_items__context_record__operation__year = forms.IntegerField( - label=_(u"Year")) - base_items__context_record__operation__code_patriarche = \ - forms.IntegerField(label=_(u"Code PATRIARCHE")) - dating__period = forms.ChoiceField(label=_(u"Period"), choices=[]) - # TODO search by warehouse - material_type = forms.ChoiceField(label=_(u"Material type"), choices=[]) - base_items__item__description = forms.CharField(label=_(u"Description")) - base_items__is_isolated = forms.NullBooleanField(label=_(u"Is isolated?")) - - def __init__(self, *args, **kwargs): - super(ItemSelect, self).__init__(*args, **kwargs) - self.fields['dating__period'].choices = \ - models.Period.get_types() - self.fields['dating__period'].help_text = \ - models.Period.get_help() - self.fields['material_type'].choices = \ - models.MaterialType.get_types() - self.fields['material_type'].help_text = \ - models.MaterialType.get_help() - -class ItemFormSelection(forms.Form): - form_label = _("Item search") - associated_models = {'pk':models.Item} - currents = {'pk':models.Item} - pk = forms.IntegerField(label="", required=False, - widget=widgets.JQueryJqGrid(reverse_lazy('get-item'), - ItemSelect(), models.Item, source_full=reverse_lazy('get-item-full')), - validators=[models.valid_id(models.Item)]) - -item_search_wizard = SearchWizard([ - ('general-item_search', ItemFormSelection)], - url_name='item_search',) - -class ItemModificationWizard(ItemWizard): - modification = True - -item_modification_wizard = ItemModificationWizard([ - ('selec-item_modification', ItemFormSelection), - ('item-item_modification', ItemForm), - ('dating-item_modification', DateForm), - ('final-item_modification', FinalForm)], - url_name='item_modification',) - -class TreatmentWizard(Wizard): - model = models.Treatment - -class BaseTreatmentForm(forms.Form): - form_label = _(u"Base treatment") - associated_models = {'treatment_type':models.TreatmentType, - 'person':models.Person, - 'location':models.Warehouse} - treatment_type = forms.ChoiceField(label=_(u"Treatment type"), choices=[]) - person = forms.IntegerField(label=_(u"Person"), - widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person'), - associated_model=models.Person, new=True), - validators=[models.valid_id(models.Person)]) - location = forms.IntegerField(label=_(u"Location"), - widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-warehouse'), associated_model=models.Warehouse, - new=True), - validators=[models.valid_id(models.Warehouse)]) - description = forms.CharField(label=_(u"Description"), - widget=forms.Textarea, required=False) - start_date = forms.DateField(label=_(u"Start date"), required=False, - widget=widgets.JQueryDate) - end_date = forms.DateField(label=_(u"End date"), required=False, - widget=widgets.JQueryDate) - - def __init__(self, *args, **kwargs): - super(BaseTreatmentForm, self).__init__(*args, **kwargs) - self.fields['treatment_type'].choices = models.TreatmentType.get_types( - exclude=['packaging']) - self.fields['treatment_type'].help_text = models.TreatmentType.get_help( - exclude=['packaging']) - -class ItemMultipleFormSelection(forms.Form): - form_label = _(u"Upstream items") - associated_models = {'items':models.Item} - associated_labels = {'items':_(u"Items")} - items = forms.CharField(label="", required=False, - widget=widgets.JQueryJqGrid(reverse_lazy('get-item'), - ItemSelect(), models.Item, multiple=True, multiple_cols=[2, 3, 4]), - validators=[models.valid_ids(models.Item)]) - - def clean(self): - if not 'items' in self.cleaned_data or not self.cleaned_data['items']: - raise forms.ValidationError(_(u"You should at least select one " - u"archaeological item.")) - return self.cleaned_data - -class ContainerForm(forms.Form): - form_label = _(u"Container") - reference = forms.CharField(label=_(u"Reference")) - container_type = forms.ChoiceField(label=_(u"Container type"), choices=[]) - location = forms.IntegerField(label=_(u"Warehouse"), - widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-warehouse'), associated_model=models.Warehouse, - new=True), - validators=[models.valid_id(models.Warehouse)]) - comment = forms.CharField(label=_(u"Comment"), - widget=forms.Textarea, required=False) - - def __init__(self, *args, **kwargs): - super(ContainerForm, self).__init__(*args, **kwargs) - self.fields['container_type'].choices = \ - models.ContainerType.get_types() - self.fields['container_type'].help_text = \ - models.ContainerType.get_help() - - def save(self, user): - dct = self.cleaned_data - dct['history_modifier'] = user - dct['container_type'] = models.ContainerType.objects.get( - pk=dct['container_type']) - dct['location'] = models.Warehouse.objects.get(pk=dct['location']) - new_item = models.Container(**dct) - new_item.save() - return new_item - -def check_treatment(form_name, type_key, type_list=[], not_type_list=[]): - type_list = [models.TreatmentType.objects.get(txt_idx=tpe).pk - for tpe in type_list] - not_type_list = [models.TreatmentType.objects.get(txt_idx=tpe).pk - for tpe in not_type_list] - 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: - type = int(request.session[storage.prefix]['step_data']\ - [form_name][form_name+'-'+type_key]) - return (not type_list or type in type_list) \ - and type not in not_type_list - except ValueError: - return False - return func - -class ResultItemForm(forms.Form): - form_label = _(u"Resulting item") - associated_models = {'material_type':models.MaterialType} - label = forms.CharField(label=_(u"ID"), - validators=[validators.MaxLengthValidator(60)]) - description = forms.CharField(label=_(u"Precise description"), - widget=forms.Textarea) - material_type = forms.ChoiceField(label=_(u"Material type"), - choices=models.MaterialType.get_types()) - volume = forms.IntegerField(label=_(u"Volume (l)")) - weight = forms.IntegerField(label=_(u"Weight (g)")) - item_number = forms.IntegerField(label=_(u"Item number")) - -ResultItemFormSet = formset_factory(ResultItemForm, can_delete=True, - formset=FormSet) -ResultItemFormSet.form_label = _(u"Resulting items") - -class UpstreamItemFormSelection(ItemFormSelection): - form_label = _(u"Upstream item") - -treatment_creation_wizard = TreatmentWizard([ - ('basetreatment-treatment_creation', BaseTreatmentForm), - ('selecitem-treatment_creation', UpstreamItemFormSelection), - ('multiselecitems-treatment_creation', ItemMultipleFormSelection), - ('container-treatment_creation', ContainerForm), - ('resultitem-treatment_creation', ResultItemForm), - ('resultitems-treatment_creation', ResultItemFormSet), - ('final-treatment_creation', FinalForm)], - condition_list={ -'selecitem-treatment_creation': - check_treatment('basetreatment-treatment_creation', 'treatment_type', - not_type_list=['physical_grouping', 'packaging']), -'multiselecitems-treatment_creation': - check_treatment('basetreatment-treatment_creation', 'treatment_type', - ['physical_grouping', 'packaging']), -'resultitems-treatment_creation': - check_treatment('basetreatment-treatment_creation', 'treatment_type', - ['split']), -'resultitem-treatment_creation': - check_treatment('basetreatment-treatment_creation', 'treatment_type', - not_type_list=['split']), -'container-treatment_creation': - check_treatment('basetreatment-treatment_creation', 'treatment_type', - ['packaging']), - }, - url_name='treatment_creation',) - -############# -# Packaging # -############# - -class PackagingWizard(TreatmentWizard): - def save_model(self, dct, m2m, whole_associated_models, request, storage, - form_list, return_object): - dct = self.get_extra_model(dct, request, storage, form_list) - obj = self.get_current_saved_object(request, storage) - dct['location'] = dct['container'].location - items = dct.pop('items') - treatment = models.Treatment(**dct) - treatment.save() - if not hasattr(items, '__iter__'): - items = [items] - for item in items: - new = item.duplicate(request.user) - item.downstream_treatment = treatment - item.save() - new.upstream_treatment = treatment - new.container = dct['container'] - new.save() - res = render_to_response('wizard_done.html', {}, - context_instance=RequestContext(request)) - return return_object and (obj, res) or res - -class ContainerSelect(forms.Form): - location = get_warehouse_field() - container_type = forms.ChoiceField(label=_(u"Container type"), choices=[]) - reference = forms.CharField(label=_(u"Reference")) - - def __init__(self, *args, **kwargs): - super(ContainerSelect, self).__init__(*args, **kwargs) - self.fields['container_type'].choices = \ - models.ContainerType.get_types() - self.fields['container_type'].help_text = \ - models.ContainerType.get_help() - -ContainerFormSelection = get_form_selection( - 'ContainerFormSelection', _(u"Container search"), 'container', - models.Container, ContainerSelect, 'get-container', - _(u"You should select a container."), new=True, - new_message=_(u"Add a new container")) - -class BasePackagingForm(forms.Form): - form_label = _(u"Packaging") - associated_models = {'treatment_type':models.TreatmentType, - 'person':models.Person, - 'location':models.Warehouse} - treatment_type = forms.IntegerField(label="", widget=forms.HiddenInput) - person = forms.IntegerField(label=_(u"Packager"), - widget=widgets.JQueryAutoComplete(reverse_lazy('autocomplete-person'), - associated_model=models.Person, new=True), - validators=[models.valid_id(models.Person)]) - start_date = forms.DateField(label=_(u"Date"), required=False, - widget=widgets.JQueryDate) - - def __init__(self, *args, **kwargs): - super(BasePackagingForm, self).__init__(*args, **kwargs) - self.fields['treatment_type'].initial = \ - models.TreatmentType.objects.get(txt_idx='packaging').pk - -class ItemPackagingFormSelection(ItemMultipleFormSelection): - form_label = _(u"Packaged items") - -warehouse_packaging_wizard = PackagingWizard([ - ('seleccontainer-packaging', ContainerFormSelection), - ('base-packaging', BasePackagingForm), - ('multiselecitems-packaging', ItemPackagingFormSelection), - ('final-packaging', FinalForm)], - url_name='warehouse_packaging',) - -""" -warehouse_packaging_wizard = ItemSourceWizard([ - ('selec-warehouse_packaging', ItemsSelection), - ('final-warehouse_packaging', FinalForm)], - url_name='warehouse_packaging',) -""" -############################################# -# Source management for archaelogical items # -############################################# - -class ItemSourceWizard(SourceWizard): - model = models.ItemSource - -SourceItemFormSelection = get_form_selection( - 'SourceItemFormSelection', _(u"Archaelogical item search"), 'item', - models.Item, ItemSelect, 'get-item', - _(u"You should select an archaelogical item.")) - -item_source_creation_wizard = ItemSourceWizard([ - ('selec-item_source_creation', SourceItemFormSelection), - ('source-item_source_creation', SourceForm), - ('authors-item_source_creation', AuthorFormset), - ('final-item_source_creation', FinalForm)], - url_name='item_source_creation',) - -class ItemSourceSelect(SourceSelect): - item__base_items__context_record__operation__year = forms.IntegerField( - label=_(u"Year of the operation")) - item__dating__period = forms.ChoiceField( - label=_(u"Period of the archaelogical item"), - choices=[]) - item__material_type = forms.ChoiceField( - label=_("Material type of the archaelogical item"), - choices=models.MaterialType.get_types()) - item__description = forms.CharField( - label=_(u"Description of the archaelogical item")) - - def __init__(self, *args, **kwargs): - super(ItemSourceSelect, self).__init__(*args, **kwargs) - self.fields['item__dating__period'].choices = \ - models.Period.get_types() - self.fields['item__dating__period'].help_text = \ - models.Period.get_help() - self.fields['item__material_type'].choices = \ - models.MaterialType.get_types() - self.fields['item__material_type'].help_text = \ - models.MaterialType.get_help() - -ItemSourceFormSelection = get_form_selection( - 'ItemSourceFormSelection', _(u"Documentation search"), 'pk', - models.ItemSource, ItemSourceSelect, 'get-itemsource', - _(u"You should select a document.")) - -item_source_modification_wizard = ItemSourceWizard([ - ('selec-item_source_modification', ItemSourceFormSelection), - ('source-item_source_modification', SourceForm), - ('authors-item_source_modification', AuthorFormset), - ('final-item_source_modification', FinalForm)], - url_name='item_source_modification',) - -class ItemSourceDeletionWizard(DeletionWizard): - model = models.ItemSource - fields = ['item', 'title', 'source_type', 'authors',] - -item_source_deletion_wizard = ItemSourceDeletionWizard([ - ('selec-item_source_deletion', ItemSourceFormSelection), - ('final-item_source_deletion', SourceDeletionForm)], - url_name='item_source_deletion',) - -""" - -#################################### -# Source management for treatments # -#################################### - -class TreatmentSourceWizard(SourceWizard): - model = models.TreamentSource - -SourceTreatementFormSelection = get_form_selection( - 'SourceTreatmentFormSelection', _(u"Treatment search"), 'operation', - models.Treatment, TreatmentSelect, 'get-treatment', - _(u"You should select a treatment.")) - -treatment_source_creation_wizard = TreatmentSourceWizard([ - ('selec-treatment_source_creation', SourceTreatmentFormSelection), - ('source-treatment_source_creation', SourceForm), - ('authors-treatment_source_creation', AuthorFormset), - ('final-treatment_source_creation', FinalForm)], - url_name='treatment_source_creation',) - -class TreatmentSourceSelect(SourceSelect): - operation__towns = get_town_field(label=_(u"Operation's town")) - treatment__treatment_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', SourceForm), - ('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',) -""" diff --git a/ishtar_common/forms_operations.py b/ishtar_common/forms_operations.py deleted file mode 100644 index e163b5869..000000000 --- a/ishtar_common/forms_operations.py +++ /dev/null @@ -1,754 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 É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. - -""" -Operations forms definitions -""" -import datetime - -from django import forms -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 _ - -from ishtar import settings - -import models -import widgets -from forms import Wizard, FinalForm, FormSet, SearchWizard, ClosingWizard, \ - ClosingDateFormSelection, DeletionWizard, formset_factory, get_now, \ - reverse_lazy, get_form_selection -from forms_common import TownForm, TownFormSet, TownFormset, ParcelFormSet, \ - ParcelForm, 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 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 \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/ishtar_common/menus.py b/ishtar_common/menus.py index c55e2a328..3737846b9 100644 --- a/ishtar_common/menus.py +++ b/ishtar_common/menus.py @@ -47,127 +47,31 @@ _extra_menus = [( ] )] +# collect menu from INSTALLED_APPS for app in settings.INSTALLED_APPS: + print app + if app == 'ishtar_common': + continue mod = __import__(app, fromlist=['ishtar_menu']) if hasattr(mod, 'ishtar_menu'): menu = getattr(mod, 'ishtar_menu') - _extra_menus.append((menu.ORDER, menu.MENU_SECTIONS)) + _extra_menus += menu.MENU_SECTIONS -_section_items = [] -for order, menu in sorted(_extra_menus, key=lambda x:x[0]): - _section_items += menu +# sort +__section_items = [menu for order, menu in sorted(_extra_menus, + key=lambda x:x[0])] + +# regroup menus +_section_items, __keys = [], [] +for section_item in _section_items: + if section_item.idx not in __keys: + __keys.append(section_item.idx) + _section_items.append(section_item) + continue + _section_items[_section_items.index(section_item.idx)].childs.append( + section_item.childs) """ - SectionItem('record_management', _(u"Context record"), - childs=[ - MenuItem('record_search', _(u"Search"), - model=models.ContextRecord, - access_controls=['view_contextrecord', - 'view_own_contextrecord']), - MenuItem('record_creation', _(u"Creation"), - model=models.ContextRecord, - access_controls=['add_contextrecord', - 'add_own_contextrecord']), - MenuItem('record_modification', _(u"Modification"), - model=models.ContextRecord, - access_controls=['change_contextrecord', - 'change_own_contextrecord']), - MenuItem('record_deletion', _(u"Deletion"), - model=models.ContextRecord, - access_controls=['delete_contextrecord', - 'delete_own_contextrecord']), - ]), - SectionItem('item_management', _(u"Item"), - childs=[ - MenuItem('item_search', _(u"Search"), - model=models.Item, - access_controls=['view_item', - 'view_own_item']), - MenuItem('item_creation', _(u"Creation"), - model=models.Item, - access_controls=['add_item', - 'add_own_item']), - MenuItem('item_modification', _(u"Modification"), - model=models.Item, - access_controls=['change_item', - 'change_own_item']), - MenuItem('warehouse_packaging', _(u"Packaging"), - model=models.Treatment, - access_controls=['add_treatment', 'add_own_treatment']), - #MenuItem('treatment_creation', _(u"Add a treatment"), - # model=models.Treatment, - # access_controls=['add_treatment', - # 'add_own_treatment']), - ]), - SectionItem('source_management', _(u"Documentation"), - childs=[ - SectionItem('admin_add_sources', _(u"Add"), - childs=[ - MenuItem('operation_source_creation', - _(u"Related to an operation"), - model=models.OperationSource, - access_controls=['change_operation', - 'change_own_operation']), - MenuItem('record_source_creation', - _(u"Related to a context record"), - model=models.ContextRecordSource, - access_controls=['change_contextrecord', - 'change_own_contextrecord']), - MenuItem('item_source_creation', - _(u"Related to an archaelogical item"), - model=models.ItemSource, - access_controls=['change_item', - 'change_own_item']), - ]), - SectionItem('admin_mod_sources', _(u"Modify"), - childs=[ - MenuItem('operation_source_modification', - _(u"Related to an operation"), - model=models.OperationSource, - access_controls=['change_operation', - 'change_own_operation']), - MenuItem('record_source_modification', - _(u"Related to a context record"), - model=models.ContextRecordSource, - access_controls=['change_contextrecord', - 'change_own_contextrecord']), - MenuItem('item_source_modification', - _(u"Related to an archaelogical item"), - model=models.ItemSource, - access_controls=['change_item', - 'change_own_item']), - ]), - SectionItem('admin_del_sources', _(u"Deletion"), - childs=[ - MenuItem('operation_source_deletion', - _(u"Related to an operation"), - model=models.OperationSource, - access_controls=['change_operation', - 'change_own_operation']), - MenuItem('record_source_deletion', - _(u"Related to a context record"), - model=models.ContextRecordSource, - access_controls=['change_contextrecord', - 'change_own_contextrecord']), - MenuItem('item_source_deletion', - _(u"Related to an archaelogical item"), - model=models.ItemSource, - access_controls=['change_item', - 'change_own_item']), - ]), - ]), - #SectionItem('warehouse', _(u"Warehouse"), - # childs=[ - # MenuItem('warehouse_inventory', _(u"Inventory"), - # model=models.Warehouse, - # access_controls=['change_warehouse',]), - # MenuItem('warehouse_recording', _(u"Recording"), - # model=models.Treatment, - # access_controls=['add_treatment', 'add_own_treatment']), - # MenuItem('warehouse_lend', _(u"Lending"), - # model=models.Treatment, - # access_controls=['add_treatment', 'add_own_treatment']), - # ]), SectionItem('dashboard', _(u"Dashboard"), childs=[ MenuItem('dashboard_main', _(u"General informations"), diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 4406a2b86..b4bf2cd57 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -398,570 +398,6 @@ class UserDashboard: self.types = types.annotate(number=Count('pk'))\ .order_by('person__person_type') -class FileDashboard: - def __init__(self): - main_dashboard = Dashboard(File) - - self.total_number = main_dashboard.total_number - - types = File.objects.values('file_type', 'file_type__label') - self.types = types.annotate(number=Count('pk')).order_by('file_type') - - by_year = File.objects.extra( - {'date':"date_trunc('year', creation_date)"}) - self.by_year = by_year.values('date')\ - .annotate(number=Count('pk')).order_by('-date') - - now = datetime.date.today() - limit = datetime.date(now.year, now.month, 1) - datetime.timedelta(365) - by_month = File.objects.filter(creation_date__gt=limit).extra( - {'date':"date_trunc('month', creation_date)"}) - self.by_month = by_month.values('date')\ - .annotate(number=Count('pk')).order_by('-date') - - # research - self.research = {} - prog_type = FileType.objects.get(txt_idx='prog') - researchs = File.objects.filter(file_type=prog_type) - self.research['total_number'] = researchs.count() - by_year = researchs.extra({'date':"date_trunc('year', creation_date)"}) - self.research['by_year'] = by_year.values('date')\ - .annotate(number=Count('pk'))\ - .order_by('-date') - by_month = researchs.filter(creation_date__gt=limit)\ - .extra({'date':"date_trunc('month', creation_date)"}) - self.research['by_month'] = by_month.values('date')\ - .annotate(number=Count('pk'))\ - .order_by('-date') - - self.research['by_dpt'] = FileByDepartment.objects\ - .filter(file__file_type=prog_type, - department__isnull=False)\ - .values('department__label')\ - .annotate(number=Count('file'))\ - .order_by('department__label') - FileTown = File.towns.through - self.research['towns'] = FileTown.objects\ - .filter(file__file_type=prog_type)\ - .values('town__name')\ - .annotate(number=Count('file'))\ - .order_by('-number','town__name')[:10] - - # rescue - rescue_type = FileType.objects.get(txt_idx='preventive') - rescues = File.objects.filter(file_type=rescue_type) - self.rescue = {} - self.rescue['total_number'] = rescues.count() - self.rescue['saisine'] = rescues.values('saisine_type__label')\ - .annotate(number=Count('pk'))\ - .order_by('saisine_type__label') - self.rescue['administrative_act'] = AdministrativeAct.objects\ - .filter(associated_file__isnull=False)\ - .values('act_type__label')\ - .annotate(number=Count('pk'))\ - .order_by('act_type__pk') - - by_year = rescues.extra({'date':"date_trunc('year', creation_date)"}) - self.rescue['by_year'] = by_year.values('date')\ - .annotate(number=Count('pk'))\ - .order_by('-date') - by_month = rescues.filter(creation_date__gt=limit)\ - .extra({'date':"date_trunc('month', creation_date)"}) - self.rescue['by_month'] = by_month.values('date')\ - .annotate(number=Count('pk'))\ - .order_by('-date') - - self.rescue['by_dpt'] = FileByDepartment.objects\ - .filter(file__file_type=rescue_type, - department__isnull=False)\ - .values('department__label')\ - .annotate(number=Count('file'))\ - .order_by('department__label') - self.rescue['towns'] = FileTown.objects\ - .filter(file__file_type=rescue_type)\ - .values('town__name')\ - .annotate(number=Count('file'))\ - .order_by('-number','town__name')[:10] - - self.rescue['with_associated_operation'] = rescues\ - .filter(operations__isnull=False).count() - - self.rescue['with_associated_operation_percent'] = round( - float(self.rescue['with_associated_operation'])\ - /self.rescue['total_number']*100, 2) - - by_year_operationnal = rescues.filter(operations__isnull=False)\ - .extra({'date':"date_trunc('year', creation_date)"}) - by_year_operationnal = by_year_operationnal.values('date')\ - .annotate(number=Count('pk'))\ - .order_by('-date') - percents, idx = [], 0 - for dct in self.rescue['by_year']: - if idx > len(by_year_operationnal): - break - if by_year_operationnal[idx]['date'] != dct['date'] or\ - not dct['number']: - continue - val = round(float(by_year_operationnal[idx]['number'])/\ - dct['number']*100, 2) - percents.append({'date':dct['date'], 'number':val}) - self.rescue['operational_by_year'] = percents - - self.rescue['surface_by_town'] = FileTown.objects\ - .filter(file__file_type=rescue_type)\ - .values('town__name')\ - .annotate(number=Sum('file__total_surface'))\ - .order_by('-number','town__name')[:10] - self.rescue['surface_by_dpt'] = FileByDepartment.objects\ - .filter(file__file_type=rescue_type, - department__isnull=False)\ - .values('department__label')\ - .annotate(number=Sum('file__total_surface'))\ - .order_by('department__label') - -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'])) - class Dashboard: def __init__(self, model): self.model = model @@ -973,7 +409,7 @@ class Dashboard: last_ids = history_model.objects.values('id')\ .annotate(hd=Max('history_date')) last_ids = last_ids.filter(history_type=modif_type) - if self.model == Item: + if self.model == Find: last_ids = last_ids.filter(downstream_treatment_id__isnull=True) if modif_type == '+': last_ids = last_ids.filter(upstream_treatment_id__isnull=True) @@ -1252,5 +688,3 @@ class Town(models.Model): if settings.COUNTRY == "fr": return u"%s (%s)" % (self.name, self.numero_insee) return self.name - - diff --git a/ishtar_common/templates/sheet_contextrecord.html b/ishtar_common/templates/sheet_contextrecord.html index c2b94bafe..806b06288 100644 --- a/ishtar_common/templates/sheet_contextrecord.html +++ b/ishtar_common/templates/sheet_contextrecord.html @@ -109,7 +109,7 @@ {% trans "Parcel" %}   - {% for find in item.base_items.all %} + {% for find in item.base_finds.all %} {{ find.full_label }} {# Displayed as (Patriarche operation code)-(Record unit label)-(Finds label). #} @@ -119,10 +119,10 @@ {# or displayed as (Year)-(index)-(Record unit label)-(material code)-(Finds label indexed by material type) #} {{find.context_record}} - {{ find.get_last_item.dating}}{# TODO .all|join:", " ? #} - {{ find.get_last_item.description }} - {{ find.get_last_item.weight }} - {{ find.get_last_item.item_number }} + {{ find.get_last_find.dating}}{# TODO .all|join:", " ? #} + {{ find.get_last_find.description }} + {{ find.get_last_find.weight }} + {{ find.get_last_find.item_number }} {{ item.context_record.parcel.short_label }} {% trans "Details" %} {#{%trans "Details"%}#} diff --git a/ishtar_common/templates/sheet_operation.html b/ishtar_common/templates/sheet_operation.html index aa571d20c..edceca989 100644 --- a/ishtar_common/templates/sheet_operation.html +++ b/ishtar_common/templates/sheet_operation.html @@ -156,17 +156,17 @@   {% for context_record in item.context_record.all %} - {% for find in context_record.base_items.all %} + {% for find in context_record.base_finds.all %} {{ find.full_label }} {# Displayed as (Patriarche operation code)-(Record unit label)-(Finds label). #} {# or displayed as (Year)-(index)-(Record unit label)-(Finds label). #} {{ find.material_type_label }} {{find.context_record.label}} - {{ find.get_last_item.dating}}{# TODO .all|join:", " ? #} - {{ find.get_last_item.description }} - {{ find.get_last_item.weight }} - {{ find.get_last_item.item_number }} + {{ find.get_last_find.dating}}{# TODO .all|join:", " ? #} + {{ find.get_last_find.description }} + {{ find.get_last_find.weight }} + {{ find.get_last_find.item_number }} {{ context_record.parcel.short_label }} {% trans "Details" %} {#{%trans "Details"%}#} diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index f6636255e..40ef4a1d2 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 Étienne Loks +# Copyright (C) 2010-2012 É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 @@ -37,97 +37,6 @@ urlpatterns = patterns('', url(r'account_management/(?P.+)?$', views.account_management_wizard, name='account_management'), ) -""" - # Archaelogical files - url(r'file_search/(?P.+)$', - ishtar_forms.file_search_wizard, name='file_search'), - url(r'file_creation/(?P.+)$', - ishtar_forms.file_creation_wizard, name='file_creation'), - url(r'file_modification/(?P.+)$', - ishtar_forms.file_modification_wizard, name='file_modification'), - url(r'file_closing/(?P.+)$', - ishtar_forms.file_closing_wizard, name='file_closing'), - url(r'file_deletion/(?P.+)$', - ishtar_forms.file_deletion_wizard, name='file_deletion'), - url(r'file_administrativeactfile/(?P.+)$', - ishtar_forms.file_administrativeactfile_wizard, - name='file_administrativeactfile'), - url(r'file_administrativeactfile_modification/(?P.+)$', - ishtar_forms.file_administrativeactfile_modification_wizard, - name='file_administrativeactfile_modification'), - url(r'file_administrativeactfile_deletion/(?P.+)$', - ishtar_forms.file_administrativeactfile_deletion_wizard, - name='file_administrativeactfile_deletion'), - # Operations - url(r'operation_search/(?P.+)$', - ishtar_forms.operation_search_wizard, name='operation_search'), - url(r'operation_creation/(?P.+)$', - ishtar_forms.operation_creation_wizard, name='operation_creation'), - url(r'operation_modification/(?P.+)$', - ishtar_forms.operation_modification_wizard, - name='operation_modification'), - url(r'operation_closing/(?P.+)$', - ishtar_forms.operation_closing_wizard, name='operation_closing'), - url(r'operation_deletion/(?P.+)$', - ishtar_forms.operation_deletion_wizard, name='operation_deletion'), - url(r'operation_administrativeactop/(?P.+)$', - ishtar_forms.operation_administrativeactop_wizard, - name='operation_administrativeactop'), - url(r'operation_administrativeactop_modification/(?P.+)$', - ishtar_forms.operation_administrativeactop_modification_wizard, - name='operation_administrativeactop_modification'), - url(r'operation_administrativeactop_deletion/(?P.+)$', - ishtar_forms.operation_administrativeactop_deletion_wizard, - name='operation_administrativeactop_deletion'), - url(r'operation_source_creation/(?P.+)$', - ishtar_forms.operation_source_creation_wizard, - name='operation_source_creation'), - url(r'operation_source_modification/(?P.+)$', - ishtar_forms.operation_source_modification_wizard, - name='operation_source_modification'), - url(r'operation_source_deletion/(?P.+)$', - ishtar_forms.operation_source_deletion_wizard, - name='operation_source_deletion'), - # Context records - url(r'record_search/(?P.+)$', - ishtar_forms.record_search_wizard, name='record_search'), - url(r'record_creation/(?P.+)$', - ishtar_forms.record_creation_wizard, name='record_creation'), - url(r'record_modification/(?P.+)$', - ishtar_forms.record_modification_wizard, name='record_modification'), - url(r'record_deletion/(?P.+)$', - ishtar_forms.record_deletion_wizard, name='record_deletion'), - url(r'record_source_creation/(?P.+)$', - ishtar_forms.record_source_creation_wizard, - name='record_source_creation'), - url(r'record_source_modification/(?P.+)$', - ishtar_forms.record_source_modification_wizard, - name='record_source_modification'), - url(r'record_source_deletion/(?P.+)$', - ishtar_forms.record_source_deletion_wizard, - name='record_source_deletion'), - # Finds - url(r'item_search/(?P.+)$', - ishtar_forms.item_search_wizard, name='item_search'), - url(r'item_creation/(?P.+)$', - ishtar_forms.item_creation_wizard, name='item_creation'), - url(r'item_modification/(?P.+)$', - ishtar_forms.item_modification_wizard, name='item_modification'), - url(r'item_source_creation/(?P.+)$', - ishtar_forms.item_source_creation_wizard, - name='item_source_creation'), - url(r'item_source_modification/(?P.+)$', - ishtar_forms.item_source_modification_wizard, - name='item_source_modification'), - url(r'item_source_deletion/(?P.+)$', - ishtar_forms.item_source_deletion_wizard, - name='item_source_deletion'), - # Treatments - url(r'treatment_creation/(?P.+)$', - ishtar_forms.treatment_creation_wizard, name='treatment_creation'), - url(r'warehouse_packaging/(?P.+)$', - ishtar_forms.warehouse_packaging_wizard, name='warehouse_packaging'), - )""" for section in menu.childs: for menu_item in section.childs: if hasattr(menu_item, 'childs'): @@ -142,6 +51,8 @@ urlpatterns += patterns('ishtar_common.views', # General url(r'(?P' + actions + r')/$', 'action', name='action'), + url(r'update-current-item/$', 'update_current_item', + name='update-current-item'), url(r'new-person/(?P.+)?/$', 'new_person', name='new-person'), url(r'autocomplete-person/([0-9_]+)?$', 'autocomplete_person', @@ -156,66 +67,4 @@ urlpatterns += patterns('ishtar_common.views', 'new_organization', name='new-organization'), url(r'autocomplete-organization/([0-9_]+)?$', 'autocomplete_organization', name='autocomplete-organization'), - url(r'new-warehouse/(?P.+)?/$', - 'new_warehouse', name='new-warehouse'), - url(r'autocomplete-warehouse/$', 'autocomplete_warehouse', - name='autocomplete-warehouse'), - # Archaelogical files - url(r'autocomplete-file/$', 'autocomplete_file', - name='autocomplete-file'), - url(r'get-file/(?P.+)?$', 'get_file', - name='get-file'), - url(r'get-file-full/(?P.+)?$', 'get_file', - name='get-file-full', kwargs={'full':True}), - url(r'get-administrativeactfile/(?P.+)?$', - 'get_administrativeactfile', name='get-administrativeactfile'), - url(r'show-file/(?P.+)?/(?P.+)?$', 'show_file', - name='show-file'), - url(r'show-historized-file/(?P.+)?/(?P.+)?$', - 'show_file', name='show-historized-file'), - url(r'revert-file/(?P.+)/(?P.+)$', - 'revert_file', name='revert-file'), - # Operations - url(r'autocomplete-operation/$', 'autocomplete_operation', - name='autocomplete-operation'), - url(r'get-operation/(?P.+)?$', 'get_operation', - name='get-operation'), - url(r'get-operation-full/(?P.+)?$', 'get_operation', - name='get-operation-full', kwargs={'full':True}), - url(r'get-available-operation-code/(?P.+)?$', - 'get_available_operation_code', name='get_available_operation_code'), - url(r'revert-operation/(?P.+)/(?P.+)$', - 'revert_operation', name='revert-operation'), - url(r'show-operation/(?P.+)?/(?P.+)?$', - 'show_operation', name='show-operation'), - url(r'get-administrativeactop/(?P.+)?$', - 'get_administrativeactop', name='get-administrativeactop'), - url(r'get-operationsource/(?P.+)?$', - 'get_operationsource', name='get-operationsource'), - # Context records - url(r'show-contextrecord/(?P.+)?/(?P.+)?$', - 'show_contextrecord', name='show-contextrecord'), - url(r'get-contextrecord/(?P.+)?$', 'get_contextrecord', - name='get-contextrecord'), - url(r'get-contextrecord-full/(?P.+)?$', - 'get_contextrecord', name='get-contextrecord-full', - kwargs={'full':True}), - url(r'get-contexrecordsource/(?P.+)?$', - 'get_contextrecordsource', name='get-contextrecordsource'), - # Finds - url(r'update-current-item/$', 'update_current_item', - name='update-current-item'), - url(r'get-item/(?P.+)?$', 'get_archaeologicalitem', - name='get-item'), - url(r'get-item-full/(?P.+)?$', 'get_archaeologicalitem', - name='get-item-full', kwargs={'full':True}), - url(r'get-itemsource/(?P.+)?$', - 'get_itemsource', name='get-itemsource'), - url(r'get-container/$', 'get_container', - name='get-container'), - # Treatments - url(r'autocomplete-container/?$', - 'autocomplete_container', name='autocomplete-container'), - url(r'new-container/(?P.+)?/$', - 'new_container', name='new-container'), ) diff --git a/ishtar_common/views.py b/ishtar_common/views.py index fe464ade7..69fa8b90b 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 Étienne Loks +# Copyright (C) 2010-2012 É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 @@ -886,32 +886,6 @@ def autocomplete_town(request): for town in towns]) return HttpResponse(data, mimetype='text/plain') -def autocomplete_file(request): - person_types = request.user.ishtaruser.person.person_type - if (not request.user.has_perm('ishtar_common.view_file', models.File) and \ - not request.user.has_perm('ishtar_common.view_own_file', models.File) - and not person_types.rights.filter(wizard__url_name='file_search' - ).count()): - return HttpResponse(mimetype='text/plain') - if not request.GET.get('term'): - return HttpResponse(mimetype='text/plain') - q = request.GET.get('term') - query = Q() - for q in q.split(' '): - extra = Q(internal_reference__icontains=q) | \ - Q(towns__name__icontains=q) - try: - value = int(q) - extra = extra | Q(year=q) | Q(numeric_reference=q) - except ValueError: - pass - query = query & extra - limit = 20 - files = models.File.objects.filter(query)[:limit] - data = json.dumps([{'id':file.pk, 'value':unicode(file)} - for file in files]) - return HttpResponse(data, mimetype='text/plain') - from types import NoneType def format_val(val): @@ -1198,72 +1172,6 @@ def revert_item(model): return HttpResponse("True", mimetype='text/plain') return func - -get_file = get_item(models.File, 'get_file', 'file') -show_file = show_item(models.File, 'file') -revert_file = revert_item(models.File) - -def autocomplete_operation(request, non_closed=True): - person_types = request.user.ishtaruser.person.person_type - if (not request.user.has_perm('ishtar_common.view_operation', models.Operation)\ - and not request.user.has_perm('ishtar_common.view_own_operation', - models.Operation) - and not person_types.rights.filter(wizard__url_name='operation_search' - ).count()): - return HttpResponse(mimetype='text/plain') - if not request.GET.get('term'): - return HttpResponse(mimetype='text/plain') - q = request.GET.get('term') - query = Q() - for q in q.split(' '): - extra = Q(towns__name__icontains=q) - try: - value = int(q) - extra = extra | Q(year=q) | Q(operation_code=q) - except ValueError: - pass - query = query & extra - if non_closed: - query = query & Q(end_date__isnull=True) - limit = 15 - operations = models.Operation.objects.filter(query)[:limit] - data = json.dumps([{'id':operation.pk, 'value':unicode(operation)} - for operation in operations]) - return HttpResponse(data, mimetype='text/plain') - -def get_available_operation_code(request, year=None): - if not request.user.has_perm('ishtar_common.view_operation', models.Operation)\ - and not request.user.has_perm('ishtar_common.view_own_operation', - models.Operation): - return HttpResponse(mimetype='text/plain') - data = json.dumps({'id':models.Operation.get_available_operation_code(year)}) - return HttpResponse(data, mimetype='text/plain') - -get_operation = get_item(models.Operation, 'get_operation', 'operation', - bool_fields = ['end_date__isnull'], - extra_request_keys={'common_name':'common_name__icontains', - 'end_date':'end_date__isnull', - 'year_index':('year', 'operation_code')}) -show_operation = show_item(models.Operation, 'operation') -revert_operation = revert_item(models.Operation) - -get_operationsource = get_item(models.OperationSource, - 'get_operationsource', 'operationsource', - extra_request_keys={'operation__towns':'operation__towns__pk', - 'operation__operation_type':'operation__operation_type__pk', - 'operation__year':'operation__year'}) - -get_administrativeactfile = get_item(models.AdministrativeAct, - 'get_administrativeactfile', 'administrativeactfile', - extra_request_keys={'associated_file__towns':'associated_file__towns__pk', - 'operation__towns':'operation__towns__pk', - 'act_type__intented_to':'act_type__intented_to'}) -get_administrativeactop = get_item(models.AdministrativeAct, - 'get_administrativeactop', 'administrativeactop', - extra_request_keys={'associated_file__towns':'associated_file__towns__pk', - 'operation__towns':'operation__towns__pk', - 'act_type__intented_to':'act_type__intented_to'}) - def autocomplete_organization(request, orga_type=None): person_types = request.user.ishtaruser.person.person_type if (not request.user.has_perm('ishtar_common.view_organization', @@ -1293,72 +1201,10 @@ def autocomplete_organization(request, orga_type=None): for org in organizations]) return HttpResponse(data, mimetype='text/plain') -show_contextrecord = show_item(models.ContextRecord, 'contextrecord') -get_contextrecord = get_item(models.ContextRecord, - 'get_contextrecord', 'contextrecord', - extra_request_keys={'parcel__town':'parcel__town__pk', - 'operation__year':'operation__year__contains', - 'datings__period':'datings__period__pk'},) -get_contextrecordsource = get_item(models.ContextRecordSource, - 'get_contextrecordsource', 'contextrecordsource', - extra_request_keys={ - 'context_record__parcel__town':'context_record__parcel__town__pk', - 'context_record__operation__year':'context_record__operation__year', - 'context_record__datings__period':'context_record__datings__period__pk', - 'context_record__unit':'context_record__unit__pk', - }) -get_archaeologicalitem = get_item(models.Item, - 'get_archaeologicalitem', 'item', - bool_fields = ['base_items__is_isolated'], - base_request={'downstream_treatment__isnull':True}, - extra_request_keys={ -'base_items__context_record__parcel__town': - 'base_items__context_record__parcel__town', -'base_items__context_record__operation__year': - 'base_items__context_record__operation__year__contains', -'base_items__context_record__operation__code_patriarche': - 'base_items__context_record__operation__code_patriarche', -'dating__period':'dating__period__pk', -'base_items__item__description':'base_items__item__description__icontains', -'base_items__is_isolated':'base_items__is_isolated'}) -get_itemsource = get_item(models.ItemSource, - 'get_itemsource', 'itemsource', - extra_request_keys={ -'item__context_record__operation__year':'item__context_record__operation__year', -'item__dating__period':'item__dating__period__pk', -'item__description':'item__description__icontains', - }) -get_container = get_item(models.Container, - 'get_container', 'container', - extra_request_keys={ -'location':'location__pk', -'container_type':'container_type__pk', -'reference':'reference__icontains', - }) - -def autocomplete_warehouse(request): - if not request.user.has_perm('ishtar_common.view_warehouse', models.Warehouse)\ - and not request.user.has_perm('ishtar_common.view_own_warehouse', - models.Warehouse) : - return HttpResponse(mimetype='text/plain') - if not request.GET.get('term'): - return HttpResponse(mimetype='text/plain') - q = request.GET.get('term') - query = Q() - for q in q.split(' '): - extra = Q(name__icontains=q) | \ - Q(warehouse_type__label__icontains=q) - query = query & extra - limit = 15 - warehouses = models.Warehouse.objects.filter(query)[:limit] - data = json.dumps([{'id':warehouse.pk, 'value':unicode(warehouse)} - for warehouse in warehouses]) - return HttpResponse(data, mimetype='text/plain') - def autocomplete_author(request): if not request.user.has_perm('ishtar_common.view_author', models.Author)\ and not request.user.has_perm('ishtar_common.view_own_author', - models.Warehouse) : + models.Author) : return HttpResponse(mimetype='text/plain') if not request.GET.get('term'): return HttpResponse(mimetype='text/plain') @@ -1376,29 +1222,6 @@ def autocomplete_author(request): for author in authors]) return HttpResponse(data, mimetype='text/plain') -def autocomplete_container(request): - if not request.user.has_perm('ishtar_common.view_warehouse', - models.Warehouse)\ - and not request.user.has_perm('ishtar_common.view_own_warehouse', - models.Warehouse): - return HttpResponse(mimetype='text/plain') - if not request.GET.get('term'): - return HttpResponse(mimetype='text/plain') - q = request.GET.get('term') - query = Q() - for q in q.split(' '): - extra = Q(container_type__label__icontains=q) | \ - Q(container_type__reference__icontains=q) | \ - Q(reference__icontains=q) | \ - Q(location__name=q) | \ - Q(location__town=q) - query = query & extra - limit = 15 - containers = models.Container.objects.filter(query)[:limit] - data = json.dumps([{'id':container.pk, 'value':unicode(container)} - for container in containers]) - return HttpResponse(data, mimetype='text/plain') - def new_item(model): def func(request, parent_name): model_name = model._meta.object_name @@ -1426,11 +1249,9 @@ def new_item(model): context_instance=RequestContext(request)) return func -new_warehouse = new_item(models.Warehouse) new_person = new_item(models.Person) new_organization = new_item(models.Organization) new_author = new_item(models.Author) -new_container = new_item(models.Container) def action(request, action_slug, obj_id=None, *args, **kwargs): """ @@ -1464,19 +1285,3 @@ def dashboard_main(request, dct, obj_id=None, *args, **kwargs): 'ishtar_users':models.UserDashboard()} return render_to_response('dashboard_main.html', dct, context_instance=RequestContext(request)) - -def dashboard_file(request, dct, obj_id=None, *args, **kwargs): - """ - Main dashboard - """ - dct = {'dashboard': models.FileDashboard()} - return render_to_response('dashboard_file.html', dct, - context_instance=RequestContext(request)) - -def dashboard_operation(request, dct, obj_id=None, *args, **kwargs): - """ - Operation dashboard - """ - dct = {'dashboard': models.OperationDashboard()} - return render_to_response('dashboard_operation.html', dct, - context_instance=RequestContext(request)) -- cgit v1.2.3