diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2021-04-28 09:55:25 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2021-07-29 11:54:14 +0200 |
commit | d552436e1847fc7541d1abe643c5fed67109bae6 (patch) | |
tree | 8959fe77756ed8db59dcada5de146d1427030b9a | |
parent | f5e7db4f7b3bceb0cdef84f9530e0cddc67beea8 (diff) | |
download | Ishtar-d552436e1847fc7541d1abe643c5fed67109bae6.tar.bz2 Ishtar-d552436e1847fc7541d1abe643c5fed67109bae6.zip |
WIP: preventive forms
-rw-r--r-- | DEVELOP.md | 34 | ||||
-rw-r--r-- | archaeological_files/forms.py | 188 | ||||
-rw-r--r-- | archaeological_files/migrations/0106_auto_20210622_1456.py (renamed from archaeological_files/migrations/0106_auto_20210423_1206.py) | 175 | ||||
-rw-r--r-- | archaeological_files/models.py | 65 | ||||
-rw-r--r-- | archaeological_files/templates/ishtar/forms/preventive_detail.html | 2 | ||||
-rw-r--r-- | archaeological_files/urls.py | 7 | ||||
-rw-r--r-- | archaeological_files/views.py | 84 | ||||
-rw-r--r-- | ishtar_common/templates/blocks/bs_formset_snippet.html | 26 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/forms/base_form.html | 50 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/forms/document.html | 51 | ||||
-rw-r--r-- | ishtar_common/templates/widgets/GramKilogramWidget.html | 21 | ||||
-rw-r--r-- | ishtar_common/templates/widgets/SquareMeterWidget.html | 21 | ||||
-rw-r--r-- | ishtar_common/templates/widgets/UnitWidget.html (renamed from ishtar_common/templates/widgets/CentimeterMeterWidget.html) | 6 | ||||
-rw-r--r-- | ishtar_common/templatetags/table_form.py | 9 | ||||
-rw-r--r-- | ishtar_common/widgets.py | 17 |
15 files changed, 565 insertions, 191 deletions
diff --git a/DEVELOP.md b/DEVELOP.md index f7398ba26..e91ca3ccf 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -3,11 +3,12 @@ Installation instruction for a debian buster with sudo. ### Install base source code -``` +``` bash sudo apt-get update -sudo apt-get install git sed python3-pip libpq-dev python3-dev libjpeg-dev zlib1g-dev \ - libxml2-dev libxslt1-dev libgeos-dev python3-cairocffi tidy libtidy-dev binutils \ - libproj-dev gdal-bin libpangocairo-1.0-0 pandoc graphviz +sudo apt-get install git sed python3-venv python3-wheel python3-pip libpq-dev \ + python3-dev libjpeg-dev zlib1g-dev libxml2-dev libxslt1-dev libgeos-dev \ + python3-cairocffi tidy libtidy-dev binutils libproj-dev gdal-bin \ + libpangocairo-1.0-0 pandoc graphviz gettext cd my_workspace python3 -m venv ishtar-venv @@ -19,18 +20,22 @@ pip3 install -r requirements.txt ### Install postgresql database -``` -apt install postgresql postgresql-contrib postgresql-11-postgis-2.5 \ +``` bash +sudo apt install postgresql postgresql-contrib postgresql-11-postgis-2.5 \ postgresql-11-postgis-2.5-scripts sudo -u postgres psql +``` + +``` sql CREATE DATABASE ishtar; CREATE USER ishtar WITH ENCRYPTED PASSWORD 'mypassword'; GRANT ALL PRIVILEGES ON DATABASE ishtar TO ishtar; +ALTER ROLE ishtar superuser; ``` ### Configure and initialize your instance -``` +``` bash cd my_workspace . ./ishtar-venv/bin/activate cd ishtar @@ -38,7 +43,22 @@ ISHTAR_PATH=`pwd` cp example_project/local_settings.py.sample example_project/local_settings.py editor example_project/local_settings.py # edit settings: SECRET_KEY, DATABASE +``` + +``` python +DATABASES = { + 'default': { + 'NAME': 'ishtar', + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'HOST': '127.0.0.1', + 'PORT': '5432', + 'USER': 'ishtar', + 'PASSWORD': 'mypassword', + }, +} +``` +``` bash cd example_project # collect static data python3 ./manage.py collectstatic --noinput diff --git a/archaeological_files/forms.py b/archaeological_files/forms.py index 2cb08b5d7..51d1fdeb0 100644 --- a/archaeological_files/forms.py +++ b/archaeological_files/forms.py @@ -20,12 +20,13 @@ """ Files forms definitions """ + import datetime from django import forms from django.conf import settings from django.core import validators -from django.forms.formsets import formset_factory +from django.forms.formsets import formset_factory, BaseFormSet from django.utils.functional import lazy from django.utils.safestring import mark_safe from ishtar_common.utils import ugettext_lazy as _ @@ -57,6 +58,7 @@ from ishtar_common.forms import ( LockForm, CustomFormSearch, DocumentItemSelect, + FormHeader, ) from ishtar_common.forms_common import get_town_field from archaeological_operations.forms import ( @@ -506,6 +508,190 @@ class FinalFileDeleteForm(FinalForm): confirm_end_msg = _("Would you like to delete this archaeological file ?") +class FileFormPreventiveDetail(forms.ModelForm, CustomForm, ManageOldType): + form_label = _("Preventive file") + form_admin_name = _("Preventive file - 020 - Edition form") + form_slug = "preventive-020-edition-form" + + associated_models = {} + + HEADERS = { + "start_date": FormHeader(_("Dates")), + "total_developed_surface": FormHeader(_("Surfaces")), + } + + class Meta: + model = models.File + fields = ["start_date", "end_date", "ground_start_date", + "ground_end_date", "study_period", "execution_report_date", + "total_developed_surface", "total_surface", "linear_meter"] + + pk = forms.IntegerField(label="", required=False, widget=forms.HiddenInput) + start_date = forms.DateField( + label=_("Start date"), widget=DatePicker, required=False + ) + end_date = forms.DateField(label=_("End date"), widget=DatePicker, required=False) + ground_start_date = forms.DateField( + label=_("Ground start date"), widget=DatePicker, required=False + ) + ground_end_date = forms.DateField( + label=_("Ground end date"), widget=DatePicker, required=False + ) + study_period = forms.CharField( + label=_("Study period"), + max_length=200, + required=False, + ) + execution_report_date = forms.DateField( + label=_("Execution report date"), widget=DatePicker, required=False + ) + total_developed_surface = forms.FloatField( + widget=widgets.AreaWidget, + label=_("Total developed surface (m2)"), + required=False, + validators=[ + validators.MinValueValidator(0), + validators.MaxValueValidator(999999999), + ], + ) + total_surface = forms.FloatField( + required=False, + widget=widgets.AreaWidget, + label=_("Total surface (m2)"), + validators=[ + validators.MinValueValidator(0), + validators.MaxValueValidator(999999999), + ], + ) + linear_meter = forms.IntegerField( + label=_("Linear meter (m)"), required=False, + widget=widgets.MeterKilometerWidget, + ) + + def __init__(self, *args, **kwargs): + self.user = None + if kwargs.get("user", None): + self.user = kwargs.pop("user") + super(FileFormPreventiveDetail, self).__init__(*args, **kwargs) + + +class FileBaseFormset(forms.BaseModelFormSet): + def __init__(self, *args, **kwargs): + self.instance = None + if "instance" in kwargs: + self.instance = kwargs.pop("instance") + super().__init__(*args, **kwargs) + self.queryset = self.model.objects.filter(pk=None) + if self.instance: + self.queryset = self.model.objects.filter(file_id=self.instance.pk) + + +class PreventiveFileJobForm(forms.ModelForm): + file_id = forms.IntegerField(widget=forms.HiddenInput) + job = forms.ChoiceField(choices=[]) + man_by_day_planned = forms.FloatField(_("Man by day - planned"), required=False) + days_planned = forms.FloatField(_("Days - planned"), required=False) + man_by_day_worked = forms.FloatField(_("Man by day - worked"), required=False) + days_worked = forms.FloatField(_("Days - worked"), required=False) + + class Meta: + model = models.PreventiveFileJob + exclude = ["file"] + + +class PreventiveFileJobBaseFormSet(FileBaseFormset): + model = models.PreventiveFileJob + + +PreventiveFileJobFormSet = formset_factory( + PreventiveFileJobForm, formset=PreventiveFileJobBaseFormSet, can_delete=True) +PreventiveFileJobFormSet.form_label = _("Preventive - Jobs") +PreventiveFileJobFormSet.form_admin_name = _("Preventive file - 030 - Jobs") +PreventiveFileJobFormSet.form_slug = "preventive-030-jobs" + + +class PreventiveFileGroundJobForm(forms.ModelForm): + file_id = forms.IntegerField(widget=forms.HiddenInput) + ground_job = forms.ChoiceField(choices=[]) + man_by_day_planned = forms.FloatField(_("Man by day - planned"), required=False) + days_planned = forms.FloatField(_("Days - planned"), required=False) + man_by_day_worked = forms.FloatField(_("Man by day - worked"), required=False) + days_worked = forms.FloatField(_("Days - worked"), required=False) + + class Meta: + model = models.PreventiveFileGroundJob + exclude = ["file"] + + +class PreventiveFileGroundJobBaseFormSet(FileBaseFormset): + model = models.PreventiveFileGroundJob + + +PreventiveFileGroundJobFormSet = formset_factory( + PreventiveFileGroundJobForm, formset=PreventiveFileGroundJobBaseFormSet, + can_delete=True +) +PreventiveFileGroundJobFormSet.form_label = _("Preventive - Ground jobs") +PreventiveFileGroundJobFormSet.form_admin_name = _( + "Preventive file - 040 - Ground jobs" +) +PreventiveFileGroundJobFormSet.form_slug = "preventive-040-ground-jobs" + + +class PreventiveFileEquipmentForm(forms.ModelForm): + file_id = forms.IntegerField(widget=forms.HiddenInput) + equipment = forms.ChoiceField(choices=[]) + man_by_day_planned = forms.FloatField(_("Man by day - planned"), required=False) + days_planned = forms.FloatField(_("Days - planned"), required=False) + man_by_day_worked = forms.FloatField(_("Man by day - worked"), required=False) + days_worked = forms.FloatField(_("Days - worked"), required=False) + + class Meta: + model = models.PreventiveFileEquipmentCost + exclude = ["file"] + + +class PreventiveFileEquipmentBaseFormSet(FileBaseFormset): + model = models.PreventiveFileEquipmentCost + + +PreventiveFileEquipmentFormSet = formset_factory( + PreventiveFileEquipmentForm, formset=PreventiveFileEquipmentBaseFormSet, + can_delete=True +) +PreventiveFileEquipmentFormSet.form_label = _("Preventive - Equipment") +PreventiveFileEquipmentFormSet.form_admin_name = _("Preventive file - 050 - Equipments") +PreventiveFileEquipmentFormSet.form_slug = "preventive-050-equipments" + + +class PreventiveFileTechnicalServiceForm(forms.ModelForm): + file_id = forms.IntegerField(widget=forms.HiddenInput) + technical_service = forms.ChoiceField(choices=[]) + man_by_day_planned = forms.FloatField(_("Man by day - planned"), required=False) + days_planned = forms.FloatField(_("Days - planned"), required=False) + man_by_day_worked = forms.FloatField(_("Man by day - worked"), required=False) + days_worked = forms.FloatField(_("Days - worked"), required=False) + + class Meta: + model = models.PreventiveFileTechnicalServiceCost + exclude = ["file"] + + +class PreventiveFileTechnicalServiceBaseFormSet(FileBaseFormset): + model = models.PreventiveFileTechnicalServiceCost + + +PreventiveFileTechnicalServiceFormSet = formset_factory( + PreventiveFileTechnicalServiceForm, + formset=PreventiveFileTechnicalServiceBaseFormSet, can_delete=True +) +PreventiveFileTechnicalServiceFormSet.form_label = _("Preventive - Technical Services") +PreventiveFileTechnicalServiceFormSet.form_admin_name = _( + "Preventive file - 060 - TechnicalServices" +) +PreventiveFileTechnicalServiceFormSet.form_slug = "preventive-060-technical_services" + + class AdministrativeActFileModifySelect(TableSelect): _model = AdministrativeAct diff --git a/archaeological_files/migrations/0106_auto_20210423_1206.py b/archaeological_files/migrations/0106_auto_20210622_1456.py index 5d147781e..2667a705d 100644 --- a/archaeological_files/migrations/0106_auto_20210423_1206.py +++ b/archaeological_files/migrations/0106_auto_20210622_1456.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.27 on 2021-04-23 12:06 +# Generated by Django 1.11.28 on 2021-06-22 14:56 from __future__ import unicode_literals import django.core.validators @@ -88,62 +88,14 @@ class Migration(migrations.Migration): bases=(ishtar_common.models_common.Cached, models.Model), ), migrations.CreateModel( - name='PreventiveFile', + name='ManDays', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('study_period', models.CharField(blank=True, default='', max_length=200, verbose_name='Study period')), - ('start_date', models.DateField(blank=True, null=True, verbose_name='Start date')), - ('end_date', models.DateField(blank=True, null=True, verbose_name='End date')), - ('ground_start_date', models.DateField(blank=True, null=True, verbose_name='Ground start date')), - ('ground_end_date', models.DateField(blank=True, null=True, verbose_name='Ground end date')), - ('execution_report_date', models.DateField(blank=True, null=True, verbose_name='Execution report date')), - ('linear_meter', models.IntegerField(blank=True, null=True, verbose_name='Linear meter')), - ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.File', verbose_name='File')), + ('man_by_day_planned', models.FloatField(blank=True, null=True, verbose_name='Man by day - planned')), + ('days_planned', models.FloatField(blank=True, null=True, verbose_name='Days - planned')), + ('man_by_day_worked', models.FloatField(blank=True, null=True, verbose_name='Man by day - worked')), + ('days_worked', models.FloatField(blank=True, null=True, verbose_name='Days - worked')), ], - options={ - 'verbose_name': 'Preventive file', - 'verbose_name_plural': 'Preventive files', - }, - ), - migrations.CreateModel( - name='PreventiveFileEquipmentCost', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('man_days_planned', models.FloatField(blank=True, null=True, verbose_name='Man-day planned')), - ('man_days_worked', models.FloatField(blank=True, null=True, verbose_name='Man-day worked')), - ('equipment_cost', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.EquipmentCost')), - ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.PreventiveFile')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='PreventiveFileJob', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('man_days_planned', models.FloatField(blank=True, null=True, verbose_name='Man-day planned')), - ('man_days_worked', models.FloatField(blank=True, null=True, verbose_name='Man-day worked')), - ('ground_man_days_planned', models.FloatField(blank=True, null=True, verbose_name='Ground man-day planned')), - ('ground_man_days_worked', models.FloatField(blank=True, null=True, verbose_name='Ground man-day worked')), - ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.PreventiveFile')), - ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.Job')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='PreventiveFileTechnicalServiceCost', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('man_days_planned', models.FloatField(blank=True, null=True, verbose_name='Man-day planned')), - ('man_days_worked', models.FloatField(blank=True, null=True, verbose_name='Man-day worked')), - ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.PreventiveFile')), - ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( name='TechnicalService', @@ -177,9 +129,108 @@ class Migration(migrations.Migration): }, ), migrations.AddField( - model_name='preventivefiletechnicalservicecost', - name='technical_service_cost', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.TechnicalServiceCost'), + model_name='file', + name='execution_report_date', + field=models.DateField(blank=True, null=True, verbose_name='Execution report date'), + ), + migrations.AddField( + model_name='file', + name='ground_end_date', + field=models.DateField(blank=True, null=True, verbose_name='Ground end date'), + ), + migrations.AddField( + model_name='file', + name='ground_start_date', + field=models.DateField(blank=True, null=True, verbose_name='Ground start date'), + ), + migrations.AddField( + model_name='file', + name='linear_meter', + field=models.IntegerField(blank=True, null=True, verbose_name='Linear meter'), + ), + migrations.AddField( + model_name='file', + name='start_date', + field=models.DateField(blank=True, null=True, verbose_name='Start date'), + ), + migrations.AddField( + model_name='file', + name='study_period', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='Study period'), + ), + migrations.AddField( + model_name='historicalfile', + name='execution_report_date', + field=models.DateField(blank=True, null=True, verbose_name='Execution report date'), + ), + migrations.AddField( + model_name='historicalfile', + name='ground_end_date', + field=models.DateField(blank=True, null=True, verbose_name='Ground end date'), + ), + migrations.AddField( + model_name='historicalfile', + name='ground_start_date', + field=models.DateField(blank=True, null=True, verbose_name='Ground start date'), + ), + migrations.AddField( + model_name='historicalfile', + name='linear_meter', + field=models.IntegerField(blank=True, null=True, verbose_name='Linear meter'), + ), + migrations.AddField( + model_name='historicalfile', + name='start_date', + field=models.DateField(blank=True, null=True, verbose_name='Start date'), + ), + migrations.AddField( + model_name='historicalfile', + name='study_period', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='Study period'), + ), + migrations.AlterField( + model_name='file', + name='end_date', + field=models.DateField(blank=True, null=True, verbose_name='End date'), + ), + migrations.AlterField( + model_name='historicalfile', + name='end_date', + field=models.DateField(blank=True, null=True, verbose_name='End date'), + ), + migrations.CreateModel( + name='PreventiveFileEquipmentCost', + fields=[ + ('mandays_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='archaeological_files.ManDays')), + ], + bases=('archaeological_files.mandays',), + ), + migrations.CreateModel( + name='PreventiveFileGroundJob', + fields=[ + ('mandays_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='archaeological_files.ManDays')), + ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ground_jobs', to='archaeological_files.File')), + ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.Job')), + ], + bases=('archaeological_files.mandays',), + ), + migrations.CreateModel( + name='PreventiveFileJob', + fields=[ + ('mandays_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='archaeological_files.ManDays')), + ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='archaeological_files.File')), + ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.Job')), + ], + bases=('archaeological_files.mandays',), + ), + migrations.CreateModel( + name='PreventiveFileTechnicalServiceCost', + fields=[ + ('mandays_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='archaeological_files.ManDays')), + ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='technical_service_costs', to='archaeological_files.File')), + ('technical_service_cost', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.TechnicalServiceCost')), + ], + bases=('archaeological_files.mandays',), ), migrations.AddField( model_name='equipmenttype', @@ -191,4 +242,14 @@ class Migration(migrations.Migration): name='equipment_type', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.EquipmentType', verbose_name='Equipment'), ), + migrations.AddField( + model_name='preventivefileequipmentcost', + name='equipment_cost', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.EquipmentCost'), + ), + migrations.AddField( + model_name='preventivefileequipmentcost', + name='file', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='equipment_costs', to='archaeological_files.File'), + ), ] diff --git a/archaeological_files/models.py b/archaeological_files/models.py index db46c1ca7..fde527ae1 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -521,6 +521,18 @@ class File( ) # <-- research archaeology + # --> preventive detail + study_period = models.CharField(_("Study period"), max_length=200, + default="", blank=True) + start_date = models.DateField(_("Start date"), blank=True, null=True) + end_date = models.DateField(_("End date"), blank=True, null=True) + ground_start_date = models.DateField(_("Ground start date"), blank=True, null=True) + ground_end_date = models.DateField(_("Ground end date"), blank=True, null=True) + execution_report_date = models.DateField(_("Execution report date"), blank=True, + null=True) + linear_meter = models.IntegerField(_("Linear meter"), blank=True, null=True) + # <-- preventive detail + documents = models.ManyToManyField( Document, related_name="files", verbose_name=_("Documents"), blank=True ) @@ -1142,47 +1154,44 @@ class FileDashboard: ) -class PreventiveFile(models.Model): - file = models.ForeignKey(File, verbose_name=_("File")) - study_period = models.CharField(_("Study period"), max_length=200, - default="", blank=True) - start_date = models.DateField(_("Start date"), blank=True, null=True) - end_date = models.DateField(_("End date"), blank=True, null=True) - ground_start_date = models.DateField(_("Ground start date"), blank=True, null=True) - ground_end_date = models.DateField(_("Ground end date"), blank=True, null=True) - execution_report_date = models.DateField(_("Execution report date"), blank=True, - null=True) - linear_meter = models.IntegerField(_("Linear meter"), blank=True, null=True) +class ManDays(models.Model): + man_by_day_planned = models.FloatField( + _("Man by day - planned"), null=True, blank=True) + days_planned = models.FloatField( + _("Days - planned"), null=True, blank=True) + man_by_day_worked = models.FloatField( + _("Man by day - worked"), null=True, blank=True) + days_worked = models.FloatField( + _("Days - worked"), null=True, blank=True) - class Meta: - verbose_name = _("Preventive file") - verbose_name_plural = _("Preventive files") + @property + def man_days_planned(self): + if not self.days_planned or not self.man_by_day_planned: + return 0 + return self.days_planned * self.man_by_day_planned + @property + def man_days_worked(self): + if not self.days_worked or not self.man_by_day_worked: + return 0 + return self.days_worked * self.man_by_day_worked -class ManDays(models.Model): - man_days_planned = models.FloatField( - _("Man-day planned"), null=True, blank=True) - man_days_worked = models.FloatField( - _("Man-day worked"), null=True, blank=True) - class Meta: - abstract = True +class PreventiveFileGroundJob(ManDays): + file = models.ForeignKey(File, related_name="ground_jobs") + job = models.ForeignKey(Job) class PreventiveFileJob(ManDays): - file = models.ForeignKey(PreventiveFile) + file = models.ForeignKey(File, related_name="jobs") job = models.ForeignKey(Job) - ground_man_days_planned = models.FloatField( - _("Ground man-day planned"), null=True, blank=True) - ground_man_days_worked = models.FloatField( - _("Ground man-day worked"), null=True, blank=True) class PreventiveFileEquipmentCost(ManDays): - file = models.ForeignKey(PreventiveFile) + file = models.ForeignKey(File, related_name="equipment_costs") equipment_cost = models.ForeignKey(EquipmentCost) class PreventiveFileTechnicalServiceCost(ManDays): - file = models.ForeignKey(PreventiveFile) + file = models.ForeignKey(File, related_name="technical_service_costs") technical_service_cost = models.ForeignKey(TechnicalServiceCost) diff --git a/archaeological_files/templates/ishtar/forms/preventive_detail.html b/archaeological_files/templates/ishtar/forms/preventive_detail.html new file mode 100644 index 000000000..3cdfb3da0 --- /dev/null +++ b/archaeological_files/templates/ishtar/forms/preventive_detail.html @@ -0,0 +1,2 @@ +{% extends "ishtar/forms/base_form.html" %} +{% load i18n %} diff --git a/archaeological_files/urls.py b/archaeological_files/urls.py index b245b0aea..3fcf9a42c 100644 --- a/archaeological_files/urls.py +++ b/archaeological_files/urls.py @@ -137,4 +137,11 @@ urlpatterns = [ check_rights(["add_operation"])(views.file_add_operation), name="file-add-operation", ), + url( + r'^file/edit-preventive/(?P<pk>.+)/$', + check_rights(["change_file", "change_own_file"])( + views.PreventiveEditView.as_view() + ), + name="file-edit-preventive", + ) ] diff --git a/archaeological_files/views.py b/archaeological_files/views.py index 35bd9d7c8..a927a94e6 100644 --- a/archaeological_files/views.py +++ b/archaeological_files/views.py @@ -23,11 +23,12 @@ import re from django.core.urlresolvers import reverse from django.db.models import Q from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.views.generic.edit import UpdateView from django.shortcuts import redirect, render from ishtar_common.utils import ugettext_lazy as _ from ishtar_common.views import wizard_is_available -from ishtar_common.views_item import get_item, show_item, revert_item +from ishtar_common.views_item import get_item, show_item, revert_item, check_permission from archaeological_operations.wizards import ( AdministrativeActDeletionWizard, @@ -45,6 +46,7 @@ from archaeological_files.wizards import ( FileAdministrativeActWizard, FileEditAdministrativeActWizard, ) +from ishtar_common.views import IshtarMixin, LoginRequiredMixin from archaeological_operations.wizards import OperationWizard from archaeological_operations.views import operation_creation_wizard @@ -354,3 +356,83 @@ def reset_wizards(request): (AdministrativeActDeletionWizard, "file_administrativeactfile_deletion_wizard"), ): wizard_class.session_reset(request, url_name) + + +class MixFormFormsetUpdateView(UpdateView): + form_inlines_class = [] + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.inline_forms = [ + inline(instance=self.object) for inline in self.form_inlines_class + ] + return super(MixFormFormsetUpdateView, self).get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + form = self.form_class(data=request.POST, instance=self.object) + inline_forms = [ + inline(instance=self.object, data=request.POST) + for inline in self.form_inlines_class + ] + if form.is_valid() and all((inline.is_valid() for inline in inline_forms)): + return self.form_valid(form, inline_forms) + else: + return self.form_invalid(form, inline_forms) + + def form_valid(self, form, inline_forms): + self.object = form.save() + for inline in inline_forms: + # save inlines... + pass + return HttpResponseRedirect(self.get_success_url()) + + def form_invalid(self, form, inline_forms): + return self.render_to_response( + self.get_context_data(form=form, inlines=inline_forms) + ) + + def get_context_data(self, *args, **kwargs): + data = super(MixFormFormsetUpdateView, self).get_context_data(*args, **kwargs) + data["inline_forms"] = self.inline_forms + return data + + +class PreventiveEditView(IshtarMixin, LoginRequiredMixin, MixFormFormsetUpdateView): + page_name = _("Preventive modification") + form_class = forms.FileFormPreventiveDetail + form_inlines_class = [ + forms.PreventiveFileJobFormSet, + forms.PreventiveFileGroundJobFormSet, + forms.PreventiveFileEquipmentFormSet, + forms.PreventiveFileTechnicalServiceFormSet + ] + template_name = "ishtar/forms/preventive_detail.html" + model = models.File + + + def get_success_url(self): + return reverse("file_modification") + "?open_item={}".format(self.object.pk) + + def get_form_kwargs(self): + kwargs = super(PreventiveEditView, self).get_form_kwargs() + try: + file = models.File.objects.get(pk=self.kwargs.get("pk")) + except models.Document.DoesNotExist: + raise Http404() + if not check_permission(self.request, "file/edit-preventive/", file.pk): + raise Http404() + initial = {} + for k in ( + list(self.form_class.base_fields.keys()) + ): + value = getattr(file, k) + if hasattr(value, "all"): + value = ",".join([str(v.pk) for v in value.all()]) + if hasattr(value, "pk"): + value = value.pk + initial[k] = value + kwargs["initial"] = initial + kwargs["user"] = self.request.user + self.file = file + return kwargs
\ No newline at end of file diff --git a/ishtar_common/templates/blocks/bs_formset_snippet.html b/ishtar_common/templates/blocks/bs_formset_snippet.html new file mode 100644 index 000000000..0d089ee1b --- /dev/null +++ b/ishtar_common/templates/blocks/bs_formset_snippet.html @@ -0,0 +1,26 @@ +{% load i18n from_dict %} +<div class="form-row"> + {% for form in formset %} + {% if form.non_field_errors and not no_error %} + <div class="alert alert-danger" role="alert"> + {{form.non_field_errors}} + </div> + {% endif %} + + {% for hidden in form.hidden_fields %} + {{hidden}} + {% if hidden.errors %}<div class="invalid-feedback"> + {{ hidden.errors }} + </div>{% endif %} + {% endfor %} + + {% csrf_token %} + {% for field in form.visible_fields %} + {% include "blocks/bs_field_snippet.html" %} + {% endfor %} + + {% if form.extra_render %} + {{form.extra_render|safe}} + {% endif %} + {% endfor %} +</div> diff --git a/ishtar_common/templates/ishtar/forms/base_form.html b/ishtar_common/templates/ishtar/forms/base_form.html new file mode 100644 index 000000000..fb1fc997f --- /dev/null +++ b/ishtar_common/templates/ishtar/forms/base_form.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} +{% load i18n inline_formset table_form from_dict %} +{% block extra_head %} +{{form.media}} +{% endblock %} + +{% block pre_container %} +<form enctype="multipart/form-data" action="." method="post"{% if confirm %} + onsubmit='return confirm("{{confirm}}");'{% endif %}>{% csrf_token %} +{% endblock %} +{% block content %} +<h2>{{page_name}}</h2> +<div class='form{% if not form.SEARCH_AND_SELECT %} container{% endif %}'> + {% if form.non_field_errors or form.errors %} + <div class="alert alert-danger"> + <div><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> + {% trans "Error on validation. Check all your fields. Modification not saved." %} + </div> + {% for key, error_details in form.errors.items %} + {% with field=key|from_dict:form.fields %}<strong>{{ field.label }}</strong>{{error_details}}{% endwith %} + {% endfor %} + </div> + {% endif %} + {% block form_head %} + {% endblock %} + {% bs_form form 0 True %} + {% for inline in inline_forms %} + {% bs_formset inline 0 True %} + {% endfor %} +</div> +{% endblock %} + +{% block footer %} +<div id="footer"> + {% if form.SEARCH_AND_SELECT %} + <p class="confirm-message">{% trans "Search and select an item in the table" %}</p> + {% endif %} + <div id='validation-bar' class="row text-center"> + <div class="col-sm"> + <button type="submit" id="submit_form" name='validate' + value="validate" class="btn btn-success"> + {% if submit_label %}{{submit_label}}{% else%}{% trans "Validate" %}{% endif %} + </button> + </div> + </div> + {% include 'ishtar/blocks/footer.html' %} +</div> +</form> +{% endblock %} + diff --git a/ishtar_common/templates/ishtar/forms/document.html b/ishtar_common/templates/ishtar/forms/document.html index 8e2683a03..fe3df8c74 100644 --- a/ishtar_common/templates/ishtar/forms/document.html +++ b/ishtar_common/templates/ishtar/forms/document.html @@ -1,48 +1,7 @@ -{% extends "base.html" %} -{% load i18n inline_formset table_form from_dict %} -{% block extra_head %} -{{form.media}} -{% endblock %} - -{% block pre_container %} -<form enctype="multipart/form-data" action="." method="post"{% if confirm %} - onsubmit='return confirm("{{confirm}}");'{% endif %}>{% csrf_token %} -{% endblock %} -{% block content %} -<h2>{{page_name}}</h2> -<div class='form{% if not form.SEARCH_AND_SELECT %} container{% endif %}'> - {% if form.non_field_errors or form.errors %} - <div class="alert alert-danger"> - <div><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> - {% trans "Error on validation. Check all your fields. Modification not saved." %} - </div> - {% for key, error_details in form.errors.items %} - {% with field=key|from_dict:form.fields %}<strong>{{ field.label }}</strong>{{error_details}}{% endwith %} - {% endfor %} - </div> - {% endif %} - {% if item_related_label %}<div class="alert alert-info"> +{% extends "ishtar/forms/base_form.html" %} +{% load i18n %} +{% block form_head %} +{% if item_related_label %}<div class="alert alert-info"> <strong>{% trans "Related items" %}{% trans ":" %}</strong> {{ item_related_label }} - </div>{% endif %} - {% bs_form form 0 True %} -</div> -{% endblock %} - -{% block footer %} -<div id="footer"> - {% if form.SEARCH_AND_SELECT %} - <p class="confirm-message">{% trans "Search and select an item in the table" %}</p> - {% endif %} - <div id='validation-bar' class="row text-center"> - <div class="col-sm"> - <button type="submit" id="submit_form" name='validate' - value="validate" class="btn btn-success"> - {% if submit_label %}{{submit_label}}{% else%}{% trans "Validate" %}{% endif %} - </button> - </div> - </div> - {% include 'ishtar/blocks/footer.html' %} -</div> -</form> +</div>{% endif %} {% endblock %} - diff --git a/ishtar_common/templates/widgets/GramKilogramWidget.html b/ishtar_common/templates/widgets/GramKilogramWidget.html deleted file mode 100644 index 27c066d13..000000000 --- a/ishtar_common/templates/widgets/GramKilogramWidget.html +++ /dev/null @@ -1,21 +0,0 @@ -<div class="input-group"> - <input class="area_widget form-control" type="text"{{final_attrs|safe}}> - <div class="input-group-append"> - <div class="input-group-text"> - {{unit}} (<span id="kg_{{id}}">0</span> kg) - </div> - </div> -</div> -<script type="text/javascript"><!--// - function evaluate_{{safe_id}}(){ - value = parseFloat($("#{{id}}").val()); - if(!isNaN(value)){ - value = value/1000; - } else { - value = 0; - } - $("#kg_{{id}}").html(value); - } - $("#{{id}}").keyup(evaluate_{{safe_id}}); - $(document).ready(evaluate_{{safe_id}}()); -//--></script> diff --git a/ishtar_common/templates/widgets/SquareMeterWidget.html b/ishtar_common/templates/widgets/SquareMeterWidget.html deleted file mode 100644 index 09e93b425..000000000 --- a/ishtar_common/templates/widgets/SquareMeterWidget.html +++ /dev/null @@ -1,21 +0,0 @@ -<div class="input-group"> - <input class="area_widget form-control" type="text"{{final_attrs|safe}}> - <div class="input-group-append"> - <div class="input-group-text"> - {{unit}} (<span id="ha_{{id}}">0</span> ha) - </div> - </div> -</div> -<script type="text/javascript"><!--// - function evaluate_{{safe_id}}(){ - value = parseFloat($("#{{id}}").val()); - if(!isNaN(value)){ - value = value/10000; - } else { - value = 0; - } - $("#ha_{{id}}").html(value); - } - $("#{{id}}").keyup(evaluate_{{safe_id}}); - $(document).ready(evaluate_{{safe_id}}()); -//--></script> diff --git a/ishtar_common/templates/widgets/CentimeterMeterWidget.html b/ishtar_common/templates/widgets/UnitWidget.html index 00c1614b5..46b883352 100644 --- a/ishtar_common/templates/widgets/CentimeterMeterWidget.html +++ b/ishtar_common/templates/widgets/UnitWidget.html @@ -2,7 +2,7 @@ <input class="area_widget form-control" type="text"{{final_attrs|safe}}> <div class="input-group-append"> <div class="input-group-text"> - {{unit}} (<span id="meter_{{id}}">0</span> m) + {{unit1}} (<span id="subunit_{{id}}">0</span> {{unit2}}) </div> </div> </div> @@ -10,11 +10,11 @@ function evaluate_{{safe_id}}(){ value = parseFloat($("#{{id}}").val()); if(!isNaN(value)){ - value = value/100; + value = value/{{factor}}; } else { value = 0; } - $("#meter_{{id}}").html(value); + $("#subunit_{{id}}").html(value); } $("#{{id}}").keyup(evaluate_{{safe_id}}); $(document).ready(evaluate_{{safe_id}}()); diff --git a/ishtar_common/templatetags/table_form.py b/ishtar_common/templatetags/table_form.py index d5b20da26..819d99c50 100644 --- a/ishtar_common/templatetags/table_form.py +++ b/ishtar_common/templatetags/table_form.py @@ -16,6 +16,15 @@ def bs_form(context, form, position=0, no_error=False): 'no_error': no_error} +@register.inclusion_tag('blocks/bs_formset_snippet.html', takes_context=True) +def bs_formset(context, formset, position=0, no_error=False): + user = context['user'] + show_field_number = user.ishtaruser and user.ishtaruser.show_field_number() + return {'formset': formset, 'odd': position % 2, + 'show_field_number': show_field_number, + 'no_error': no_error} + + @register.inclusion_tag('blocks/bs_compact_form_snippet.html', takes_context=True) def bs_compact_form(context, form, position=0): diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index 8f95f4521..008925276 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -521,18 +521,23 @@ class CustomWidget(forms.TextInput): class SquareMeterWidget(CustomWidget): - TEMPLATE = "widgets/SquareMeterWidget.html" - EXTRA_DCT = {"unit": settings.SURFACE_UNIT_LABEL} + TEMPLATE = "widgets/UnitWidget.html" + EXTRA_DCT = {"unit1": "m²", "unit2": "ha", "factor": 10000} class GramKilogramWidget(CustomWidget): - TEMPLATE = "widgets/GramKilogramWidget.html" - EXTRA_DCT = {"unit": "g"} + TEMPLATE = "widgets/UnitWidget.html" + EXTRA_DCT = {"unit1": "g", "unit2": "kg", "factor": 1000} class CentimeterMeterWidget(CustomWidget): - TEMPLATE = "widgets/CentimeterMeterWidget.html" - EXTRA_DCT = {"unit": "cm"} + TEMPLATE = "widgets/UnitWidget.html" + EXTRA_DCT = {"unit1": "cm", "unit2": "m", "factor": 100} + + +class MeterKilometerWidget(CustomWidget): + TEMPLATE = "widgets/UnitWidget.html" + EXTRA_DCT = {"unit1": "m", "unit2": "km", "factor": 1000} AreaWidget = forms.TextInput |