Compare commits
	
		
			26 Commits
		
	
	
		
			2d83c9ceec
			...
			advanced-f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8636c513b7 | |||
| 3d263ca27c | |||
| 1c56dd44f9 | |||
| a300d66f66 | |||
| 191705c6a6 | |||
| ba0da19810 | |||
| 1e20cb458f | |||
| ac0f363a1e | |||
| b00cc19e61 | |||
| 52749da6e6 | |||
| 5fa6700bb4 | |||
| 8c5d017ed1 | |||
| c47350f449 | |||
| 0aadf4305f | |||
| b26c54dfce | |||
| 009ff5ae96 | |||
| a566e198b8 | |||
| ea623212bb | |||
| 2bc0f3124c | |||
| a34557499a | |||
| 35255cf4e9 | |||
| 873e13542a | |||
| 1e302e4595 | |||
| c6fae17154 | |||
| 3b1eb6118f | |||
| 76c79403f2 | 
| @@ -50,8 +50,9 @@ class PartsComponentViewSet(viewsets.ModelViewSet): | ||||
|     queryset = parts_models.Component.objects.all() | ||||
|     serializer_class = ComponentSerializer | ||||
|     permission_classes = [permissions.DjangoModelPermissions] | ||||
|     filter_backends = [filters.SearchFilter] | ||||
|     filter_backends = [filters.SearchFilter, django_filters.rest_framework.DjangoFilterBackend] | ||||
|     search_fields = ['id', 'name', 'package__name', 'manufacturer__name'] | ||||
|     filterset_fields = ['id', 'name'] | ||||
|  | ||||
| class PartsComponentTypeViewSet(viewsets.ModelViewSet): | ||||
|     queryset = parts_models.ComponentType.objects.all() | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -244,6 +244,7 @@ class AdvancedComponentSearchForm(forms.Form): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.helper = FormHelper() | ||||
|         self.helper.form_tag = False | ||||
|         self.helper.disable_csrf = True | ||||
|         self.helper.layout = Layout( | ||||
|             Row( | ||||
|                 Column('name'), | ||||
| @@ -262,17 +263,27 @@ class AdvancedComponentSearchForm(forms.Form): | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| PARAMETER_COMPARISON_TYPES = ( | ||||
|     ('eq', '=='), | ||||
|     ('lte', '<='), | ||||
|     ('gte', '>='), | ||||
| ) | ||||
|  | ||||
| class ComponentParameterSearchForm(forms.Form): | ||||
|     parameter = AutocompleteForeingKeyField(required=True, foreign_model=parts_models.ComponentParameterType, api_search_url='componentparametertype-list', image_field_name=None, name_field_name='parameter_name') | ||||
|     value = forms.CharField(max_length=100, required=False) | ||||
|     compare_method = forms.ChoiceField(choices=PARAMETER_COMPARISON_TYPES, required=True, initial=1) | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.helper = FormHelper() | ||||
|         self.helper.form_tag = False | ||||
|         self.helper.disable_csrf = True | ||||
|         self.helper.layout = Layout( | ||||
|             Row( | ||||
|                 Column('parameter'), | ||||
|                 Column('compare_method'), | ||||
|                 Column('value') | ||||
|             ) | ||||
|         ) | ||||
|   | ||||
| @@ -0,0 +1,59 @@ | ||||
| from django.core.management.base import BaseCommand | ||||
| from parts.models import Component, Package, Distributor, Manufacturer | ||||
| import os | ||||
| import shutil | ||||
|  | ||||
| class Command(BaseCommand): | ||||
| 	help = 'Migrate all media files to the current folder structure' | ||||
|  | ||||
| 	def move_files_of_model(self, queryset): | ||||
| 		for comp in queryset: | ||||
| 			img_path = comp.image.name | ||||
| 			img_path = os.path.normpath(img_path) | ||||
| 			path_components = img_path.split(os.sep) | ||||
|  | ||||
| 			if len(path_components) <= 2: | ||||
| 				self.stdout.write(f'Legacy path found: {img_path}. Will be moved') | ||||
| 				full_path_components = os.path.normpath(comp.image.path).split(os.sep) | ||||
| 				fname = full_path_components[-1] | ||||
| 				path_elem_count = len(full_path_components) | ||||
| 				full_path_components.insert(path_elem_count-1, str(fname[1])) | ||||
| 				full_path_components.insert(path_elem_count-1, str(fname[0])) | ||||
| 				dest_path = os.sep.join(full_path_components) | ||||
|  | ||||
| 				# Move file | ||||
| 				os.makedirs(os.path.dirname(dest_path), exist_ok=True) | ||||
| 				shutil.move(comp.image.path, dest_path) | ||||
|  | ||||
| 				# Update model | ||||
| 				new_rel_path_comps = path_components | ||||
| 				l = len(new_rel_path_comps) | ||||
| 				new_rel_path_comps.insert(l-1, str(fname[1])) | ||||
| 				new_rel_path_comps.insert(l-1, str(fname[0])) | ||||
| 				new_name = os.sep.join(new_rel_path_comps) | ||||
| 				self.stdout.write(f'New location: {dest_path}, new name: {new_name}') | ||||
| 				comp.image.name = new_name | ||||
| 				comp.save() | ||||
|  | ||||
| 	def handle(self, *args, **kwargs): | ||||
|  | ||||
| 		self.stdout.write('Querying components...') | ||||
| 		components = Component.objects.exclude(image='') | ||||
| 		self.stdout.write(f'Count of components with images: {components.count()}'); | ||||
| 		self.move_files_of_model(components) | ||||
|  | ||||
| 		self.stdout.write('Querying packages...') | ||||
| 		pkgs = Package.objects.exclude(image='') | ||||
| 		self.stdout.write(f'Count of components with images: {pkgs.count()}'); | ||||
| 		self.move_files_of_model(pkgs) | ||||
|  | ||||
| 		self.stdout.write('Querying manufacturers...') | ||||
| 		manufacturers = Manufacturer.objects.exclude(image='') | ||||
| 		self.stdout.write(f'Count of components with images: {manufacturers.count()}'); | ||||
| 		self.move_files_of_model(manufacturers) | ||||
|  | ||||
| 		self.stdout.write('Querying distributors...') | ||||
| 		distris = Distributor.objects.exclude(image='') | ||||
| 		self.stdout.write(f'Count of components with images: {distris.count()}'); | ||||
| 		self.move_files_of_model(distris) | ||||
|  | ||||
| @@ -0,0 +1,44 @@ | ||||
| # Generated by Django 3.2.5 on 2022-01-10 18:12 | ||||
|  | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| import uuid | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('parts', '0010_auto_20220103_1606'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='componenttype', | ||||
|             name='key_parameter1', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='type_param1', to='parts.componentparametertype'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='componenttype', | ||||
|             name='key_parameter2', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='type_param2', to='parts.componentparametertype'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='componenttype', | ||||
|             name='key_parameter3', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='type_param3', to='parts.componentparametertype'), | ||||
|         ), | ||||
|         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')}, | ||||
|             }, | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,19 @@ | ||||
| # Generated by Django 3.2.5 on 2022-11-11 20:18 | ||||
|  | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('parts', '0011_auto_20220110_1812'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='componenttype', | ||||
|             name='parent_class', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='child_classes', to='parts.componenttype'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -47,11 +47,35 @@ class ComponentType(models.Model): | ||||
| 	class Meta: | ||||
| 		ordering = ['class_name'] | ||||
| 	class_name = models.CharField(max_length=50, unique=True) | ||||
| 	parent_class = models.ForeignKey('self', on_delete=models.PROTECT, related_name='child_classes', null=True, blank=True) | ||||
| 	passive = models.BooleanField() | ||||
| 	possible_parameter = models.ManyToManyField(ComponentParameterType, blank=True) | ||||
| 	key_parameter1 = models.ForeignKey(ComponentParameterType, on_delete=models.CASCADE, blank=True, null=True, related_name="type_param1") | ||||
| 	key_parameter2 = models.ForeignKey(ComponentParameterType, on_delete=models.CASCADE, blank=True, null=True, related_name="type_param2") | ||||
| 	key_parameter3 = models.ForeignKey(ComponentParameterType, on_delete=models.CASCADE, blank=True, null=True, related_name="type_param3") | ||||
| 	 | ||||
| 	def __str__(self): | ||||
| 		return '[' + self.class_name + ']' | ||||
| 		return self.get_full_path() | ||||
|  | ||||
| 	 | ||||
| 	def get_path_components(self): | ||||
| 		chain = [] | ||||
| 		iterator = self | ||||
| 		chain.append(self) | ||||
| 		while iterator.parent_class is not None: | ||||
| 			chain.append(iterator.parent_class) | ||||
| 			iterator = iterator.parent_class | ||||
|  | ||||
| 		return chain | ||||
|  | ||||
| 	def get_full_path(self): | ||||
| 		output = '' | ||||
| 		 | ||||
| 		chain = self.get_path_components() | ||||
|  | ||||
| 		for i in range(len(chain) - 1, -1, -1): | ||||
| 			output = output + ' / ' + chain[i].class_name | ||||
| 		return output | ||||
|  | ||||
| class Storage(models.Model): | ||||
| 	class Meta: | ||||
| @@ -144,6 +168,37 @@ class Package(models.Model): | ||||
| 	def __str__(self): | ||||
| 		return self.name | ||||
|  | ||||
| class PackageParameter(models.Model): | ||||
| 	class Meta: | ||||
| 		unique_together = ('package', 'parameter_type') | ||||
| 		ordering = ['id'] | ||||
| 	id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True) | ||||
| 	package = models.ForeignKey(Package, on_delete=models.CASCADE)  # A target package 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 __str__(self): | ||||
| 		if self.parameter_type.parameter_type == 'F': | ||||
| 			value = self.text_value | ||||
| 		else: | ||||
| 			value = str(self.value) | ||||
|  | ||||
| 		return str(self.package)+ ': '+ str(self.parameter_type) + ': ' + value | ||||
|  | ||||
| 	def resolved_value_as_string(self): | ||||
| 		my_type = self.parameter_type.parameter_type | ||||
|  | ||||
| 		if my_type == 'E' or my_type == 'I': | ||||
| 			# Engineering float number | ||||
| 			(num, prefix) = NumConv.number_to_engineering(self.value, it_unit=(True if my_type=='I' else False)) | ||||
| 			return f'{num:.3f} {prefix}{self.parameter_type.unit}' | ||||
| 		elif my_type == 'N': | ||||
| 			# Standard float number | ||||
| 			return  f'{self.value:.3f} {self.parameter_type.unit}' | ||||
| 		elif my_type == 'F': | ||||
| 			return self.text_value | ||||
|  | ||||
|  | ||||
| class Manufacturer(models.Model): | ||||
| 	class Meta: | ||||
| @@ -206,6 +261,33 @@ class Component(models.Model): | ||||
| 			sum = 0 | ||||
| 		return sum | ||||
|  | ||||
| 	def get_key_parameters(self): | ||||
| 		""" | ||||
| 		Get the key parameters of a component defined by its component type. | ||||
| 		Returns a tuple of 3 elements. All three might be None | ||||
| 		""" | ||||
| 		p1 = None | ||||
| 		p2 = None | ||||
| 		p3 = None | ||||
|  | ||||
| 		if self.component_type: | ||||
| 			t = (self.component_type.key_parameter1, self.component_type.key_parameter2, self.component_type.key_parameter3) | ||||
| 			if t[0]: | ||||
| 				p1 = ComponentParameter.objects.filter(component=self, parameter_type=t[0]).first() | ||||
| 			if t[1]: | ||||
| 				p2 = ComponentParameter.objects.filter(component=self, parameter_type=t[1]).first() | ||||
| 			if t[2]: | ||||
| 				p3 = ComponentParameter.objects.filter(component=self, parameter_type=t[2]).first() | ||||
|  | ||||
| 		return (p1, p2, p3) | ||||
| 	 | ||||
| 	def get_key_parameters_as_text(self): | ||||
| 		params = self.get_key_parameters() | ||||
| 		ret_strings = [] | ||||
| 		for p in params: | ||||
| 			if p: | ||||
| 				ret_strings.append(p.resolved_value_as_string()) | ||||
| 		return ret_strings | ||||
|  | ||||
| class ComponentParameter(models.Model): | ||||
| 	class Meta: | ||||
| @@ -231,10 +313,12 @@ class ComponentParameter(models.Model): | ||||
| 		if my_type == 'E' or my_type == 'I': | ||||
| 			# Engineering float number | ||||
| 			(num, prefix) = NumConv.number_to_engineering(self.value, it_unit=(True if my_type=='I' else False)) | ||||
| 			return f'{num:.3f} {prefix}{self.parameter_type.unit}' | ||||
| 			num = round(num, 3) | ||||
| 			return f'{num} {prefix}{self.parameter_type.unit}' | ||||
| 		elif my_type == 'N': | ||||
| 			# Standard float number | ||||
| 			return  f'{self.value:.3f} {self.parameter_type.unit}' | ||||
| 			num = round(self.value, 3) | ||||
| 			return  f'{num} {self.parameter_type.unit}' | ||||
| 		elif my_type == 'F': | ||||
| 			return self.text_value | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from . import views as parts_views | ||||
| urlpatterns = [ | ||||
|     path('', parts_views.MainView.as_view(), name='parts-main'), | ||||
|     path('components/', parts_views.ComponentView.as_view(), name='parts-components'), | ||||
|     path('componenttypes/', parts_views.ComponentTypeView.as_view(), name='parts-componenttypes'), | ||||
|     path('packages/', parts_views.PackageView.as_view(), name='parts-packages'), | ||||
|     path('distributors/', parts_views.DistributorView.as_view(), name='parts-distributors'), | ||||
|     path('stocks/', parts_views.StockView.as_view(), name='parts-stocks'), | ||||
| @@ -16,4 +17,5 @@ urlpatterns = [ | ||||
|     path('distributors/<slug:uuid>/', parts_views.DistributorDetailView.as_view(), name='parts-distributors-detail'), | ||||
|     path('manufacturers/', parts_views.ManufacturersViewSet.as_view(), name='parts-manufacturers'), | ||||
|     path("manufacturers/<slug:uuid>/", parts_views.ManufacturerDetailViewSet.as_view(), name='parts-manufacturers-detail'), | ||||
|     path("componenttypes/<slug:uuid>/", parts_views.ComponentTypeDetailView.as_view(), name='parts-componenttypes-detail'),     | ||||
| ] | ||||
|   | ||||
| @@ -2,6 +2,7 @@ from django.shortcuts import render, redirect | ||||
| from django.urls import resolve, reverse | ||||
| from django.contrib.auth import logout, login | ||||
| from django.contrib.auth.models import User | ||||
| from django.utils.http import urlencode | ||||
| from django.http import HttpResponse | ||||
| from .navbar import NavBar | ||||
| from django.contrib.auth.forms import AuthenticationForm as AuthForm | ||||
| @@ -11,7 +12,9 @@ from django.views import View | ||||
| import django.forms as forms | ||||
| from django.views.generic import TemplateView, DetailView | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin | ||||
| from .models import Storage, Stock, Component, Distributor, Manufacturer, Package, ComponentParameter, ComponentParameterType, DistributorNum | ||||
| from .models import Storage, Stock, Component, Distributor, Manufacturer, Package | ||||
| from .models import ComponentParameter, ComponentParameterType, DistributorNum, PackageParameter | ||||
| from .models import ComponentType | ||||
| from .qr_parser import QrCodeValidator | ||||
| from django.core.paginator import Paginator | ||||
| from django.core.exceptions import ValidationError | ||||
| @@ -19,11 +22,12 @@ from django.db import IntegrityError | ||||
| from django.db.models import ProtectedError | ||||
| from .forms import * | ||||
| from django.db.models import Q | ||||
| from django.db.models import Prefetch | ||||
| from django.db.models.functions import Lower | ||||
| from django.forms import formset_factory | ||||
| import uuid | ||||
|  | ||||
| ParameterSearchFormSet = formset_factory(ComponentParameterSearchForm, extra=2) | ||||
| ParameterSearchFormSet = formset_factory(ComponentParameterSearchForm, extra=0) | ||||
|  | ||||
| class QrSearchForm(forms.Form): | ||||
|     my_qr_validator = QrCodeValidator() | ||||
| @@ -34,6 +38,15 @@ class QrSearchForm(forms.Form): | ||||
|  | ||||
|     qr_search = forms.CharField(label='qr_search', validators=[my_qr_validator]) | ||||
|  | ||||
| class KeepSearchParamMixin(object): | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         search = self.request.GET.get('search', default=None) | ||||
|         if search: | ||||
|             context['additional_params'] = urlencode({'search': search}) | ||||
|         return context | ||||
|      | ||||
|  | ||||
| class BaseTemplateMixin(object): | ||||
|     navbar_selected = '' | ||||
|     base_title = '' | ||||
| @@ -142,16 +155,69 @@ def login_view(request): | ||||
|  | ||||
| # Create your views here. | ||||
|  | ||||
| class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
| class ComponentTypeView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|     template_name = 'parts/component-types.html' | ||||
|     base_title = 'Component Types' | ||||
|     default_page_size = 25 | ||||
|  | ||||
|     def filter_queryset(self, queryset, search_string): | ||||
|         if search_string is None or search_string == '': | ||||
|             return queryset | ||||
|  | ||||
|         search_fragments = search_string.strip().split() | ||||
|         for search in search_fragments: | ||||
|             queryset = queryset.filter(Q(class_name__icontains = search)) | ||||
|  | ||||
|         return queryset | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         search = self.request.GET.get('search', default=None) | ||||
|         page_num = self.request.GET.get('page', default=1) | ||||
|  | ||||
|         context['search_string'] = search | ||||
|  | ||||
|         queryset = ComponentType.objects.all()  | ||||
|         types = self.filter_queryset(queryset, search) | ||||
|          | ||||
|         comptypes = Paginator(types, self.default_page_size) | ||||
|          | ||||
|  | ||||
|         context['comptypes'] = comptypes.get_page(page_num) | ||||
|         return context | ||||
|      | ||||
|  | ||||
|     def post(self, request, *args, **kwargs): | ||||
|         return super().post(request, *args, **kwargs) | ||||
|  | ||||
| class ComponentTypeDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|     model = ComponentType | ||||
|     base_title = '' | ||||
|     pk_url_kwarg = 'uuid' | ||||
|     template_name = 'parts/component-types-detail.html' | ||||
|  | ||||
|     def get_breadcrumbs(self): | ||||
|         crumbs = self.object.get_path_components() | ||||
|         # Reverse list and drop the last element of the reversed list | ||||
|         crumbs = crumbs[::-1][:-1] | ||||
|         return crumbs | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         context['breadcrumbs'] = self.get_breadcrumbs() | ||||
|         return context | ||||
|      | ||||
|  | ||||
| class ComponentView(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, TemplateView): | ||||
|     template_name = 'parts/components.html' | ||||
|     base_title = 'Components' | ||||
|     navbar_selected = 'Components' | ||||
|     default_page_size = 25 | ||||
|  | ||||
|     def get_component_query_set(self, search_string): | ||||
|         queryset = Component.objects.all() | ||||
|         queryset = Component.objects.select_related('package', 'manufacturer', 'component_type').prefetch_related('componentparameter_set').all() | ||||
|          | ||||
|         if not search_string: | ||||
|         if search_string is None or search_string == '': | ||||
|             return queryset | ||||
|  | ||||
|         search_fragments = search_string.strip().split() | ||||
| @@ -161,7 +227,7 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|         return queryset | ||||
|  | ||||
|     def get_component_queryset_from_advanced_search(self, cleaned_data): | ||||
|         queryset = Component.objects.all() | ||||
|         queryset = Component.objects.select_related('manufacturer', 'package', 'component_type').prefetch_related('componentparameter_set').all() | ||||
|  | ||||
|         if cleaned_data['name']: | ||||
|             queryset = queryset.filter(Q(name__icontains=cleaned_data['name'])) | ||||
| @@ -181,15 +247,19 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|             queryset = queryset.filter(manufacturer=cleaned_data['manufacturer']) | ||||
|         return queryset | ||||
|  | ||||
|     def filter_queryset_with_parameters(self, queryset, parameter, value): | ||||
|     def filter_queryset_with_parameters(self, queryset, parameter, value, compare_method): | ||||
|         if parameter and (value is None or value == ''): | ||||
|             return queryset.filter(Q(componentparameter__parameter_type=parameter)) | ||||
|         elif parameter and value is not None: | ||||
|             if parameter.parameter_type == 'F': | ||||
|                 return queryset.filter(Q(componentparameter__text_value__icontains=value)) | ||||
|                 return queryset.filter(Q(componentparameter__text_value__icontains=value) & Q(componentparameter__parameter_type=parameter)) | ||||
|             else: | ||||
|                 return queryset.filter(Q(componentparameter__value=value)) | ||||
|  | ||||
|                 if compare_method == 'lte': # <= | ||||
|                     return queryset.filter(Q(componentparameter__value__lte=value) & Q(componentparameter__parameter_type=parameter)) | ||||
|                 elif compare_method == 'gte': # >= | ||||
|                     return queryset.filter(Q(componentparameter__value__gte=value) & Q(componentparameter__parameter_type=parameter)) | ||||
|                 else: | ||||
|                     return queryset.filter(Q(componentparameter__value=value) & Q(componentparameter__parameter_type=parameter)) | ||||
|         return queryset | ||||
|  | ||||
|     def get_context_data_int(self, advanced_search, parameter_formset : ParameterSearchFormSet, **kwargs): | ||||
| @@ -205,15 +275,13 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|             if advanced_search.is_valid(): | ||||
|                 paginator_queryset = self.get_component_queryset_from_advanced_search(advanced_search.cleaned_data) | ||||
|             else: | ||||
|                 paginator_queryset = Component.objects.all() | ||||
|                 paginator_queryset = self.get_component_query_set(None) | ||||
|  | ||||
|             if parameter_formset.is_valid(): | ||||
|             # Process parameters | ||||
|             for f in parameter_formset: | ||||
|                 # If the form is valid and has changed compared to its initial empty state | ||||
|                 if f.is_valid() and f.has_changed(): | ||||
|                         print(f.cleaned_data) | ||||
|                         paginator_queryset = self.filter_queryset_with_parameters(paginator_queryset, f.cleaned_data['parameter'], f.cleaned_data['value']) | ||||
|                     paginator_queryset = self.filter_queryset_with_parameters(paginator_queryset, f.cleaned_data['parameter'], f.cleaned_data['value'], f.cleaned_data['compare_method']) | ||||
|                          | ||||
|              | ||||
|         else: | ||||
| @@ -228,15 +296,24 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|  | ||||
|         if not parameter_formset: | ||||
|             context['advanced_search_param_formset'] = ParameterSearchFormSet() | ||||
|  | ||||
|  | ||||
|         if not advanced_search: | ||||
|             context['advanced_search_form'] = AdvancedComponentSearchForm(auto_id='adv_search_%s') | ||||
|  | ||||
|         return context | ||||
|      | ||||
|     def get_context_data(self, **kwargs): | ||||
|         return self.get_context_data_int(advanced_search = None, parameter_formset=None, **kwargs)     | ||||
|         adv_search_form = None | ||||
|         adv_param_search_formset = None | ||||
|         if 'submit-advanced-search' in self.request.GET: | ||||
|             adv_search_form = AdvancedComponentSearchForm(auto_id='adv_search_%s', data=self.request.GET) | ||||
|             adv_param_search_formset = ParameterSearchFormSet(data=self.request.GET) | ||||
|             if adv_search_form.is_valid(): | ||||
|                 pass | ||||
|             if adv_param_search_formset.is_valid(): | ||||
|                 pass | ||||
|              | ||||
|  | ||||
|         return self.get_context_data_int(advanced_search = adv_search_form, parameter_formset=adv_param_search_formset, **kwargs)     | ||||
|      | ||||
|  | ||||
|     def handle_new_component_post(self, request, open=False, **kwargs): | ||||
| @@ -253,32 +330,15 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|             return redirect(reverse('parts-components-detail', kwargs={'uuid':new_component.id})) | ||||
|         return self.render_to_response(context) | ||||
|  | ||||
|     def handle_advanced_search_post(self, request, **kwargs): | ||||
|          | ||||
|         form = AdvancedComponentSearchForm(auto_id='adv_search_%s', data=request.POST) | ||||
|         param_formset = ParameterSearchFormSet(data=request.POST) | ||||
|  | ||||
|         if form.is_valid(): | ||||
|             print('Valid') | ||||
|  | ||||
|         if param_formset.is_valid(): | ||||
|             print('Formset is valid!') | ||||
|  | ||||
|  | ||||
|         context = self.get_context_data_int(form, param_formset, **kwargs) | ||||
|         return self.render_to_response(context) | ||||
|  | ||||
|     def post(self, request, *args, **kwargs): | ||||
|         if 'submit-edit-component' in request.POST: | ||||
|             return self.handle_new_component_post(request, open=False, **kwargs) | ||||
|         elif 'submit-edit-component-open' in request.POST: | ||||
|             return self.handle_new_component_post(request, open=True, **kwargs) | ||||
|         elif 'submit-advanced-search' in request.POST: | ||||
|             return self.handle_advanced_search_post(request, **kwargs) | ||||
|         else: | ||||
|             return super().post(request, *args, **kwargs) | ||||
|      | ||||
| class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
| class PackageView(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, TemplateView): | ||||
|     template_name = 'parts/packages.html' | ||||
|     base_title = 'Packages' | ||||
|     navbar_selected = 'Packages' | ||||
| @@ -340,7 +400,7 @@ class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|         return super().post(request, *args, **kwargs) | ||||
|      | ||||
|  | ||||
| class DistributorView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
| class DistributorView(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, TemplateView): | ||||
|     template_name = 'parts/distributors.html' | ||||
|     base_title = 'Distributors' | ||||
|     navbar_selected = 'Distributors' | ||||
| @@ -454,7 +514,7 @@ class StockView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
|  | ||||
|         return super().post(request, **kwargs) | ||||
|      | ||||
| class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
| class StockViewDetail(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, DetailView): | ||||
|     template_name = 'parts/stocks-detail.html' | ||||
|     model = Storage | ||||
|     pk_url_kwarg = 'uuid' | ||||
| @@ -516,7 +576,6 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|         context['add_storage_form'] = add_storage_form | ||||
|         context['delete_storage_error'] = None | ||||
|         context['add_stock_form'] = AddStockForm() | ||||
|  | ||||
|         return context | ||||
|  | ||||
|     def handle_add_storage_post(self, request, **kwargs): | ||||
| @@ -627,7 +686,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|  | ||||
| class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|     template_name = 'parts/components-detail.html' | ||||
|     model = Component | ||||
|     queryset = Component.objects.select_related('component_type', 'package', 'manufacturer').prefetch_related('componentparameter_set', 'distributornum_set') | ||||
|     pk_url_kwarg = 'uuid' | ||||
|     base_title = '' | ||||
|     navbar_selected = 'Components' | ||||
| @@ -640,8 +699,13 @@ class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|         context['comp_form'] = ComponentForm(instance=self.object) | ||||
|         context['new_distri_num_form'] = DistributorNumberCreateForm() | ||||
|         context['new_param_form'] = ComponentParameterCreateForm() | ||||
|         context['distri_nums'] = DistributorNum.objects.filter(component=self.object).order_by('distributor__name') | ||||
|         context['parameters'] = ComponentParameter.objects.filter(component=self.object).order_by('parameter_type__parameter_name') | ||||
|         context['distri_nums'] = self.object.distributornum_set.select_related('distributor').order_by('distributor__name') | ||||
|         context['parameters'] = self.object.componentparameter_set.order_by('parameter_type__parameter_name') | ||||
|         if self.object.package: | ||||
|             context['package_parameters'] = PackageParameter.objects.filter(package=self.object.package).order_by('parameter_type__parameter_name') | ||||
|          | ||||
|         parameter_texts = self.object.get_key_parameters_as_text() | ||||
|         context['key_parameter_string'] = ', '.join(parameter_texts) | ||||
|  | ||||
|         return context | ||||
|  | ||||
| @@ -851,7 +915,7 @@ class DistributorDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|          | ||||
|         return super().post(request, *args, **kwargs) | ||||
|  | ||||
| class ManufacturersViewSet(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
| class ManufacturersViewSet(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, TemplateView): | ||||
|     template_name = 'parts/manufacturers.html' | ||||
|     base_title = 'Manufacturers' | ||||
|     navbar_selected = 'Manufacturers' | ||||
|   | ||||
| @@ -31,7 +31,7 @@ class EngineeringNumberConverter(): | ||||
| 	] | ||||
|  | ||||
| 	@classmethod | ||||
|     def number_to_engineering(c, number, it_unit = False): | ||||
| 	def number_to_engineering(c, number, it_unit=False): | ||||
| 		""" | ||||
| 		Convert a number to engineering SI syntax with prefix. | ||||
| 		This function will return a tuple of (new_number, prefix) | ||||
|   | ||||
| @@ -6,8 +6,13 @@ from django.utils.deconstruct import deconstructible | ||||
| @deconstructible | ||||
| class RandomFileName(object): | ||||
| 	def __init__(self, path): | ||||
| 		self.path = os.path.join(path, "%s%s") | ||||
| 		self.path = os.path.join(path, "%s/%s/%s%s") | ||||
|  | ||||
| 	def __call__(self, _, filename): | ||||
| 		extension = os.path.splitext(filename)[1] | ||||
| 		return self.path % (uuid.uuid4(), extension) | ||||
| 		file_uuid = uuid.uuid4() | ||||
| 		uuid_str = str(file_uuid) | ||||
| 		first_char = uuid_str[0] | ||||
| 		second_char = uuid_str[1] | ||||
|  | ||||
| 		return self.path % (first_char, second_char, file_uuid, extension) | ||||
|   | ||||
| @@ -2,21 +2,21 @@ | ||||
| <nav aria-label="{{aria_label}}"> | ||||
|     <ul class="pagination"> | ||||
|         {% if paginator.has_previous %} | ||||
|             <li class="page-item"><a class="page-link" href="?{{get_param}}={{paginator.previous_page_number}}">«</a></li> | ||||
|             <li class="page-item"><a class="page-link" href="?{{get_param}}={{paginator.previous_page_number}}{% if additional_params %}&{{additional_params}}{% endif %}">«</a></li> | ||||
|         {% else %} | ||||
|             <li class="page-item disabled"><span class="page-link">«</span></li> | ||||
|         {% endif %} | ||||
|         {% for i in paginator.paginator.page_range %} | ||||
|             {% if i <= paginator.number|add:5 and i >= paginator.number|add:-5 %} | ||||
|                 {% if i == paginator.number %} | ||||
|                         <li class="page-item active"><a class="page-link" href="?{{get_param}}={{i}}">{{i}}</a></li> | ||||
|                         <li class="page-item active"><a class="page-link" href="?{{get_param}}={{i}}{% if additional_params %}&{{additional_params}}{% endif %}">{{i}}</a></li> | ||||
|                 {% else %} | ||||
|                     <li class="page-item"><a class="page-link" href="?{{get_param}}={{i}}">{{i}}</a></li> | ||||
|                     <li class="page-item"><a class="page-link" href="?{{get_param}}={{i}}{% if additional_params %}&{{additional_params}}{% endif %}">{{i}}</a></li> | ||||
|                 {% endif %} | ||||
|             {% endif %} | ||||
|         {% endfor %} | ||||
|         {% if paginator.has_next %} | ||||
|             <li class="page-item"><a class="page-link" href="?{{get_param}}={{paginator.next_page_number}}">»</a></li> | ||||
|             <li class="page-item"><a class="page-link" href="?{{get_param}}={{paginator.next_page_number}}{% if additional_params %}&{{additional_params}}{% endif %}">»</a></li> | ||||
|         {% else %} | ||||
|             <li class="page-item disabled"><span class="page-link">»</span></li> | ||||
|         {% endif %} | ||||
|   | ||||
| @@ -0,0 +1,27 @@ | ||||
| {% extends 'base.html' %} | ||||
| {% load crispy_forms_tags %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="container"> | ||||
|     <nav aria-label="breadcrumb" class="fs-4"> | ||||
|         <ol class="breadcrumb"> | ||||
|             <li class="breadcrumb-item"></li> | ||||
|             {% for crumb in breadcrumbs %} | ||||
|             <li class="breadcrumb-item"><a href="{% url 'parts-componenttypes-detail' uuid=crumb.id %}">{{crumb.class_name}}</a></li> | ||||
|             {% endfor %} | ||||
|             <li class="breadcrumb-item active" aria-current="page">{{object.class_name}}</li> | ||||
|         </ol> | ||||
|     </nav> | ||||
|     <div class="row"> | ||||
|         <div class="col-md"> | ||||
|             <h2>Component Type: {{object.class_name}}</h2> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| {% endblock content %} | ||||
| {% block custom_scripts %} | ||||
|  | ||||
| <script type="text/javascript"> | ||||
| </script> | ||||
| {% endblock custom_scripts %} | ||||
							
								
								
									
										46
									
								
								shimatta_kenkyusho/templates/parts/component-types.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								shimatta_kenkyusho/templates/parts/component-types.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| {% extends 'base.html' %} | ||||
| {% load crispy_forms_tags %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="container"> | ||||
|     <div class="row"> | ||||
|         <div class="col-md"> | ||||
|             <h2>Component Types</h2> | ||||
|             <form action="" method="get"> | ||||
|                 <div class="input-group mb-3"> | ||||
|                     <input class="form-control" name="search" type="search" placeholder="Search Component Type..." {% if search_string %}value="{{search_string}}"{% endif %}> | ||||
|                     <button type="submit" class="btn btn-primary"> | ||||
|                         <i class="bi bi-search"></i> | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </form> | ||||
|  | ||||
|             {% include 'paginator.html' with paginator=comptypes get_param='page' aria_label='Component Type Page Navigation' %} | ||||
|  | ||||
|             <div class="list-group mb-3"> | ||||
|                 {% for t in comptypes %} | ||||
|                     <a href="{% url 'parts-componenttypes-detail' uuid=t.id %}" class="text-decoration-none"> | ||||
|                     <li class="list-group-item list-group-item-action d-flex flex-row align-items-center justify-content-between"> | ||||
|                     <div class="p-2"> | ||||
|                         {{t}} | ||||
|                     </div> | ||||
|                     {% if t.passive %} | ||||
|                     <div class="p-2"> | ||||
|                         <span class="text-muted"> passive</span> | ||||
|                     </div> | ||||
|                     {% endif %} | ||||
|                     </li> | ||||
|                     </a> | ||||
|                 {% endfor %} | ||||
|             </div> | ||||
|             {% include 'paginator.html' with paginator=comptypes get_param='page' aria_label='Component Type Page Navigation' %} | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| {% endblock content %} | ||||
| {% block custom_scripts %} | ||||
|  | ||||
| <script type="text/javascript"> | ||||
| </script> | ||||
| {% endblock custom_scripts %} | ||||
| @@ -39,7 +39,7 @@ | ||||
|                 <tbody> | ||||
|                     <tr> | ||||
|                         <td class="align-middle" scope="row"> | ||||
|                             {{component.name}} | ||||
|                             {{component.name}}{% if key_parameter_string %}<br><span class="text-secondary">{{key_parameter_string}}</span>{% endif %} | ||||
|                         </td> | ||||
|                         <td class="align-middle" > | ||||
|                             {% if component.package %} | ||||
| @@ -119,6 +119,17 @@ | ||||
|                             <th scope="col"></th> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                             {% for param in package_parameters %} | ||||
|                             <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-secondary">from Package</span></td> | ||||
|                             {% endfor %} | ||||
|                             {% for param in parameters %} | ||||
|                             <tr> | ||||
|                                 <td> | ||||
| @@ -148,6 +159,13 @@ | ||||
|                         </div> | ||||
|                         {% endif %} | ||||
|                         {% 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 class="col"> | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
|                 </div> | ||||
|             </form> | ||||
|             <div class="collapse mb-3{% if advanced_search_shown %} show{% endif %}" id="advanced-search-collapse" aria-expanded="{% if advanced_search_shown %}true{% else %}false{% endif %}"> | ||||
|                 <form method="POST"> | ||||
|                 <form method="GET"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm"> | ||||
|                             {% crispy advanced_search_form %} | ||||
| @@ -48,7 +48,12 @@ | ||||
|                             {% endif %} | ||||
|                         </div> | ||||
|                         <div class="flex-grow-1 ms-3"> | ||||
|                             <h6 class="mt-0 text-primary">{{ comp.name }}</h6> | ||||
|                             <h6 class="mt-0 text-primary"> | ||||
|                                 {{ comp.name }} | ||||
|                                 {% for key_param in comp.get_key_parameters_as_text %} | ||||
|                                 {{key_param}} | ||||
|                                 {% endfor %} | ||||
|                             </h6> | ||||
|                             {% if comp.package %} | ||||
|                                 Package: {{comp.package}}<br> | ||||
|                             {% endif %} | ||||
|   | ||||
| @@ -69,7 +69,11 @@ | ||||
|                             {% endif %} | ||||
|                         </div> | ||||
|                         <div class="flex-grow-1 ms-3"> | ||||
|                             <h6 class="mt-0 text-primary"><a href="{% url 'parts-components-detail' uuid=stock.component.id %}" class="text-decoration-none">{{ stock.component.name }}</a></h6> | ||||
|                             <h6 class="mt-0 text-primary"><a href="{% url 'parts-components-detail' uuid=stock.component.id %}" class="text-decoration-none">{{ stock.component.name }}</a> | ||||
|                             {% for key_param in stock.component.get_key_parameters_as_text %} | ||||
|                                 {{key_param}}  | ||||
|                             {% endfor %} | ||||
|                              </h6> | ||||
|                             {% if stock.component.package %} | ||||
|                                 Package: {{stock.component.package}}<br> | ||||
|                             {% endif %} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user