#21: feature/21-add-package-params #32
@@ -252,15 +252,19 @@ class DistributorNumberDeleteForm(forms.Form):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ComponentParameterDeleteForm(forms.Form):
 | 
					class ComponentParameterDeleteForm(forms.Form):
 | 
				
			||||||
    param_num = forms.UUIDField(required=True)
 | 
					    param_num = forms.UUIDField(required=True)
 | 
				
			||||||
 | 
					    model = parts_models.ComponentParameter
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def clean_param_num(self):
 | 
					    def clean_param_num(self):
 | 
				
			||||||
        my_uuid = self.cleaned_data['param_num']
 | 
					        my_uuid = self.cleaned_data['param_num']
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            param = parts_models.ComponentParameter.objects.get(id=my_uuid)
 | 
					            param = self.model.objects.get(id=my_uuid)
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            raise ValidationError('Parameter Number Invalid')
 | 
					            raise ValidationError('Parameter Number Invalid')
 | 
				
			||||||
        return param
 | 
					        return param
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PackageParameterDeleteForm(ComponentParameterDeleteForm):
 | 
				
			||||||
 | 
					    model = parts_models.PackageParameter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AdvancedComponentSearchForm(forms.Form):
 | 
					class AdvancedComponentSearchForm(forms.Form):
 | 
				
			||||||
    name = forms.CharField(max_length=255, label='Component Name', required=False)
 | 
					    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)
 | 
					    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):
 | 
					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')
 | 
					    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)
 | 
					    value = forms.CharField(required=True, max_length=256)
 | 
				
			||||||
 | 
					    model = parts_models.ComponentParameter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clean(self):
 | 
					    def clean(self):
 | 
				
			||||||
        data = super().clean()
 | 
					        data = super().clean()
 | 
				
			||||||
@@ -338,7 +343,20 @@ class ComponentParameterCreateForm(forms.Form):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            text_value = ''
 | 
					            text_value = ''
 | 
				
			||||||
            value = self.cleaned_data['number_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):
 | 
					class QrSearchForm(forms.Form):
 | 
				
			||||||
    my_qr_validator = QrCodeValidator()
 | 
					    my_qr_validator = QrCodeValidator()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					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)
 | 
				
			||||||
| 
						
							
	
	
	
	
	
	
	
	 
				
					
						sst
						commented  
			
		If performance matters it should be possible to forward select the components and add them to the first query using .prefetch_selected ...consider If performance matters it should be possible to forward select the components and add them to the first query using .prefetch_selected
...consider 
			
			
		 | 
				|||||||
 | 
								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)
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								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()
 | 
				
			||||||
@@ -8,7 +8,7 @@ from django.db.models import Q
 | 
				
			|||||||
from django.forms import formset_factory
 | 
					from django.forms import formset_factory
 | 
				
			||||||
from django.db import IntegrityError
 | 
					from django.db import IntegrityError
 | 
				
			||||||
from django.db.models import ProtectedError
 | 
					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 ..forms import *
 | 
				
			||||||
from .component_import import import_components_from_csv
 | 
					from .component_import import import_components_from_csv
 | 
				
			||||||
from .generic_views import BaseTemplateMixin
 | 
					from .generic_views import BaseTemplateMixin
 | 
				
			||||||
@@ -174,6 +174,8 @@ class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
            'distributor__name')
 | 
					            'distributor__name')
 | 
				
			||||||
        context['parameters'] = ComponentParameter.objects.filter(component=self.object).order_by(
 | 
					        context['parameters'] = ComponentParameter.objects.filter(component=self.object).order_by(
 | 
				
			||||||
            'parameter_type__parameter_name')
 | 
					            'parameter_type__parameter_name')
 | 
				
			||||||
 | 
					        context['package_parameters'] = PackageParameter.objects.filter(package=self.object.package).order_by(
 | 
				
			||||||
 | 
					            'parameter_type__parameter_name')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ from django.core.paginator import Paginator
 | 
				
			|||||||
from django.db.models import ProtectedError
 | 
					from django.db.models import ProtectedError
 | 
				
			||||||
from django.db.models import Q
 | 
					from django.db.models import Q
 | 
				
			||||||
from ..forms import *
 | 
					from ..forms import *
 | 
				
			||||||
from ..models import Package
 | 
					from ..models import Package, PackageParameter
 | 
				
			||||||
from .generic_views import BaseTemplateMixin
 | 
					from .generic_views import BaseTemplateMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
 | 
					class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
 | 
				
			||||||
@@ -82,6 +82,9 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
        context = super().get_context_data(**kwargs)
 | 
					        context = super().get_context_data(**kwargs)
 | 
				
			||||||
        context['package'] = self.object
 | 
					        context['package'] = self.object
 | 
				
			||||||
        context['edit_form'] = PackageForm(instance=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
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -116,6 +119,27 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
        if not edit_form.is_valid():
 | 
					        if not edit_form.is_valid():
 | 
				
			||||||
            context['edit_form'] = edit_form
 | 
					            context['edit_form'] = edit_form
 | 
				
			||||||
        return self.render_to_response(context)
 | 
					        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):
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
        self.object = self.get_object()
 | 
					        self.object = self.get_object()
 | 
				
			||||||
