diff --git a/shimatta_kenkyusho/parts/admin.py b/shimatta_kenkyusho/parts/admin.py index 046e01b..95de51e 100644 --- a/shimatta_kenkyusho/parts/admin.py +++ b/shimatta_kenkyusho/parts/admin.py @@ -9,6 +9,7 @@ admin.site.register(parts_models.Manufacturer) admin.site.register(parts_models.Storage) admin.site.register(parts_models.Stock) admin.site.register(parts_models.ComponentParameter) +admin.site.register(parts_models.PackageParameter) admin.site.register(parts_models.ComponentParameterType) admin.site.register(parts_models.ComponentType) admin.site.register(parts_models.Distributor) diff --git a/shimatta_kenkyusho/parts/forms.py b/shimatta_kenkyusho/parts/forms.py index 5e1743c..44752e6 100644 --- a/shimatta_kenkyusho/parts/forms.py +++ b/shimatta_kenkyusho/parts/forms.py @@ -252,15 +252,19 @@ class DistributorNumberDeleteForm(forms.Form): class ComponentParameterDeleteForm(forms.Form): param_num = forms.UUIDField(required=True) + model = parts_models.ComponentParameter def clean_param_num(self): my_uuid = self.cleaned_data['param_num'] try: - param = parts_models.ComponentParameter.objects.get(id=my_uuid) + param = self.model.objects.get(id=my_uuid) except: raise ValidationError('Parameter Number Invalid') return param +class PackageParameterDeleteForm(ComponentParameterDeleteForm): + model = parts_models.PackageParameter + class AdvancedComponentSearchForm(forms.Form): name = forms.CharField(max_length=255, label='Component Name', required=False) package = AutocompleteForeingKeyField(required=False, api_search_url='package-list', foreign_model=parts_models.Package) @@ -309,6 +313,7 @@ class ComponentParameterSearchForm(forms.Form): class ComponentParameterCreateForm(forms.Form): parameter_type = AutocompleteForeingKeyField(required=True, foreign_model=parts_models.ComponentParameterType, api_search_url='componentparametertype-list', image_field_name=None, name_field_name='descriptive_name') value = forms.CharField(required=True, max_length=256) + model = parts_models.ComponentParameter def clean(self): data = super().clean() @@ -338,7 +343,20 @@ class ComponentParameterCreateForm(forms.Form): else: text_value = '' value = self.cleaned_data['number_value'] - parts_models.ComponentParameter.objects.create(parameter_type=param_type, component=component, value=value, text_value=text_value) + self.model.objects.create(parameter_type=param_type, component=component, value=value, text_value=text_value) + +class PackageParameterCreateForm(ComponentParameterCreateForm): + model = parts_models.PackageParameter + + def save(self, package): + param_type = self.cleaned_data['parameter_type'] + if param_type.parameter_type == 'F': + text_value = self.cleaned_data['value'] + value = 0 + else: + text_value = '' + value = self.cleaned_data['number_value'] + self.model.objects.create(parameter_type=param_type, package=package, value=value, text_value=text_value) class QrSearchForm(forms.Form): my_qr_validator = QrCodeValidator() diff --git a/shimatta_kenkyusho/parts/management/commands/consolidate_component_package_parameters.py b/shimatta_kenkyusho/parts/management/commands/consolidate_component_package_parameters.py new file mode 100644 index 0000000..3ab4980 --- /dev/null +++ b/shimatta_kenkyusho/parts/management/commands/consolidate_component_package_parameters.py @@ -0,0 +1,43 @@ +from django.core.management.base import BaseCommand, CommandParser +from django.contrib.auth import get_user_model +from parts.models import Component, ComponentParameter, ComponentParameterType, PackageParameter, Package + +class Command(BaseCommand): + help = "Remove component parameters, that are also set on the package with the same value" + + def add_arguments(self, parser: CommandParser): + parser.add_argument('--dry-run', + help='Do not perform parameter deletion. Print only', + action='store_true') + + def handle(self, *args, **options): + # Get all components with set packages. Ignore the ones without packages + all_comps = Component.objects.exclude(package__isnull=True) + + for component in all_comps: + package_parameters = PackageParameter.objects.filter(package=component.package) + component_parameters = ComponentParameter.objects.filter(component=component) + package_param_ids = package_parameters.values_list('parameter_type_id', flat=True) + component_param_ids = component_parameters.values_list('parameter_type_id', flat=True) + + # Skip trivial cases + if len(package_param_ids) == 0 or len(component_param_ids) == 0: + continue + + self.stdout.write(f'Comp: {str(component)} Found {len(component_param_ids)} different parameters') + self.stdout.write(f'\tPackage: {str(component.package)} Found {len(package_param_ids)} different parameters') + + commontypes = ctypes = ComponentParameterType.objects.filter(id__in=component_param_ids).filter(id__in=package_param_ids) + self.stdout.write(f'\tCommon parameter count: {len(commontypes)}') + + # Check if values are the same when rendered as a string. This avoids float comparison problems + for common_type in commontypes: + s1 = package_parameters.filter(parameter_type=common_type).first().resolved_value_as_string() + comp_param = component_parameters.filter(parameter_type=common_type).first() + s2 = comp_param.resolved_value_as_string() + if s1 == s2: + self.stdout.write(f'\tParameter {common_type.parameter_name} is the same value for component and package: {s1}. Removing from component') + if not options['dry_run']: + comp_param.delete() + else: + self.stderr.write(f'\tParameter {common_type.parameter_name} is set on component {str(component)} and its package with different values: "{s1}" vs "{s2}"') diff --git a/shimatta_kenkyusho/parts/migrations/0013_packageparameter.py b/shimatta_kenkyusho/parts/migrations/0013_packageparameter.py new file mode 100644 index 0000000..f11b6a3 --- /dev/null +++ b/shimatta_kenkyusho/parts/migrations/0013_packageparameter.py @@ -0,0 +1,49 @@ +# Generated by Django 5.1.3 on 2024-11-19 20:26 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("parts", "0012_storage_verbose_name"), + ] + + operations = [ + migrations.CreateModel( + name="PackageParameter", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("value", models.FloatField(default=0)), + ("text_value", models.TextField(blank=True)), + ( + "package", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="parts.package" + ), + ), + ( + "parameter_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="parts.componentparametertype", + ), + ), + ], + options={ + "ordering": ["id"], + "unique_together": {("package", "parameter_type")}, + }, + ), + ] diff --git a/shimatta_kenkyusho/parts/models.py b/shimatta_kenkyusho/parts/models.py index ee10485..251aed5 100644 --- a/shimatta_kenkyusho/parts/models.py +++ b/shimatta_kenkyusho/parts/models.py @@ -238,24 +238,25 @@ class Component(models.Model): sum = 0 return sum - -class ComponentParameter(models.Model): +class AbstractParameter(models.Model): class Meta: - unique_together = ('component', 'parameter_type') - ordering = ['id'] + abstract = True + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True) - component = models.ForeignKey(Component, on_delete=models.CASCADE) # A target component is required! parameter_type = models.ForeignKey(ComponentParameterType, on_delete=models.CASCADE) value = models.FloatField(default=0) text_value = models.TextField(null=False, blank=True) + def _get_object_of_param(self): + return None + def __str__(self): if self.parameter_type.parameter_type == 'F': value = self.text_value else: value = str(self.value) - return str(self.component)+ ': '+ str(self.parameter_type) + ': ' + value + return str(self._get_object_of_param())+ ': '+ str(self.parameter_type) + ': ' + value def resolved_value_as_string(self): my_type = self.parameter_type.parameter_type @@ -270,6 +271,24 @@ class ComponentParameter(models.Model): elif my_type == 'F': return self.text_value +class ComponentParameter(AbstractParameter): + class Meta: + unique_together = ('component', 'parameter_type') + ordering = ['id'] + component = models.ForeignKey(Component, on_delete=models.CASCADE) # A target component is required! + + def _get_object_of_param(self): + return self.component + +class PackageParameter(AbstractParameter): + class Meta: + unique_together = ('package', 'parameter_type') + ordering = ['id'] + package = models.ForeignKey(Package, on_delete=models.CASCADE) + + def _get_object_of_param(self): + return self.package + class Stock(models.Model): class Meta: unique_together = ('component', 'storage') diff --git a/shimatta_kenkyusho/parts/views/component_views.py b/shimatta_kenkyusho/parts/views/component_views.py index 76eed61..d092c84 100644 --- a/shimatta_kenkyusho/parts/views/component_views.py +++ b/shimatta_kenkyusho/parts/views/component_views.py @@ -8,7 +8,7 @@ from django.db.models import Q from django.forms import formset_factory from django.db import IntegrityError from django.db.models import ProtectedError -from ..models import Stock, Component, ComponentParameter, DistributorNum +from ..models import Stock, Component, ComponentParameter, DistributorNum, PackageParameter from ..forms import * from .component_import import import_components_from_csv from .generic_views import BaseTemplateMixin @@ -174,6 +174,8 @@ class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): 'distributor__name') context['parameters'] = ComponentParameter.objects.filter(component=self.object).order_by( 'parameter_type__parameter_name') + context['package_parameters'] = PackageParameter.objects.filter(package=self.object.package).order_by( + 'parameter_type__parameter_name') return context diff --git a/shimatta_kenkyusho/parts/views/package_views.py b/shimatta_kenkyusho/parts/views/package_views.py index 37ad2e7..2615581 100644 --- a/shimatta_kenkyusho/parts/views/package_views.py +++ b/shimatta_kenkyusho/parts/views/package_views.py @@ -5,7 +5,7 @@ from django.core.paginator import Paginator from django.db.models import ProtectedError from django.db.models import Q from ..forms import * -from ..models import Package +from ..models import Package, PackageParameter from .generic_views import BaseTemplateMixin class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): @@ -82,6 +82,9 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): context = super().get_context_data(**kwargs) context['package'] = self.object context['edit_form'] = PackageForm(instance=self.object) + context['new_param_form'] = PackageParameterCreateForm() + context['parameters'] = PackageParameter.objects.filter(package=self.object).order_by( + 'parameter_type__parameter_name') return context @@ -116,6 +119,27 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): if not edit_form.is_valid(): context['edit_form'] = edit_form return self.render_to_response(context) + + def handle_submit_delete_param_post(self, request, **kwargs): + form = PackageParameterDeleteForm(data=request.POST) + if form.is_valid(): + form.cleaned_data['param_num'].delete() + + context = self.get_context_data(**kwargs) + return self.render_to_response(context) + + def handle_submit_new_param_post(self, request, **kwargs): + form = PackageParameterCreateForm(data=request.POST) + + if form.is_valid(): + try: + form.save(self.object) + except IntegrityError: + form.add_error('__all__', 'This parameter is already set') + context = self.get_context_data(**kwargs) + if not form.is_valid(): + context['new_param_form'] = form + return self.render_to_response(context) def post(self, request, *args, **kwargs): self.object = self.get_object() @@ -124,5 +148,9 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): return self.handle_delete_package(request) elif 'submit-pkg-edit' in request.POST: return self.edit_package(request) + elif 'submit-delete-param' in request.POST: + return self.handle_submit_delete_param_post(request, **kwargs) + elif 'submit-create-new-param' in request.POST: + return self.handle_submit_new_param_post(request, **kwargs) return super().post(request, *args, **kwargs) \ No newline at end of file diff --git a/shimatta_kenkyusho/templates/parts/components-detail.html b/shimatta_kenkyusho/templates/parts/components-detail.html index a8d2163..c565ac4 100644 --- a/shimatta_kenkyusho/templates/parts/components-detail.html +++ b/shimatta_kenkyusho/templates/parts/components-detail.html @@ -119,6 +119,19 @@
Parameter | +Value | ++ + + {% for param in parameters %} + |
---|---|---|
+ + {{param.parameter_type.parameter_name}} ++ |
+ + {{param.resolved_value_as_string}} + | ++ + | +