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() |     queryset = parts_models.Component.objects.all() | ||||||
|     serializer_class = ComponentSerializer |     serializer_class = ComponentSerializer | ||||||
|     permission_classes = [permissions.DjangoModelPermissions] |     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'] |     search_fields = ['id', 'name', 'package__name', 'manufacturer__name'] | ||||||
|  |     filterset_fields = ['id', 'name'] | ||||||
|  |  | ||||||
| class PartsComponentTypeViewSet(viewsets.ModelViewSet): | class PartsComponentTypeViewSet(viewsets.ModelViewSet): | ||||||
|     queryset = parts_models.ComponentType.objects.all() |     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.Storage) | ||||||
| admin.site.register(parts_models.Stock) | admin.site.register(parts_models.Stock) | ||||||
| admin.site.register(parts_models.ComponentParameter) | admin.site.register(parts_models.ComponentParameter) | ||||||
|  | admin.site.register(parts_models.PackageParameter) | ||||||
| admin.site.register(parts_models.ComponentParameterType) | admin.site.register(parts_models.ComponentParameterType) | ||||||
| admin.site.register(parts_models.ComponentType) | admin.site.register(parts_models.ComponentType) | ||||||
| admin.site.register(parts_models.Distributor) | admin.site.register(parts_models.Distributor) | ||||||
|   | |||||||
| @@ -244,6 +244,7 @@ class AdvancedComponentSearchForm(forms.Form): | |||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|         self.helper = FormHelper() |         self.helper = FormHelper() | ||||||
|         self.helper.form_tag = False |         self.helper.form_tag = False | ||||||
|  |         self.helper.disable_csrf = True | ||||||
|         self.helper.layout = Layout( |         self.helper.layout = Layout( | ||||||
|             Row( |             Row( | ||||||
|                 Column('name'), |                 Column('name'), | ||||||
| @@ -262,17 +263,27 @@ class AdvancedComponentSearchForm(forms.Form): | |||||||
|             ), |             ), | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | PARAMETER_COMPARISON_TYPES = ( | ||||||
|  |     ('eq', '=='), | ||||||
|  |     ('lte', '<='), | ||||||
|  |     ('gte', '>='), | ||||||
|  | ) | ||||||
|  |  | ||||||
| class ComponentParameterSearchForm(forms.Form): | 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') |     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) |     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): |     def __init__(self, *args, **kwargs): | ||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|         self.helper = FormHelper() |         self.helper = FormHelper() | ||||||
|         self.helper.form_tag = False |         self.helper.form_tag = False | ||||||
|  |         self.helper.disable_csrf = True | ||||||
|         self.helper.layout = Layout( |         self.helper.layout = Layout( | ||||||
|             Row( |             Row( | ||||||
|                 Column('parameter'), |                 Column('parameter'), | ||||||
|  |                 Column('compare_method'), | ||||||
|                 Column('value') |                 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: | 	class Meta: | ||||||
| 		ordering = ['class_name'] | 		ordering = ['class_name'] | ||||||
| 	class_name = models.CharField(max_length=50, unique=True) | 	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() | 	passive = models.BooleanField() | ||||||
| 	possible_parameter = models.ManyToManyField(ComponentParameterType, blank=True) | 	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): | 	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 Storage(models.Model): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| @@ -144,6 +168,37 @@ class Package(models.Model): | |||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return self.name | 		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 Manufacturer(models.Model): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| @@ -206,7 +261,34 @@ class Component(models.Model): | |||||||
| 			sum = 0 | 			sum = 0 | ||||||
| 		return sum | 		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 ComponentParameter(models.Model): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		unique_together = ('component', 'parameter_type') | 		unique_together = ('component', 'parameter_type') | ||||||
| @@ -231,10 +313,12 @@ class ComponentParameter(models.Model): | |||||||
| 		if my_type == 'E' or my_type == 'I': | 		if my_type == 'E' or my_type == 'I': | ||||||
| 			# Engineering float number | 			# Engineering float number | ||||||
| 			(num, prefix) = NumConv.number_to_engineering(self.value, it_unit=(True if my_type=='I' else False)) | 			(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': | 		elif my_type == 'N': | ||||||
| 			# Standard float number | 			# 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': | 		elif my_type == 'F': | ||||||
| 			return self.text_value | 			return self.text_value | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ from . import views as parts_views | |||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|     path('', parts_views.MainView.as_view(), name='parts-main'), |     path('', parts_views.MainView.as_view(), name='parts-main'), | ||||||
|     path('components/', parts_views.ComponentView.as_view(), name='parts-components'), |     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('packages/', parts_views.PackageView.as_view(), name='parts-packages'), | ||||||
|     path('distributors/', parts_views.DistributorView.as_view(), name='parts-distributors'), |     path('distributors/', parts_views.DistributorView.as_view(), name='parts-distributors'), | ||||||
|     path('stocks/', parts_views.StockView.as_view(), name='parts-stocks'), |     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('distributors/<slug:uuid>/', parts_views.DistributorDetailView.as_view(), name='parts-distributors-detail'), | ||||||
|     path('manufacturers/', parts_views.ManufacturersViewSet.as_view(), name='parts-manufacturers'), |     path('manufacturers/', parts_views.ManufacturersViewSet.as_view(), name='parts-manufacturers'), | ||||||
|     path("manufacturers/<slug:uuid>/", parts_views.ManufacturerDetailViewSet.as_view(), name='parts-manufacturers-detail'), |     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.urls import resolve, reverse | ||||||
| from django.contrib.auth import logout, login | from django.contrib.auth import logout, login | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  | from django.utils.http import urlencode | ||||||
| from django.http import HttpResponse | from django.http import HttpResponse | ||||||
| from .navbar import NavBar | from .navbar import NavBar | ||||||
| from django.contrib.auth.forms import AuthenticationForm as AuthForm | from django.contrib.auth.forms import AuthenticationForm as AuthForm | ||||||
| @@ -11,7 +12,9 @@ from django.views import View | |||||||
| import django.forms as forms | import django.forms as forms | ||||||
| from django.views.generic import TemplateView, DetailView | from django.views.generic import TemplateView, DetailView | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin | 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 .qr_parser import QrCodeValidator | ||||||
| from django.core.paginator import Paginator | from django.core.paginator import Paginator | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| @@ -19,11 +22,12 @@ from django.db import IntegrityError | |||||||
| from django.db.models import ProtectedError | from django.db.models import ProtectedError | ||||||
| from .forms import * | from .forms import * | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
|  | from django.db.models import Prefetch | ||||||
| from django.db.models.functions import Lower | from django.db.models.functions import Lower | ||||||
| from django.forms import formset_factory | from django.forms import formset_factory | ||||||
| import uuid | import uuid | ||||||
|  |  | ||||||
| ParameterSearchFormSet = formset_factory(ComponentParameterSearchForm, extra=2) | ParameterSearchFormSet = formset_factory(ComponentParameterSearchForm, extra=0) | ||||||
|  |  | ||||||
| class QrSearchForm(forms.Form): | class QrSearchForm(forms.Form): | ||||||
|     my_qr_validator = QrCodeValidator() |     my_qr_validator = QrCodeValidator() | ||||||
| @@ -34,6 +38,15 @@ class QrSearchForm(forms.Form): | |||||||
|  |  | ||||||
|     qr_search = forms.CharField(label='qr_search', validators=[my_qr_validator]) |     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): | class BaseTemplateMixin(object): | ||||||
|     navbar_selected = '' |     navbar_selected = '' | ||||||
|     base_title = '' |     base_title = '' | ||||||
| @@ -142,16 +155,69 @@ def login_view(request): | |||||||
|  |  | ||||||
| # Create your views here. | # 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' |     template_name = 'parts/components.html' | ||||||
|     base_title = 'Components' |     base_title = 'Components' | ||||||
|     navbar_selected = 'Components' |     navbar_selected = 'Components' | ||||||
|     default_page_size = 25 |     default_page_size = 25 | ||||||
|  |  | ||||||
|     def get_component_query_set(self, search_string): |     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 |             return queryset | ||||||
|  |  | ||||||
|         search_fragments = search_string.strip().split() |         search_fragments = search_string.strip().split() | ||||||
| @@ -161,7 +227,7 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | |||||||
|         return queryset |         return queryset | ||||||
|  |  | ||||||
|     def get_component_queryset_from_advanced_search(self, cleaned_data): |     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']: |         if cleaned_data['name']: | ||||||
|             queryset = queryset.filter(Q(name__icontains=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']) |             queryset = queryset.filter(manufacturer=cleaned_data['manufacturer']) | ||||||
|         return queryset |         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 == ''): |         if parameter and (value is None or value == ''): | ||||||
|             return queryset.filter(Q(componentparameter__parameter_type=parameter)) |             return queryset.filter(Q(componentparameter__parameter_type=parameter)) | ||||||
|         elif parameter and value is not None: |         elif parameter and value is not None: | ||||||
|             if parameter.parameter_type == 'F': |             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: |             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 |         return queryset | ||||||
|  |  | ||||||
|     def get_context_data_int(self, advanced_search, parameter_formset : ParameterSearchFormSet, **kwargs): |     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(): |             if advanced_search.is_valid(): | ||||||
|                 paginator_queryset = self.get_component_queryset_from_advanced_search(advanced_search.cleaned_data) |                 paginator_queryset = self.get_component_queryset_from_advanced_search(advanced_search.cleaned_data) | ||||||
|             else: |             else: | ||||||
|                 paginator_queryset = Component.objects.all() |                 paginator_queryset = self.get_component_query_set(None) | ||||||
|  |  | ||||||
|             if parameter_formset.is_valid(): |             # Process parameters | ||||||
|                 # Process parameters |             for f in parameter_formset: | ||||||
|                 for f in parameter_formset: |                 # If the form is valid and has changed compared to its initial empty state | ||||||
|                     # If the form is valid and has changed compared to its initial empty state |                 if f.is_valid() and f.has_changed(): | ||||||
|                     if f.is_valid() and f.has_changed(): |                     paginator_queryset = self.filter_queryset_with_parameters(paginator_queryset, f.cleaned_data['parameter'], f.cleaned_data['value'], f.cleaned_data['compare_method']) | ||||||
|                         print(f.cleaned_data) |  | ||||||
|                         paginator_queryset = self.filter_queryset_with_parameters(paginator_queryset, f.cleaned_data['parameter'], f.cleaned_data['value']) |  | ||||||
|                          |                          | ||||||
|              |              | ||||||
|         else: |         else: | ||||||
| @@ -228,15 +296,24 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | |||||||
|  |  | ||||||
|         if not parameter_formset: |         if not parameter_formset: | ||||||
|             context['advanced_search_param_formset'] = ParameterSearchFormSet() |             context['advanced_search_param_formset'] = ParameterSearchFormSet() | ||||||
|  |  | ||||||
|  |  | ||||||
|         if not advanced_search: |         if not advanced_search: | ||||||
|             context['advanced_search_form'] = AdvancedComponentSearchForm(auto_id='adv_search_%s') |             context['advanced_search_form'] = AdvancedComponentSearchForm(auto_id='adv_search_%s') | ||||||
|  |  | ||||||
|         return context |         return context | ||||||
|      |      | ||||||
|     def get_context_data(self, **kwargs): |     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): |     def handle_new_component_post(self, request, open=False, **kwargs): | ||||||
| @@ -252,33 +329,16 @@ class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | |||||||
|         if open and new_component: |         if open and new_component: | ||||||
|             return redirect(reverse('parts-components-detail', kwargs={'uuid':new_component.id})) |             return redirect(reverse('parts-components-detail', kwargs={'uuid':new_component.id})) | ||||||
|         return self.render_to_response(context) |         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): |     def post(self, request, *args, **kwargs): | ||||||
|         if 'submit-edit-component' in request.POST: |         if 'submit-edit-component' in request.POST: | ||||||
|             return self.handle_new_component_post(request, open=False, **kwargs) |             return self.handle_new_component_post(request, open=False, **kwargs) | ||||||
|         elif 'submit-edit-component-open' in request.POST: |         elif 'submit-edit-component-open' in request.POST: | ||||||
|             return self.handle_new_component_post(request, open=True, **kwargs) |             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: |         else: | ||||||
|             return super().post(request, *args, **kwargs) |             return super().post(request, *args, **kwargs) | ||||||
|      |      | ||||||
| class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | class PackageView(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, TemplateView): | ||||||
|     template_name = 'parts/packages.html' |     template_name = 'parts/packages.html' | ||||||
|     base_title = 'Packages' |     base_title = 'Packages' | ||||||
|     navbar_selected = 'Packages' |     navbar_selected = 'Packages' | ||||||
| @@ -340,7 +400,7 @@ class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | |||||||
|         return super().post(request, *args, **kwargs) |         return super().post(request, *args, **kwargs) | ||||||
|      |      | ||||||
|  |  | ||||||
| class DistributorView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | class DistributorView(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, TemplateView): | ||||||
|     template_name = 'parts/distributors.html' |     template_name = 'parts/distributors.html' | ||||||
|     base_title = 'Distributors' |     base_title = 'Distributors' | ||||||
|     navbar_selected = 'Distributors' |     navbar_selected = 'Distributors' | ||||||
| @@ -454,7 +514,7 @@ class StockView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | |||||||
|  |  | ||||||
|         return super().post(request, **kwargs) |         return super().post(request, **kwargs) | ||||||
|      |      | ||||||
| class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): | class StockViewDetail(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, DetailView): | ||||||
|     template_name = 'parts/stocks-detail.html' |     template_name = 'parts/stocks-detail.html' | ||||||
|     model = Storage |     model = Storage | ||||||
|     pk_url_kwarg = 'uuid' |     pk_url_kwarg = 'uuid' | ||||||
| @@ -516,7 +576,6 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): | |||||||
|         context['add_storage_form'] = add_storage_form |         context['add_storage_form'] = add_storage_form | ||||||
|         context['delete_storage_error'] = None |         context['delete_storage_error'] = None | ||||||
|         context['add_stock_form'] = AddStockForm() |         context['add_stock_form'] = AddStockForm() | ||||||
|  |  | ||||||
|         return context |         return context | ||||||
|  |  | ||||||
|     def handle_add_storage_post(self, request, **kwargs): |     def handle_add_storage_post(self, request, **kwargs): | ||||||
| @@ -627,7 +686,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): | |||||||
|  |  | ||||||
| class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||||
|     template_name = 'parts/components-detail.html' |     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' |     pk_url_kwarg = 'uuid' | ||||||
|     base_title = '' |     base_title = '' | ||||||
|     navbar_selected = 'Components' |     navbar_selected = 'Components' | ||||||
| @@ -640,8 +699,13 @@ class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | |||||||
|         context['comp_form'] = ComponentForm(instance=self.object) |         context['comp_form'] = ComponentForm(instance=self.object) | ||||||
|         context['new_distri_num_form'] = DistributorNumberCreateForm() |         context['new_distri_num_form'] = DistributorNumberCreateForm() | ||||||
|         context['new_param_form'] = ComponentParameterCreateForm() |         context['new_param_form'] = ComponentParameterCreateForm() | ||||||
|         context['distri_nums'] = DistributorNum.objects.filter(component=self.object).order_by('distributor__name') |         context['distri_nums'] = self.object.distributornum_set.select_related('distributor').order_by('distributor__name') | ||||||
|         context['parameters'] = ComponentParameter.objects.filter(component=self.object).order_by('parameter_type__parameter_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 |         return context | ||||||
|  |  | ||||||
| @@ -851,7 +915,7 @@ class DistributorDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | |||||||
|          |          | ||||||
|         return super().post(request, *args, **kwargs) |         return super().post(request, *args, **kwargs) | ||||||
|  |  | ||||||
| class ManufacturersViewSet(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | class ManufacturersViewSet(LoginRequiredMixin, KeepSearchParamMixin, BaseTemplateMixin, TemplateView): | ||||||
|     template_name = 'parts/manufacturers.html' |     template_name = 'parts/manufacturers.html' | ||||||
|     base_title = 'Manufacturers' |     base_title = 'Manufacturers' | ||||||
|     navbar_selected = 'Manufacturers' |     navbar_selected = 'Manufacturers' | ||||||
|   | |||||||
| @@ -1,67 +1,67 @@ | |||||||
|  |  | ||||||
| class EngineeringNumberConverter(): | class EngineeringNumberConverter(): | ||||||
|  |  | ||||||
|     prefixes = [ | 	prefixes = [ | ||||||
|         ('y', 1e-24), | 		('y', 1e-24), | ||||||
|         ('z', 1e-21), | 		('z', 1e-21), | ||||||
|         ('a', 1e-18), | 		('a', 1e-18), | ||||||
|         ('f', 1e-15), | 		('f', 1e-15), | ||||||
|         ('p', 1e-12), | 		('p', 1e-12), | ||||||
|         ('n', 1e-9), | 		('n', 1e-9), | ||||||
|         ('u', 1e-6), | 		('u', 1e-6), | ||||||
|         ('m', 1e-3), | 		('m', 1e-3), | ||||||
|         # We skip centi and dezi because no one really uses these besides for length measurements | 		# We skip centi and dezi because no one really uses these besides for length measurements | ||||||
|         ('', 1), | 		('', 1), | ||||||
|         # We also skip h for hekto | 		# We also skip h for hekto | ||||||
|         ('k', 1e3), | 		('k', 1e3), | ||||||
|         ('M', 1e6), | 		('M', 1e6), | ||||||
|         ('G', 1e9), | 		('G', 1e9), | ||||||
|         ('T', 1e12), | 		('T', 1e12), | ||||||
|         ('P', 1e15), | 		('P', 1e15), | ||||||
|         ('E', 1e18), | 		('E', 1e18), | ||||||
|         ('Z', 1e21), | 		('Z', 1e21), | ||||||
|         ('Y', 1e24), | 		('Y', 1e24), | ||||||
|     ] | 	] | ||||||
|     it_prefixes = [ | 	it_prefixes = [ | ||||||
|         ('', 1), | 		('', 1), | ||||||
|         ('Ki', 1024), | 		('Ki', 1024), | ||||||
|         ('Mi', 1024*1024), | 		('Mi', 1024*1024), | ||||||
|         ('Gi', 1024*1024*1024), | 		('Gi', 1024*1024*1024), | ||||||
|         ('Ti', 1024*1024*1024*1024) | 		('Ti', 1024*1024*1024*1024) | ||||||
|     ] | 	] | ||||||
|      |  | ||||||
|     @classmethod |  | ||||||
|     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) |  | ||||||
|         """ |  | ||||||
|         if it_unit: |  | ||||||
|             used_prefixes = c.it_prefixes |  | ||||||
|         else: |  | ||||||
|             used_prefixes = c.prefixes |  | ||||||
|  |  | ||||||
|         if (len(used_prefixes) < 2): | 	@classmethod | ||||||
|             return (number / used_prefixes[0][1], used_prefixes[0]) | 	def number_to_engineering(c, number, it_unit=False): | ||||||
|          | 		""" | ||||||
|         for i, (prefix, scale) in enumerate(used_prefixes[1:], 1): | 		Convert a number to engineering SI syntax with prefix. | ||||||
|             if number < scale: | 		This function will return a tuple of (new_number, prefix) | ||||||
|                 return (number / used_prefixes[i-1][1], used_prefixes[i-1][0]) | 		""" | ||||||
|          | 		if it_unit: | ||||||
|         return (number / used_prefixes[-1][1], used_prefixes[-1][0]) | 			used_prefixes = c.it_prefixes | ||||||
|  | 		else: | ||||||
|  | 			used_prefixes = c.prefixes | ||||||
|  |  | ||||||
|     @classmethod | 		if (len(used_prefixes) < 2): | ||||||
|     def engineering_to_number(c, input): | 			return (number / used_prefixes[0][1], used_prefixes[0]) | ||||||
|         cleaned_input = input.strip().replace(' ', '') |  | ||||||
|  |  | ||||||
|         selected_scaling = 1 | 		for i, (prefix, scale) in enumerate(used_prefixes[1:], 1): | ||||||
|  | 			if number < scale: | ||||||
|  | 				return (number / used_prefixes[i-1][1], used_prefixes[i-1][0]) | ||||||
|  |  | ||||||
|         for (prefix, scale) in c.prefixes+c.it_prefixes: | 		return (number / used_prefixes[-1][1], used_prefixes[-1][0]) | ||||||
|             if prefix == '': |  | ||||||
|                 continue | 	@classmethod | ||||||
|             if cleaned_input.endswith(prefix): | 	def engineering_to_number(c, input): | ||||||
|                 cleaned_input = cleaned_input.replace(prefix, '') | 		cleaned_input = input.strip().replace(' ', '') | ||||||
|                 selected_scaling = scale |  | ||||||
|                 break | 		selected_scaling = 1 | ||||||
|          |  | ||||||
|         return float(cleaned_input) * selected_scaling | 		for (prefix, scale) in c.prefixes+c.it_prefixes: | ||||||
|  | 			if prefix == '': | ||||||
|  | 				continue | ||||||
|  | 			if cleaned_input.endswith(prefix): | ||||||
|  | 				cleaned_input = cleaned_input.replace(prefix, '') | ||||||
|  | 				selected_scaling = scale | ||||||
|  | 				break | ||||||
|  |  | ||||||
|  | 		return float(cleaned_input) * selected_scaling | ||||||
|   | |||||||
| @@ -6,8 +6,13 @@ from django.utils.deconstruct import deconstructible | |||||||
| @deconstructible | @deconstructible | ||||||
| class RandomFileName(object): | class RandomFileName(object): | ||||||
| 	def __init__(self, path): | 	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): | 	def __call__(self, _, filename): | ||||||
| 		extension = os.path.splitext(filename)[1] | 		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}}"> | <nav aria-label="{{aria_label}}"> | ||||||
|     <ul class="pagination"> |     <ul class="pagination"> | ||||||
|         {% if paginator.has_previous %} |         {% 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 %} |         {% else %} | ||||||
|             <li class="page-item disabled"><span class="page-link">«</span></li> |             <li class="page-item disabled"><span class="page-link">«</span></li> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|         {% for i in paginator.paginator.page_range %} |         {% for i in paginator.paginator.page_range %} | ||||||
|             {% if i <= paginator.number|add:5 and i >= paginator.number|add:-5 %} |             {% if i <= paginator.number|add:5 and i >= paginator.number|add:-5 %} | ||||||
|                 {% if i == paginator.number %} |                 {% 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 %} |                 {% 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 %} | ||||||
|             {% endif %} |             {% endif %} | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|         {% if paginator.has_next %} |         {% 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 %} |         {% else %} | ||||||
|             <li class="page-item disabled"><span class="page-link">»</span></li> |             <li class="page-item disabled"><span class="page-link">»</span></li> | ||||||
|         {% endif %} |         {% 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> |                 <tbody> | ||||||
|                     <tr> |                     <tr> | ||||||
|                         <td class="align-middle" scope="row"> |                         <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> | ||||||
|                         <td class="align-middle" > |                         <td class="align-middle" > | ||||||
|                             {% if component.package %} |                             {% if component.package %} | ||||||
| @@ -119,6 +119,17 @@ | |||||||
|                             <th scope="col"></th> |                             <th scope="col"></th> | ||||||
|                         </thead> |                         </thead> | ||||||
|                         <tbody> |                         <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 %} |                             {% for param in parameters %} | ||||||
|                             <tr> |                             <tr> | ||||||
|                                 <td> |                                 <td> | ||||||
| @@ -148,6 +159,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"> | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ | |||||||
|                 </div> |                 </div> | ||||||
|             </form> |             </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 %}"> |             <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="row"> | ||||||
|                         <div class="col-sm"> |                         <div class="col-sm"> | ||||||
|                             {% crispy advanced_search_form %} |                             {% crispy advanced_search_form %} | ||||||
| @@ -48,7 +48,12 @@ | |||||||
|                             {% endif %} |                             {% endif %} | ||||||
|                         </div> |                         </div> | ||||||
|                         <div class="flex-grow-1 ms-3"> |                         <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 %} |                             {% if comp.package %} | ||||||
|                                 Package: {{comp.package}}<br> |                                 Package: {{comp.package}}<br> | ||||||
|                             {% endif %} |                             {% endif %} | ||||||
|   | |||||||
| @@ -69,7 +69,11 @@ | |||||||
|                             {% endif %} |                             {% endif %} | ||||||
|                         </div> |                         </div> | ||||||
|                         <div class="flex-grow-1 ms-3"> |                         <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 %} |                             {% if stock.component.package %} | ||||||
|                                 Package: {{stock.component.package}}<br> |                                 Package: {{stock.component.package}}<br> | ||||||
|                             {% endif %} |                             {% endif %} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user