@@ -124,5 +148,9 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView):
 | 
				
			|||||||
            return self.handle_delete_package(request)
 | 
					            return self.handle_delete_package(request)
 | 
				
			||||||
        elif 'submit-pkg-edit' in request.POST:
 | 
					        elif 'submit-pkg-edit' in request.POST:
 | 
				
			||||||
            return self.edit_package(request)
 | 
					            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)
 | 
					        return super().post(request, *args, **kwargs)
 | 
				
			||||||
@@ -119,6 +119,19 @@
 | 
				
			|||||||
                            <th scope="col"></th>
 | 
					                            <th scope="col"></th>
 | 
				
			||||||
                        </thead>
 | 
					                        </thead>
 | 
				
			||||||
                        <tbody>
 | 
					                        <tbody>
 | 
				
			||||||
 | 
					                            {% for param in package_parameters %}
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <h6 {% if param.parameter_type.parameter_description %} class="accordion-header" data-bs-toggle="collapse" data-bs-target="#collapse-pkg-parameter-desc-{{forloop.counter}}"{% endif %}>
 | 
				
			||||||
 | 
					                                        {{param.parameter_type.parameter_name}}
 | 
				
			||||||
 | 
					                                    </h6>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    {{param.resolved_value_as_string}}
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                                <td><span class="text-info">from Package</span></td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                            {% endfor %}
 | 
				
			||||||
                            {% for param in parameters %}
 | 
					                            {% for param in parameters %}
 | 
				
			||||||
                            <tr>
 | 
					                            <tr>
 | 
				
			||||||
                                <td>
 | 
					                                <td>
 | 
				
			||||||
@@ -148,6 +161,13 @@
 | 
				
			|||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        {% endif %}
 | 
					                        {% endif %}
 | 
				
			||||||
                        {% endfor %}
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					                        {% for param in package_parameters %}
 | 
				
			||||||
 | 
					                        {% if param.parameter_type.parameter_description %}
 | 
				
			||||||
 | 
					                        <div class="collapse accordion-collapse" id="collapse-pkg-parameter-desc-{{forloop.counter}}" data-bs-parent="#accordion-param-desc">
 | 
				
			||||||
 | 
					                            {{param.parameter_type.parameter_description}}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="col">
 | 
					                <div class="col">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,46 @@
 | 
				
			|||||||
            <input type="submit" class="btn btn-primary" value="Save" name="submit-pkg-edit">
 | 
					            <input type="submit" class="btn btn-primary" value="Save" name="submit-pkg-edit">
 | 
				
			||||||
        </form>
 | 
					        </form>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="col-md-3">
 | 
				
			||||||
 | 
					            <h3>Parameters <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#new-component-parameter-modal"><i class="bi bi-plus-circle"></i></button></h3>
 | 
				
			||||||
 | 
					            <table class="table align-middle mb-3">
 | 
				
			||||||
 | 
					                <thead>
 | 
				
			||||||
 | 
					                    <th scope="col">Parameter</th>
 | 
				
			||||||
 | 
					                    <th scope="col">Value</th>
 | 
				
			||||||
 | 
					                    <th scope="col"></th>
 | 
				
			||||||
 | 
					                </thead>
 | 
				
			||||||
 | 
					                <tbody>
 | 
				
			||||||
 | 
					                    {% for param in parameters %}
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <td>
 | 
				
			||||||
 | 
					                            <h6 {% if param.parameter_type.parameter_description %} class="accordion-header" data-bs-toggle="collapse" data-bs-target="#collapse-parameter-desc-{{forloop.counter}}"{% endif %}>
 | 
				
			||||||
 | 
					                                {{param.parameter_type.parameter_name}}
 | 
				
			||||||
 | 
					                            </h6>
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>
 | 
				
			||||||
 | 
					                            {{param.resolved_value_as_string}}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>
 | 
				
			||||||
 | 
					                            <form method="post">
 | 
				
			||||||
 | 
					                                {% csrf_token %}
 | 
				
			||||||
 | 
					                                <input type="hidden" value="{{param.id}}" name="param_num">
 | 
				
			||||||
 | 
					                                <button class="btn btn-danger" name="submit-delete-param">X</button>
 | 
				
			||||||
 | 
					                            </form>    
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </tbody>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					            <div class="accordion" id="accordion-param-desc">
 | 
				
			||||||
 | 
					                {% for param in parameters %}
 | 
				
			||||||
 | 
					                {% if param.parameter_type.parameter_description %}
 | 
				
			||||||
 | 
					                <div class="collapse accordion-collapse" id="collapse-parameter-desc-{{forloop.counter}}" data-bs-parent="#accordion-param-desc">
 | 
				
			||||||
 | 
					                    {{param.parameter_type.parameter_description}}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,7 +120,7 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					{% include 'parts/modals/new-component-parameter-modal.html' with component_name=object.name form=new_param_form %}
 | 
				
			||||||
| 
							
							
								
									
	
	
	
	
	
	
	
	 
					
					sst marked this conversation as resolved
					
				 
				
				
					
						sst
						commented  
			
		Either I am blind or you forgot to commit this file :) Either I am blind or you forgot to commit this file :) 
			
			
		
				
					
						sst
						commented  
			
		forget it - I was blind forget it - I was blind 
			
			
		 | 
					|||||||
 | 
					
 | 
				
			||||||
{% endblock content %}
 | 
					{% endblock content %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user
	
Concider moving the duplicated code into its own function e.g.
param_type, text_value, value = _get_type_specific_values(self.cleaned_data)...but feel free to leave it as is