From faa399bb5232883d51f848a2795d33853896cc8f Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 9 Jul 2021 19:20:12 +0200 Subject: Preventive file: work on inlines --- archaeological_files/admin.py | 26 +- archaeological_files/forms.py | 98 +++---- .../migrations/0106_auto_20210708_1044.py | 299 --------------------- .../migrations/0106_auto_20210720_1203.py | 244 +++++++++++++++++ archaeological_files/models.py | 110 ++++---- .../templates/ishtar/forms/preventive_detail.html | 30 ++- archaeological_files/views.py | 64 +++-- .../templates/blocks/bs_field_snippet.html | 17 +- 8 files changed, 454 insertions(+), 434 deletions(-) delete mode 100644 archaeological_files/migrations/0106_auto_20210708_1044.py create mode 100644 archaeological_files/migrations/0106_auto_20210720_1203.py diff --git a/archaeological_files/admin.py b/archaeological_files/admin.py index 9ba3d17d5..6ac40a69b 100644 --- a/archaeological_files/admin.py +++ b/archaeological_files/admin.py @@ -20,6 +20,7 @@ from ajax_select import make_ajax_form from django.conf import settings +from django.contrib import admin from ishtar_common.apps import admin_site from ishtar_common.admin import HistorizedObjectAdmin, GeneralTypeAdmin @@ -65,13 +66,30 @@ admin_site.register(models.File, FileAdmin) general_models = [ - models.FileType, models.PermitType, models.Job, models.GenericEquipmentType, - models.EquipmentType, models.TechnicalService, + models.FileType, models.PermitType, models.Job, + models.GenericEquipmentServiceType, ] if settings.COUNTRY == "fr": general_models.append(models.SaisineType) for model in general_models: admin_site.register(model, GeneralTypeAdmin) -admin_site.register(models.EquipmentCost) -admin_site.register(models.TechnicalServiceCost) + +class EquipmentServiceTypeAdmin(GeneralTypeAdmin): + list_filter = ("available", 'generic_equipment_type') + + +admin_site.register(models.EquipmentServiceType, EquipmentServiceTypeAdmin) + + +class EquipmentServiceCostAdmin(admin.ModelAdmin): + search_fields = ( + "equipment_service_type__label", + "service_provider", + ) + list_filter = ("available",) + list_display = ["equipment_service_type", "specificity", "unitary_cost", "unit", + "flat_rate", "available"] + + +admin_site.register(models.EquipmentServiceCost, EquipmentServiceCostAdmin) diff --git a/archaeological_files/forms.py b/archaeological_files/forms.py index 8b5b3d00d..3c113c665 100644 --- a/archaeological_files/forms.py +++ b/archaeological_files/forms.py @@ -26,7 +26,7 @@ import datetime from django import forms from django.conf import settings from django.core import validators -from django.forms.formsets import formset_factory, BaseFormSet +from django.forms.formsets import formset_factory from django.utils.functional import lazy from django.utils.safestring import mark_safe from ishtar_common.utils import ugettext_lazy as _ @@ -592,6 +592,12 @@ class FileBaseFormset(forms.BaseModelFormSet): if self.instance: self.queryset = self.model.objects.filter(file_id=self.instance.pk) + def get_form_kwargs(self, index): + kwargs = super(FileBaseFormset, self).get_form_kwargs(index) + if self.instance: + kwargs['file_id'] = self.instance.pk + return kwargs + INLINE_JOB_FIELDS = ["man_by_day_planned", "days_planned", "man_by_day_worked", "days_worked"] @@ -607,12 +613,25 @@ JOB_LABELS = { "days_planned": _("Days"), "man_by_day_worked": _("Man by day"), "days_worked": _("Days"), + "Job": _("Job") } -class PreventiveFileJobForm(forms.ModelForm): +class PreventiveFileForm(forms.ModelForm): file_id = forms.IntegerField(widget=forms.HiddenInput) + def __init__(self, *args, **kwargs): + self.file_id = kwargs.pop("file_id") + super().__init__(*args, **kwargs) + self.fields["file_id"].initial = self.file_id + + def save(self, commit=True): + if not self.errors: + self.instance.file_id = self.file_id + return super().save(commit=commit) + + +class PreventiveFileJobForm(PreventiveFileForm): class Meta: model = models.PreventiveFileJob fields = ["job"] + INLINE_JOB_FIELDS @@ -633,9 +652,7 @@ PreventiveFileJobFormSet.form_slug = "preventive-030-post-excavation" PreventiveFileJobFormSet.dynamic_add = True -class PreventiveFileGroundJobForm(forms.ModelForm): - file_id = forms.IntegerField(widget=forms.HiddenInput) - +class PreventiveFileGroundJobForm(PreventiveFileForm): class Meta: model = models.PreventiveFileGroundJob fields = ["job"] + INLINE_JOB_FIELDS @@ -663,67 +680,54 @@ COST_LABELS = { "days_planned": _("Days / weeks / months"), "quantity_by_day_worked": _("Quantity"), "days_worked": _("Days / weeks / months"), + "equipment_service_cost": _("Equipment / service") } INLINE_COST_FIELDS = [ "quantity_by_day_planned", "days_planned", "quantity_by_day_worked", "days_worked"] COST_WIDGETS = { - "quantity_by_day_planned": forms.NumberInput(attrs={"class": "w-50 form-planned"}), + "quantity_by_day_planned": forms.NumberInput( + attrs={"class": "w-50 form-planned"}), "days_planned": forms.NumberInput(attrs={"class": "w-50 form-planned"}), "quantity_by_day_worked": forms.NumberInput(attrs={"class": "w-50 form-worked"}), - "days_worked": forms.NumberInput(attrs={"class": "w-50 form-worked"}) + "days_worked": forms.NumberInput(attrs={"class": "w-50 form-worked"}), + "equipment_service_cost": forms.Select( + attrs={"class":"form-cost", "bs_col_width": "col-12"}) } -class PreventiveFileEquipmentForm(forms.ModelForm): - file_id = forms.IntegerField(widget=forms.HiddenInput) - - class Meta: - model = models.PreventiveFileEquipmentCost - fields = ["equipment_cost"] + INLINE_COST_FIELDS - widgets = COST_WIDGETS - labels = COST_LABELS - - -class PreventiveFileEquipmentBaseFormSet(FileBaseFormset): - model = models.PreventiveFileEquipmentCost - - -PreventiveFileEquipmentFormSet = formset_factory( - PreventiveFileEquipmentForm, formset=PreventiveFileEquipmentBaseFormSet, - can_delete=True -) -PreventiveFileEquipmentFormSet.form_label = _("Equipment") -PreventiveFileEquipmentFormSet.form_admin_name = _("Preventive file - 050 - Equipments") -PreventiveFileEquipmentFormSet.form_slug = "preventive-050-equipments" -PreventiveFileEquipmentFormSet.dynamic_add = True - - -class PreventiveFileTechnicalServiceForm(forms.ModelForm): - file_id = forms.IntegerField(widget=forms.HiddenInput) +class PreventiveFileEquipmentServiceForm(PreventiveFileForm): + type_filter = None class Meta: - model = models.PreventiveFileTechnicalServiceCost - fields = ["technical_service_cost"] + INLINE_COST_FIELDS + model = models.PreventiveFileEquipmentServiceCost + fields = ["equipment_service_cost"] + INLINE_COST_FIELDS widgets = COST_WIDGETS labels = COST_LABELS + def __init__(self, *args, **kwargs): + super(PreventiveFileEquipmentServiceForm, self).__init__(*args, **kwargs) + q = models.EquipmentServiceCost.objects.filter( + available=True, + equipment_service_type__parent__isnull=True, + equipment_service_type__generic_equipment_type__txt_idx=self.type_filter + ) -class PreventiveFileTechnicalServiceBaseFormSet(FileBaseFormset): - model = models.PreventiveFileTechnicalServiceCost + self.unities = {} + unit_dict = dict(models.ES_UNITS) + choices = [("", "--")] + for cost in q.all(): + choices.append((cost.pk, str(cost))) + if cost.unit: + self.unities[cost.pk] = unit_dict[cost.unit] + self.fields["equipment_service_cost"].choices = choices + self.fields["quantity_by_day_planned"].initial = 1 + self.fields["days_planned"].unit = "..." -PreventiveFileTechnicalServiceFormSet = formset_factory( - PreventiveFileTechnicalServiceForm, - formset=PreventiveFileTechnicalServiceBaseFormSet, can_delete=True -) -PreventiveFileTechnicalServiceFormSet.form_label = _("Technical Services") -PreventiveFileTechnicalServiceFormSet.form_admin_name = _( - "Preventive file - 060 - TechnicalServices" -) -PreventiveFileTechnicalServiceFormSet.form_slug = "preventive-060-technical_services" -PreventiveFileTechnicalServiceFormSet.dynamic_add = True +class PreventiveFileEquipmentServiceBaseFormSet(FileBaseFormset): + model = models.PreventiveFileEquipmentServiceCost class AdministrativeActFileModifySelect(TableSelect): diff --git a/archaeological_files/migrations/0106_auto_20210708_1044.py b/archaeological_files/migrations/0106_auto_20210708_1044.py deleted file mode 100644 index c09f9338f..000000000 --- a/archaeological_files/migrations/0106_auto_20210708_1044.py +++ /dev/null @@ -1,299 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.28 on 2021-07-08 10:44 -from __future__ import unicode_literals - -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import ishtar_common.models_common -import re - - -class Migration(migrations.Migration): - - dependencies = [ - ('archaeological_files', '0105_auto_20201204_1442'), - ] - - operations = [ - migrations.CreateModel( - name='EquipmentCost', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('service_provider', models.CharField(default='-', max_length=200, verbose_name='Service provider')), - ('flat_rate', models.BooleanField(default=False, verbose_name='Flat rate')), - ('daily_cost', models.FloatField(blank=True, null=True, verbose_name='Daily cost')), - ('monday', models.BooleanField(default=True, verbose_name='Monday')), - ('tuesday', models.BooleanField(default=True, verbose_name='Tuesday')), - ('wednesday', models.BooleanField(default=True, verbose_name='Wednesday')), - ('thursday', models.BooleanField(default=True, verbose_name='Thursday')), - ('friday', models.BooleanField(default=True, verbose_name='Friday')), - ('order', models.IntegerField(default=10, verbose_name='Order')), - ], - options={ - 'verbose_name': 'Equipment cost', - 'verbose_name_plural': 'Equipment costs', - }, - ), - migrations.CreateModel( - name='EquipmentType', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('label', models.TextField(verbose_name='Label')), - ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z', 32), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('available', models.BooleanField(default=True, verbose_name='Available')), - ], - options={ - 'verbose_name': 'Equipment type', - 'verbose_name_plural': 'Equipment types', - 'ordering': ('label',), - }, - bases=(ishtar_common.models_common.Cached, models.Model), - ), - migrations.CreateModel( - name='GenericEquipmentType', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('label', models.TextField(verbose_name='Label')), - ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z', 32), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('available', models.BooleanField(default=True, verbose_name='Available')), - ], - options={ - 'verbose_name': 'Generic equipment type', - 'verbose_name_plural': 'Generic equipment types', - 'ordering': ('label',), - }, - bases=(ishtar_common.models_common.Cached, models.Model), - ), - migrations.CreateModel( - name='Job', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('label', models.TextField(verbose_name='Label')), - ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z', 32), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('available', models.BooleanField(default=True, verbose_name='Available')), - ('ground_daily_cost', models.FloatField(blank=True, null=True, verbose_name='Ground daily cost')), - ('daily_cost', models.FloatField(blank=True, null=True, verbose_name='Daily cost')), - ('permanent_contract', models.NullBooleanField(verbose_name='Is a permanent contract')), - ('default_daily_need_on_ground', models.FloatField(default=0, verbose_name='Default daily number needed on the ground')), - ('default_daily_need', models.FloatField(default=0, verbose_name='Default daily number needed')), - ('order', models.IntegerField(default=10, verbose_name='Order')), - ('parents', models.ManyToManyField(blank=True, help_text='Auto-add this job when a parent is added', related_name='_job_parents_+', to='archaeological_files.Job', verbose_name='Parents')), - ], - options={ - 'verbose_name': 'Job', - 'verbose_name_plural': 'Jobs', - 'ordering': ('label',), - }, - bases=(ishtar_common.models_common.Cached, models.Model), - ), - migrations.CreateModel( - name='PreventiveFileEquipmentCost', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity_by_day_planned', models.FloatField(blank=True, null=True, verbose_name='Quantity by day - planned')), - ('days_planned', models.FloatField(blank=True, null=True, verbose_name='Days - planned')), - ('quantity_by_day_worked', models.FloatField(blank=True, null=True, verbose_name='Quantity by day - worked')), - ('days_worked', models.FloatField(blank=True, null=True, verbose_name='Days - worked')), - ('equipment_cost', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.EquipmentCost')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='PreventiveFileGroundJob', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='PreventiveFileJob', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='PreventiveFileTechnicalServiceCost', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity_by_day_planned', models.FloatField(blank=True, null=True, verbose_name='Quantity by day - planned')), - ('days_planned', models.FloatField(blank=True, null=True, verbose_name='Days - planned')), - ('quantity_by_day_worked', models.FloatField(blank=True, null=True, verbose_name='Quantity by day - worked')), - ('days_worked', models.FloatField(blank=True, null=True, verbose_name='Days - worked')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='TechnicalService', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('label', models.TextField(verbose_name='Label')), - ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z', 32), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('available', models.BooleanField(default=True, verbose_name='Available')), - ], - options={ - 'verbose_name': 'Technical service', - 'verbose_name_plural': 'Technical services', - 'ordering': ('label',), - }, - bases=(ishtar_common.models_common.Cached, models.Model), - ), - migrations.CreateModel( - name='TechnicalServiceCost', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('service_provider', models.CharField(default='-', max_length=200, verbose_name='Service provider')), - ('flat_rate', models.BooleanField(default=False, verbose_name='Flat rate')), - ('unitary_cost', models.FloatField(blank=True, null=True, verbose_name='Unitary cost')), - ('unit', models.CharField(blank=True, choices=[('D', 'Days'), ('M', 'Linear meter')], max_length=1, null=True, verbose_name='Unit')), - ('order', models.IntegerField(default=10, verbose_name='Order')), - ('parents', models.ManyToManyField(blank=True, help_text='Auto-add this cost when a parent is added', related_name='_technicalservicecost_parents_+', to='archaeological_files.TechnicalServiceCost', verbose_name='Parents')), - ('technical_service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.TechnicalService', verbose_name='Technical service')), - ], - options={ - 'verbose_name': 'Technical service cost', - 'verbose_name_plural': 'Technical service costs', - }, - ), - migrations.AddField( - 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.AddField( - model_name='preventivefiletechnicalservicecost', - name='file', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='technical_service_costs', to='archaeological_files.File'), - ), - migrations.AddField( - model_name='preventivefiletechnicalservicecost', - name='technical_service_cost', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.TechnicalServiceCost'), - ), - migrations.AddField( - model_name='preventivefilejob', - name='file', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='archaeological_files.File'), - ), - migrations.AddField( - model_name='preventivefilejob', - name='job', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.Job'), - ), - migrations.AddField( - model_name='preventivefilegroundjob', - name='file', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ground_jobs', to='archaeological_files.File'), - ), - migrations.AddField( - model_name='preventivefilegroundjob', - name='job', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.Job'), - ), - 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'), - ), - migrations.AddField( - model_name='equipmenttype', - name='generic_equipment_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.GenericEquipmentType', verbose_name='Generic type'), - ), - migrations.AddField( - model_name='equipmentcost', - name='equipment_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.EquipmentType', verbose_name='Equipment'), - ), - migrations.AddField( - model_name='equipmentcost', - name='parents', - field=models.ManyToManyField(blank=True, help_text='Auto-add this cost when a parent is added', related_name='_equipmentcost_parents_+', to='archaeological_files.EquipmentCost', verbose_name='Parents'), - ), - ] diff --git a/archaeological_files/migrations/0106_auto_20210720_1203.py b/archaeological_files/migrations/0106_auto_20210720_1203.py new file mode 100644 index 000000000..86ff78ec6 --- /dev/null +++ b/archaeological_files/migrations/0106_auto_20210720_1203.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2021-07-20 12:03 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import ishtar_common.models_common +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_files', '0105_auto_20201204_1442'), + ] + + operations = [ + migrations.CreateModel( + name='EquipmentServiceCost', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('service_provider', models.CharField(blank=True, default='', max_length=200, verbose_name='Service provider')), + ('flat_rate', models.BooleanField(default=False, verbose_name='Flat rate')), + ('unitary_cost', models.FloatField(blank=True, null=True, verbose_name='Unitary cost')), + ('unit', models.CharField(blank=True, choices=[('D', 'days'), ('W', 'weeks'), ('M', 'months'), ('L', 'linear meter')], max_length=1, null=True, verbose_name='Unit')), + ('specificity', models.CharField(blank=True, default='', max_length=200, verbose_name='Specificity')), + ('order', models.IntegerField(default=10, verbose_name='Order')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ], + options={ + 'verbose_name': 'Equipment/service cost', + 'verbose_name_plural': 'Equipment/service costs', + 'ordering': ('order', 'equipment_service_type__label'), + }, + ), + migrations.CreateModel( + name='EquipmentServiceType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.TextField(verbose_name='Label')), + ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z', 32), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ('order', models.IntegerField(default=10, verbose_name='Order')), + ], + options={ + 'verbose_name': 'Equipment/service type', + 'verbose_name_plural': 'Equipment/service types', + 'ordering': ('order', 'label'), + }, + bases=(ishtar_common.models_common.Cached, models.Model), + ), + migrations.CreateModel( + name='GenericEquipmentServiceType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.TextField(verbose_name='Label')), + ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z', 32), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ('order', models.IntegerField(default=10, verbose_name='Order')), + ], + options={ + 'verbose_name': 'Generic equipment type', + 'verbose_name_plural': 'Generic equipment types', + 'ordering': ('order', 'label'), + }, + bases=(ishtar_common.models_common.Cached, models.Model), + ), + migrations.CreateModel( + name='Job', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.TextField(verbose_name='Label')), + ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z', 32), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ('ground_daily_cost', models.FloatField(blank=True, null=True, verbose_name='Ground daily cost')), + ('daily_cost', models.FloatField(blank=True, null=True, verbose_name='Daily cost')), + ('permanent_contract', models.NullBooleanField(verbose_name='Is a permanent contract')), + ('default_daily_need_on_ground', models.FloatField(default=0, verbose_name='Default daily number needed on the ground')), + ('default_daily_need', models.FloatField(default=0, verbose_name='Default daily number needed')), + ('order', models.IntegerField(default=10, verbose_name='Order')), + ('parents', models.ManyToManyField(blank=True, help_text='Auto-add this job when a parent is added', related_name='_job_parents_+', to='archaeological_files.Job', verbose_name='Parents')), + ], + options={ + 'verbose_name': 'Job', + 'verbose_name_plural': 'Jobs', + 'ordering': ('order', 'label'), + }, + bases=(ishtar_common.models_common.Cached, models.Model), + ), + migrations.CreateModel( + name='PreventiveFileEquipmentServiceCost', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity_by_day_planned', models.FloatField(blank=True, null=True, verbose_name='Quantity by day - planned')), + ('days_planned', models.FloatField(blank=True, null=True, verbose_name='Days - planned')), + ('quantity_by_day_worked', models.FloatField(blank=True, null=True, verbose_name='Quantity by day - worked')), + ('days_worked', models.FloatField(blank=True, null=True, verbose_name='Days - worked')), + ('equipment_service_cost', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.EquipmentServiceCost')), + ], + options={ + 'ordering': ('equipment_service_cost',), + }, + ), + migrations.CreateModel( + name='PreventiveFileGroundJob', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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={ + 'ordering': ('job',), + }, + ), + migrations.CreateModel( + name='PreventiveFileJob', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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={ + 'ordering': ('job',), + }, + ), + migrations.AddField( + 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.AddField( + model_name='preventivefilejob', + name='file', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='archaeological_files.File'), + ), + migrations.AddField( + model_name='preventivefilejob', + name='job', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.Job'), + ), + migrations.AddField( + model_name='preventivefilegroundjob', + name='file', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ground_jobs', to='archaeological_files.File'), + ), + migrations.AddField( + model_name='preventivefilegroundjob', + name='job', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.Job'), + ), + migrations.AddField( + model_name='preventivefileequipmentservicecost', + name='file', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='equipment_costs', to='archaeological_files.File'), + ), + migrations.AddField( + model_name='equipmentservicetype', + name='generic_equipment_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.GenericEquipmentServiceType', verbose_name='Generic type'), + ), + migrations.AddField( + model_name='equipmentservicetype', + name='parent', + field=models.ForeignKey(blank=True, help_text='Auto-add this cost when a parent is added', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='archaeological_files.EquipmentServiceType', verbose_name='Parent'), + ), + migrations.AddField( + model_name='equipmentservicecost', + name='equipment_service_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='archaeological_files.EquipmentServiceType', verbose_name='Equipment/Service'), + ), + ] diff --git a/archaeological_files/models.py b/archaeological_files/models.py index 87abd4083..334e8faa4 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -88,84 +88,72 @@ class Job(GeneralType): class Meta: verbose_name = _("Job") verbose_name_plural = _("Jobs") - ordering = ("label",) + ordering = ("order", "label",) + +class GenericEquipmentServiceType(GeneralType): + order = models.IntegerField(_("Order"), default=10) -class GenericEquipmentType(GeneralType): class Meta: verbose_name = _("Generic equipment type") verbose_name_plural = _("Generic equipment types") - ordering = ("label",) + ordering = ("order", "label",) -class EquipmentType(GeneralType): +class EquipmentServiceType(GeneralType): generic_equipment_type = models.ForeignKey( - GenericEquipmentType, verbose_name=_("Generic type")) - - class Meta: - verbose_name = _("Equipment type") - verbose_name_plural = _("Equipment types") - ordering = ("label",) - - -class EquipmentCost(models.Model): - service_provider = models.CharField(_("Service provider"), max_length=200, - default="-") - equipment_type = models.ForeignKey( - EquipmentType, verbose_name=_("Equipment")) - flat_rate = models.BooleanField(_("Flat rate"), default=False) - daily_cost = models.FloatField(_("Daily cost"), blank=True, null=True) - monday = models.BooleanField(_("Monday"), default=True) - tuesday = models.BooleanField(_("Tuesday"), default=True) - wednesday = models.BooleanField(_("Wednesday"), default=True) - thursday = models.BooleanField(_("Thursday"), default=True) - friday = models.BooleanField(_("Friday"), default=True) + GenericEquipmentServiceType, verbose_name=_("Generic type")) order = models.IntegerField(_("Order"), default=10) - parents = models.ManyToManyField( + parent = models.ForeignKey( "self", blank=True, - verbose_name=_("Parents"), - help_text=_("Auto-add this cost when a parent is added") + null=True, + on_delete=models.CASCADE, + verbose_name=_("Parent"), + help_text=_("Auto-add this cost when a parent is added"), + related_name="children" ) class Meta: - verbose_name = _("Equipment cost") - verbose_name_plural = _("Equipment costs") - - -class TechnicalService(GeneralType): - class Meta: - verbose_name = _("Technical service") - verbose_name_plural = _("Technical services") - ordering = ("label",) + verbose_name = _("Equipment/service type") + verbose_name_plural = _("Equipment/service types") + ordering = ("order", "label",) -TECH_UNITS = ( - ("D", _("Days")), - ("M", _("Linear meter")), +ES_UNITS = ( + ("D", _("days")), + ("W", _("weeks")), + ("M", _("months")), + ("L", _("linear meter")), ) -class TechnicalServiceCost(models.Model): - service_provider = models.CharField(_("Service provider"), max_length=200, - default="-") - technical_service = models.ForeignKey( - TechnicalService, verbose_name=_("Technical service")) +class EquipmentServiceCost(models.Model): + equipment_service_type = models.ForeignKey( + EquipmentServiceType, verbose_name=_("Equipment/Service")) + service_provider = models.CharField( + _("Service provider"), max_length=200, blank=True, default="") flat_rate = models.BooleanField(_("Flat rate"), default=False) unitary_cost = models.FloatField(_("Unitary cost"), blank=True, null=True) - unit = models.CharField(_("Unit"), max_length=1, choices=TECH_UNITS, + unit = models.CharField(_("Unit"), max_length=1, choices=ES_UNITS, blank=True, null=True) + specificity = models.CharField(_("Specificity"), blank=True, + max_length=200, default="") order = models.IntegerField(_("Order"), default=10) - parents = models.ManyToManyField( - "self", - blank=True, - verbose_name=_("Parents"), - help_text=_("Auto-add this cost when a parent is added") - ) + available = models.BooleanField(_("Available"), default=True) class Meta: - verbose_name = _("Technical service cost") - verbose_name_plural = _("Technical service costs") + verbose_name = _("Equipment/service cost") + verbose_name_plural = _("Equipment/service costs") + ordering = ("order", "equipment_service_type__label",) + + def __str__(self): + lbl = str(self.equipment_service_type) + if self.specificity: + lbl += " - " + self.specificity + if self.service_provider: + lbl += f" ({self.service_provider})" + return lbl class FileType(GeneralType): @@ -1205,11 +1193,17 @@ class PreventiveFileGroundJob(ManDays): file = models.ForeignKey(File, related_name="ground_jobs") job = models.ForeignKey(Job) + class Meta: + ordering = ("job",) + class PreventiveFileJob(ManDays): file = models.ForeignKey(File, related_name="jobs") job = models.ForeignKey(Job) + class Meta: + ordering = ("job",) + class TechDays(models.Model): quantity_by_day_planned = models.FloatField( @@ -1237,11 +1231,9 @@ class TechDays(models.Model): return self.days_worked * self.quantity_by_day_worked -class PreventiveFileEquipmentCost(TechDays): +class PreventiveFileEquipmentServiceCost(TechDays): file = models.ForeignKey(File, related_name="equipment_costs") - equipment_cost = models.ForeignKey(EquipmentCost) + equipment_service_cost = models.ForeignKey(EquipmentServiceCost) - -class PreventiveFileTechnicalServiceCost(TechDays): - file = models.ForeignKey(File, related_name="technical_service_costs") - technical_service_cost = models.ForeignKey(TechnicalServiceCost) + class Meta: + ordering = ("equipment_service_cost",) diff --git a/archaeological_files/templates/ishtar/forms/preventive_detail.html b/archaeological_files/templates/ishtar/forms/preventive_detail.html index 20adcccfb..5b6f7f9ed 100644 --- a/archaeological_files/templates/ishtar/forms/preventive_detail.html +++ b/archaeological_files/templates/ishtar/forms/preventive_detail.html @@ -17,12 +17,15 @@
- +
- {{block.super}} + {% for inline in inline_forms %} +

{{inline.form_label}}

+ {% bs_formset inline 0 True %} + {% endfor %} {% endblock %} @@ -31,7 +34,7 @@ {% block end_js %} {{block.super}} var check_planned_value = function() { - if (this.value == 'false'){ + if (this.value === 'false'){ $(".form-planned").parent().hide() $(".form-worked").parent().show() } else { @@ -39,7 +42,24 @@ $(".form-worked").parent().hide() } }; - $('input[type=radio][name=planned-toggle]').change(check_planned_value); - $(document).ready(check_planned_value); + + const cost_units = {{% for unit_pk, value in form_unities %}{% if forloop.counter0 %},{% endif %} + {{unit_pk}}: "{{value}}"{% endfor %} + }; + var update_units = function() { + $(".form-cost").each(function(){ + var unit = cost_units[$(this).val()]; + if (!unit){ + unit = "..."; + } + $(this).parent().parent().find(".unit-label").html(unit); + }); + }; + $(document).ready(function(){ + $('input[type=radio][name=planned-toggle]').change(check_planned_value); + $(".form-cost").change(update_units); + check_planned_value(); + update_units(); + }); {% endblock %} {# #} diff --git a/archaeological_files/views.py b/archaeological_files/views.py index 366ef3754..90d41c68b 100644 --- a/archaeological_files/views.py +++ b/archaeological_files/views.py @@ -23,6 +23,7 @@ import re from django.core.urlresolvers import reverse from django.db.models import Q from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.forms.formsets import formset_factory from django.views.generic.edit import UpdateView from django.shortcuts import redirect, render from ishtar_common.utils import ugettext_lazy as _ @@ -361,37 +362,41 @@ def reset_wizards(request): class MixFormFormsetUpdateView(UpdateView): form_inlines_class = [] + def get_form_inlines_class(self): + return self.form_inlines_class + def get(self, request, *args, **kwargs): self.object = self.get_object() self.inline_forms = [ inline(instance=self.object, prefix=getattr(inline, "form_slug", "form")) - for inline in self.form_inlines_class + for inline in self.get_form_inlines_class() ] return super(MixFormFormsetUpdateView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() - attrs = {"instance": self.object} form = self.form_class(data=request.POST, instance=self.object) - inline_forms = [ + self.inline_forms = [ inline(instance=self.object, data=request.POST, prefix=getattr(inline, "form_slug", "form")) - for inline in self.form_inlines_class + for inline in self.get_form_inlines_class() ] - if form.is_valid() and all((inline.is_valid() for inline in inline_forms)): - return self.form_valid(form, inline_forms) + if form.is_valid() and all((inline.is_valid() for inline in self.inline_forms)): + return self.form_valid(form, self.inline_forms) else: - return self.form_invalid(form, inline_forms) + return self.form_invalid(form, self.inline_forms) def form_valid(self, form, inline_forms): self.object = form.save() + print("OK") for inline in inline_forms: - # save inlines... + inline.save() pass return HttpResponseRedirect(self.get_success_url()) def form_invalid(self, form, inline_forms): + print("NOP") return self.render_to_response( self.get_context_data(form=form, inlines=inline_forms) ) @@ -405,15 +410,35 @@ class MixFormFormsetUpdateView(UpdateView): 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_form_inlines_class(self): + inlines = [ + forms.PreventiveFileGroundJobFormSet, + forms.PreventiveFileJobFormSet, + ] + self.inline_types = list( + models.GenericEquipmentServiceType.objects.filter(available=True).all()) + + for inline_type in self.inline_types: + form_class_name = "".join([ + t.capitalize() + for t in inline_type.txt_idx.replace( + '-', ' ').replace('_', ' ').split(" ")]) + "Formset" + form = type(form_class_name, + (forms.PreventiveFileEquipmentServiceForm,), + {"type_filter": inline_type.txt_idx}) + formset = formset_factory( + form, formset=forms.PreventiveFileEquipmentServiceBaseFormSet, + can_delete=True + ) + formset.form_label = str(inline_type) + formset.form_slug = inline_type.txt_idx + formset.dynamic_add = True + inlines.append(formset) + return inlines + def get_success_url(self): return reverse("file_modification") + "?open_item={}".format(self.object.pk) @@ -438,4 +463,13 @@ class PreventiveEditView(IshtarMixin, LoginRequiredMixin, MixFormFormsetUpdateVi kwargs["initial"] = initial kwargs["user"] = self.request.user self.file = file - return kwargs \ No newline at end of file + return kwargs + + def get_context_data(self, **kwargs): + context = super(PreventiveEditView, self).get_context_data(**kwargs) + unities = {} + for inline_formset in context["inline_forms"]: + if inline_formset.forms and hasattr(inline_formset.forms[0], "unities"): + unities.update(inline_formset.forms[0].unities) + context["form_unities"] = unities.items() + return context diff --git a/ishtar_common/templates/blocks/bs_field_snippet.html b/ishtar_common/templates/blocks/bs_field_snippet.html index beecf624f..86ec89e84 100644 --- a/ishtar_common/templates/blocks/bs_field_snippet.html +++ b/ishtar_common/templates/blocks/bs_field_snippet.html @@ -1,5 +1,5 @@ {% load i18n %} -
{% if field.label %}{{ field.label_tag }}{% endif %} {% if extra_field_label %}{% endif %} @@ -7,17 +7,24 @@ {% if field.field.order_number %} {{field.field.order_number}} {% endif %}{% endif %} - {% if field.help_text %} + {% if field.help_text or field.field.unit %}
{% endif %} {{field|safe}} - {% if field.help_text %} + {% if field.help_text or field.field.unit %} + {% if field.field.unit %} + + {{field.field.unit}} + + {% endif %} + {% if field.help_text %} + {% endif %} {% if field.errors %}
{{ field.errors }} @@ -25,11 +32,11 @@
- {{field.help_text}} + {{field.hep_text}}
{% endif %} - {% if field.errors and not field.help_text %} + {% if field.errors and not field.help_text and not field.unit %}
{{ field.errors }}
{% endif %} -- cgit v1.2.3