diff --git a/shimatta_kenkyusho/api/serializers.py b/shimatta_kenkyusho/api/serializers.py
index 183e134..e31505b 100644
--- a/shimatta_kenkyusho/api/serializers.py
+++ b/shimatta_kenkyusho/api/serializers.py
@@ -44,6 +44,7 @@ class ComponentSerializer(serializers.HyperlinkedModelSerializer):
ro_component_type = serializers.ReadOnlyField(source='component_type.class_name')
ro_parameters = ComponentParameterSerializer(many=True, source='componentparameter_set', read_only=True)
ro_distributor_numbers = ComponentDistributorNumSerializer(many=True, source='distributornum_set', read_only=True)
+ ro_dynamic_description = serializers.ReadOnlyField(source='dynamic_description')
class Meta:
model = parts_models.Component
@@ -60,12 +61,14 @@ class ComponentSerializer(serializers.HyperlinkedModelSerializer):
'ro_image',
'ro_component_type',
'ro_parameters',
- 'ro_distributor_numbers']
+ 'ro_distributor_numbers',
+ 'ro_dynamic_description']
class StockSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField()
ro_package_name = serializers.ReadOnlyField(source='component.package.name')
ro_component_name = serializers.ReadOnlyField(source='component.name')
+ ro_component_dynamic_description = serializers.ReadOnlyField(source='component.dynamic_description')
ro_manufacturer_name = serializers.ReadOnlyField(source='component.manufacturer.name')
ro_image = serializers.ReadOnlyField(source='component.get_resolved_image')
class Meta:
@@ -80,7 +83,7 @@ class StorageSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = parts_models.Storage
- fields = ['url', 'id', 'name', 'verbose_name', 'parent_storage', 'responsible', 'template', 'full_path']
+ fields = ['url', 'id', 'name', 'verbose_name', 'parent_storage', 'responsible', 'template', 'full_path', 'full_path_verbose']
class StorageSerializerStocksExpanded(StorageSerializer):
ro_stocks = StockSerializerExpandComponent(many=True, read_only=True, source='stock_set')
diff --git a/shimatta_kenkyusho/api/views.py b/shimatta_kenkyusho/api/views.py
index c6d8ef7..241abe8 100644
--- a/shimatta_kenkyusho/api/views.py
+++ b/shimatta_kenkyusho/api/views.py
@@ -43,8 +43,8 @@ class GroupViewSet(viewsets.ReadOnlyModelViewSet):
class PartsStorageViewSet(viewsets.ModelViewSet):
queryset = parts_models.Storage.objects.all()
permission_classes = [permissions.DjangoModelPermissions]
- filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
- search_fields = ['id', 'name', 'parent_storage']
+ filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.SearchFilter]
+ search_fields = ['id', 'name', 'verbose_name']
filterset_fields = ['id', 'name', 'parent_storage']
def get_serializer_class(self):
diff --git a/shimatta_kenkyusho/parts/forms.py b/shimatta_kenkyusho/parts/forms.py
index 44752e6..8eaaa2f 100644
--- a/shimatta_kenkyusho/parts/forms.py
+++ b/shimatta_kenkyusho/parts/forms.py
@@ -53,7 +53,7 @@ class AutoCompleteWidget(widgets.Input):
'name_field_name': self.name_field_name,
'prepend': self.prepend,
'name': display_name,
- }
+ }
return context
class AutocompleteForeingKeyField(forms.UUIDField):
@@ -72,7 +72,7 @@ class AutocompleteForeingKeyField(forms.UUIDField):
prepend)
self.foreign_model = foreign_model
-
+
def clean(self, value):
try:
pre_cleaned_uuid = super().clean(value)
@@ -101,7 +101,8 @@ class ChangeStorageForm(forms.Form):
foreign_model=get_user_model(),
prepend='@')
- is_template = forms.BooleanField(label='is_template', required=False)
+ expand_sub_storage_stocks = forms.BooleanField(label='Expand sub storage Stocks', required=False)
+ is_template = forms.BooleanField(label='Is template', required=False)
class AddSubStorageForm(ChangeStorageForm):
template = AutocompleteForeingKeyField(api_search_url='storage-template-list',
@@ -112,15 +113,13 @@ class AddSubStorageForm(ChangeStorageForm):
class DeleteStockForm(forms.Form):
stock_uuid = forms.UUIDField()
-class EditWatermarkForm(forms.Form):
+class EditStockBaseForm(forms.Form):
stock_uuid = forms.UUIDField()
- watermark_active = forms.BooleanField(required=False) #If it is false, the webbrowser won't send it at all. Therefore we have to set it to required=False
- watermark = forms.IntegerField(min_value=0)
def clean(self):
cleaned_data = super().clean()
id = cleaned_data.get("stock_uuid")
-
+
if not id:
raise ValidationError("No stock UUID given")
@@ -133,6 +132,10 @@ class EditWatermarkForm(forms.Form):
return cleaned_data
+class EditWatermarkForm(EditStockBaseForm):
+ watermark_active = forms.BooleanField(required=False) #If it is false, the webbrowser won't send it at all. Therefore we have to set it to required=False
+ watermark = forms.IntegerField(min_value=0)
+
def save(self):
stock = self.cleaned_data['stock']
active = self.cleaned_data['watermark_active']
@@ -144,32 +147,36 @@ class EditWatermarkForm(forms.Form):
stock.watermark = watermark
stock.save()
-class EditStockAmountForm(forms.Form):
- stock_uuid = forms.UUIDField()
+class EditLotForm(EditStockBaseForm):
+ lot = forms.IntegerField(min_value=0)
+
+ def save(self):
+ stock = self.cleaned_data['stock']
+ lot = self.cleaned_data['lot']
+
+ stock.lot = lot
+ stock.save()
+
+class RelocateStockForm(forms.ModelForm):
+ storage = AutocompleteForeingKeyField(api_search_url='storage-list',
+ foreign_model=parts_models.Storage,
+ name_field_name='full_path_verbose',
+ image_field_name=None,
+ required=True)
+
+ class Meta:
+ model = parts_models.Stock
+ fields = ['storage']
+
+class EditStockAmountForm(EditStockBaseForm):
amount = forms.IntegerField(min_value=0)
- def clean(self):
- cleaned_data = super().clean()
- id = cleaned_data.get("stock_uuid")
-
- if not id:
- raise ValidationError("No stock UUID given")
-
- stock = None
- try:
- stock = parts_models.Stock.objects.get(id=id)
- except:
- raise ValidationError("Stock with uuid %s does not exist" % (id))
- cleaned_data['stock'] = stock
-
- return cleaned_data
-
def save(self, increase: bool):
stock = self.cleaned_data['stock']
amount = self.cleaned_data['amount']
if not increase:
- amount = -amount
+ amount = -amount
return stock.atomic_increment(amount)
@@ -194,7 +201,7 @@ class AddStockForm(forms.Form):
cleaned_data['component'] = component
return cleaned_data
-
+
def save(self, storage):
component = self.cleaned_data.get('component')
amount = self.cleaned_data.get('amount')
@@ -253,7 +260,7 @@ class DistributorNumberDeleteForm(forms.Form):
class ComponentParameterDeleteForm(forms.Form):
param_num = forms.UUIDField(required=True)
model = parts_models.ComponentParameter
-
+
def clean_param_num(self):
my_uuid = self.cleaned_data['param_num']
try:
@@ -322,7 +329,7 @@ class ComponentParameterCreateForm(forms.Form):
if not ptype:
raise ValidationError('No valid parameter type selected')
-
+
if not value:
raise ValidationError('No valid parameter value')
@@ -334,7 +341,7 @@ class ComponentParameterCreateForm(forms.Form):
data['number_value'] = number
else:
pass
-
+
def save(self, component):
param_type = self.cleaned_data['parameter_type']
if param_type.parameter_type == 'F':
@@ -363,6 +370,6 @@ class QrSearchForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
-
+
qr_search = forms.CharField(label='qr_search', validators=[my_qr_validator])
\ No newline at end of file
diff --git a/shimatta_kenkyusho/parts/migrations/0014_storage_expand_sub_storage_stocks.py b/shimatta_kenkyusho/parts/migrations/0014_storage_expand_sub_storage_stocks.py
new file mode 100644
index 0000000..9dc3c30
--- /dev/null
+++ b/shimatta_kenkyusho/parts/migrations/0014_storage_expand_sub_storage_stocks.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.3 on 2025-01-25 14:01
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('parts', '0013_packageparameter'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='storage',
+ name='expand_sub_storage_stocks',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/shimatta_kenkyusho/parts/migrations/0015_componenttype_description_template.py b/shimatta_kenkyusho/parts/migrations/0015_componenttype_description_template.py
new file mode 100644
index 0000000..0d40d19
--- /dev/null
+++ b/shimatta_kenkyusho/parts/migrations/0015_componenttype_description_template.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.3 on 2025-01-31 21:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('parts', '0014_storage_expand_sub_storage_stocks'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='componenttype',
+ name='description_template',
+ field=models.TextField(blank=True),
+ ),
+ ]
diff --git a/shimatta_kenkyusho/parts/migrations/0016_componentparametertype_interfix.py b/shimatta_kenkyusho/parts/migrations/0016_componentparametertype_interfix.py
new file mode 100644
index 0000000..ded8b86
--- /dev/null
+++ b/shimatta_kenkyusho/parts/migrations/0016_componentparametertype_interfix.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.3 on 2025-01-31 21:44
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('parts', '0015_componenttype_description_template'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='componentparametertype',
+ name='interfix',
+ field=models.CharField(blank=True, max_length=10),
+ ),
+ ]
diff --git a/shimatta_kenkyusho/parts/migrations/0017_alter_componentparametertype_interfix_and_more.py b/shimatta_kenkyusho/parts/migrations/0017_alter_componentparametertype_interfix_and_more.py
new file mode 100644
index 0000000..d1c6b53
--- /dev/null
+++ b/shimatta_kenkyusho/parts/migrations/0017_alter_componentparametertype_interfix_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.1.3 on 2025-02-02 10:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('parts', '0016_componentparametertype_interfix'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='componentparametertype',
+ name='interfix',
+ field=models.CharField(blank=True, help_text='char to be used as decimal point in dynamic description eg. 2R2', max_length=10),
+ ),
+ migrations.AlterField(
+ model_name='componenttype',
+ name='description_template',
+ field=models.TextField(blank=True, help_text="Template to assemble the dynamic description. Use template syntax, access the component with 'object', parameters with 'param_*'."),
+ ),
+ ]
diff --git a/shimatta_kenkyusho/parts/models.py b/shimatta_kenkyusho/parts/models.py
index f5d4ec4..c5b448e 100644
--- a/shimatta_kenkyusho/parts/models.py
+++ b/shimatta_kenkyusho/parts/models.py
@@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.dispatch import receiver
from django.core.validators import MinValueValidator, MaxValueValidator
+from django.template import engines
import os
import uuid
from shimatta_modules.EngineeringNumberConverter import EngineeringNumberConverter as NumConv
@@ -29,6 +30,7 @@ class ComponentParameterType(models.Model):
parameter_name = models.CharField(max_length=50, unique=True)
parameter_description = models.TextField(null=False, blank=True)
unit = models.CharField(max_length=10, null=False, blank=True)
+ interfix = models.CharField(max_length=10, null=False, blank=True, help_text="char to be used as decimal point in dynamic description eg. 2R2")
parameter_type = models.CharField(max_length=1, choices=TYPE_CHOICES, default='N')
def __str__(self):
@@ -49,6 +51,9 @@ class ComponentType(models.Model):
class_name = models.CharField(max_length=50, unique=True)
passive = models.BooleanField()
possible_parameter = models.ManyToManyField(ComponentParameterType, blank=True)
+ description_template = models.TextField(blank=True,
+ help_text="Template to assemble the dynamic description. "
+ "Use template syntax, access the component with 'object', parameters with 'param_*'.")
def __str__(self):
return '[' + self.class_name + ']'
@@ -65,6 +70,7 @@ class Storage(models.Model):
verbose_name = models.CharField(max_length=100, null=True, blank=True)
parent_storage = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
responsible = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, blank=True, null=True)
+ expand_sub_storage_stocks = models.BooleanField(default=False)
# allow storages to be templates which can be selected when adding new storages
is_template = models.BooleanField(default=False)
@@ -74,25 +80,40 @@ class Storage(models.Model):
null=True,
related_name='template_of')
- def get_path_components(self):
+ # caching variable for subtrees
+ storage_list = None
+
+ def get_path_components(self, top_level=None):
chain = []
iterator = self
chain.append(self)
while iterator.parent_storage is not None:
+
+ if top_level and iterator.parent_storage == top_level:
+ break
+
chain.append(iterator.parent_storage)
iterator = iterator.parent_storage
return chain
- def get_full_path(self):
+ def get_full_path(self, top_level=None):
output = ''
- chain = self.get_path_components()
+ chain = self.get_path_components(top_level)
for i in range(len(chain) - 1, -1, -1):
output = output + '/' + chain[i].name
return output
+ @property
+ def full_path_verbose(self):
+ full_path = f'{self.get_full_path()} ({self.id})'
+
+ if self.verbose_name:
+ full_path += f' ({self.verbose_name})'
+ return full_path
+
def get_qr_code(self):
qrdata = '[stor_uuid]' + str(self.id)
return qrdata
@@ -106,6 +127,19 @@ class Storage(models.Model):
else:
return self.storage_set.all()
+ @classmethod
+ def get_substorage_list(cls, sub_storages):
+ sub_sub_storages = cls.objects.filter(parent_storage__in=sub_storages)
+
+ final_sub_storages = sub_storages | sub_sub_storages
+ if sub_sub_storages:
+ final_sub_storages |= cls.get_substorage_list(sub_sub_storages)
+
+ return final_sub_storages
+
+ def get_storage_list(self):
+ return Storage.objects.filter(id=self.id) | self.get_substorage_list(self.storage_set.all())
+
def validate_unique(self, exclude=None):
if Storage.objects.exclude(id=self.id).filter(name=self.name, parent_storage__isnull=True).exists():
if self.parent_storage is None:
@@ -116,12 +150,18 @@ class Storage(models.Model):
raise
def get_total_stock_amount(self):
- stocks = Stock.objects.filter(storage=self)
+ stocks = Stock.objects.filter(storage__in=self.storage_list or self.get_storage_list())
sum = stocks.aggregate(Sum('amount'))['amount__sum']
if sum is None:
sum = 0
return sum
+ def get_total_stock_count(self):
+ return Stock.objects.filter(storage__in=self.storage_list or self.get_storage_list()).count()
+
+ def get_total_substorage_amount(self):
+ return len(self.storage_list or self.get_storage_list()) - 1 # -1 as thhe storage list counts the parent storage as well
+
@classmethod
def from_path(cls, path, root_storage=None):
'''
@@ -237,6 +277,22 @@ class Component(models.Model):
if sum is None:
sum = 0
return sum
+
+ @property
+ def dynamic_description(self):
+
+ if not self.component_type or not self.component_type.description_template:
+ return ''
+
+ django_engine = engines["django"]
+ template = django_engine.from_string(self.component_type.description_template)
+
+ parameters = list(ComponentParameter.objects.filter(component=self))
+ parameters += list(PackageParameter.objects.filter(package=self.package))
+
+ context = {f'param_{param.parameter_type.parameter_name}': param for param in parameters}
+ context.update({'object': self})
+ return template.render(context)
class AbstractParameter(models.Model):
class Meta:
@@ -271,6 +327,25 @@ class AbstractParameter(models.Model):
elif my_type == 'F':
return self.text_value
+ def resolved_value_as_short_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))
+ result = f'{round(num, 3):g}'
+ interpostfix = (prefix if prefix else self.parameter_type.interfix or '.')
+ if '.' in result:
+ result = result.replace('.', interpostfix)
+ else:
+ result = result + interpostfix
+ return result
+ elif my_type == 'N':
+ # Standard float number
+ return f'{round(self.value, 3):g}{self.parameter_type.unit}'
+ else:
+ return self.resolved_value_as_string()
+
class ComponentParameter(AbstractParameter):
class Meta:
unique_together = ('component', 'parameter_type')
@@ -308,7 +383,7 @@ class Stock(models.Model):
return True
def get_qr_code(self):
- qr_data = '[stock]'+str(self.id)
+ qr_data = '[stck_uuid]'+str(self.id)
return qr_data
def __str__(self):
@@ -407,5 +482,6 @@ def auto_apply_template_structure(sender, instance, created, **kwargs):
Storage.objects.create(name=sub_storage.name,
parent_storage=instance,
responsible=instance.responsible,
+ expand_sub_storage_stocks=instance.expand_sub_storage_stocks,
is_template=False,
template=sub_storage)
diff --git a/shimatta_kenkyusho/parts/qr_parser.py b/shimatta_kenkyusho/parts/qr_parser.py
index 4e5020d..9e3854a 100644
--- a/shimatta_kenkyusho/parts/qr_parser.py
+++ b/shimatta_kenkyusho/parts/qr_parser.py
@@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.urls import reverse as url_reverse
import re
-from .models import Storage, Component
+from .models import Storage, Component, Stock
class QrCode:
prefix = ''
@@ -19,6 +19,7 @@ class QrCodeValidator:
qr_patterns = {
'stor_uuid': QrCode('stor_uuid', 'parts-stocks-detail', Storage),
'comp_uuid': QrCode('comp_uuid', 'parts-components-detail', Component),
+ 'stck_uuid': QrCode('stck_uuid', 'parts-stock-detail', Stock),
}
def __init__(self):
@@ -32,16 +33,13 @@ class QrCodeValidator:
qr_type = matches.group('prefix')
qr_uuid = matches.group('uuid')
- url_name = self.qr_patterns[qr_type].detail_view
- model = None
try:
+ url_name = self.qr_patterns[qr_type].detail_view
model = self.qr_patterns[qr_type].model
- except:
- model = None
- if model is None:
- raise ValidationError('QR Pattern not registered')
- return (model,qr_uuid, url_name)
+ except KeyError as ex:
+ raise ValidationError('QR Pattern not registered') from ex
+ return (model, qr_uuid, url_name)
def get_redirect_url(self, data):
model, uuid, url_name = self._get_model_from_qr(data)
diff --git a/shimatta_kenkyusho/parts/templatetags/__init__.py b/shimatta_kenkyusho/parts/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/shimatta_kenkyusho/parts/templatetags/storage_tags.py b/shimatta_kenkyusho/parts/templatetags/storage_tags.py
new file mode 100644
index 0000000..85ce3b2
--- /dev/null
+++ b/shimatta_kenkyusho/parts/templatetags/storage_tags.py
@@ -0,0 +1,9 @@
+import datetime
+from django import template
+
+register = template.Library()
+
+
+@register.filter(name="get_relative_storage_path")
+def get_relative_storage_path(storage, top_level):
+ return f'.{storage.get_full_path(top_level)}'
diff --git a/shimatta_kenkyusho/parts/views/storage_views.py b/shimatta_kenkyusho/parts/views/storage_views.py
index 1e1e402..e8b0614 100644
--- a/shimatta_kenkyusho/parts/views/storage_views.py
+++ b/shimatta_kenkyusho/parts/views/storage_views.py
@@ -74,7 +74,13 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
return crumbs
def search_stock_queryset(self, search):
- stocks_in_storage = Stock.objects.filter(storage=self.object).order_by(Lower('component__name'))
+
+ if self.object.expand_sub_storage_stocks:
+ stocks_in_storage = Stock.objects.filter(storage__in=self.object.get_storage_list())
+ else:
+ stocks_in_storage = Stock.objects.filter(storage=self.object)
+
+ stocks_in_storage = stocks_in_storage.order_by(Lower('component__name'))
if search is None or search == '':
return stocks_in_storage
@@ -115,14 +121,16 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
context['storages'] = storage_paginator.get_page(storage_page)
stocks = stock_paginator.get_page(componente_stock_page)
context['stocks'] = stocks
+ context['stocks_with_forms'] = [{'object': s, 'relocate_form': RelocateStockForm(instance=s, prefix=str(s.id))} for s in stocks]
context['stock_search'] = stock_search_input
add_storage_form = AddSubStorageForm()
add_storage_form.fields['responsible'].initial = self.request.user.id
context['add_storage_form'] = add_storage_form
- change_storage_form = ChangeStorageForm()
+ change_storage_form = ChangeStorageForm(prefix='change_storage')
change_storage_form.fields['storage_name'].initial = self.object.name
change_storage_form.fields['verbose_name'].initial = self.object.verbose_name
change_storage_form.fields['responsible'].initial = self.object.responsible.id
+ change_storage_form.fields['expand_sub_storage_stocks'].initial = self.object.expand_sub_storage_stocks
change_storage_form.fields['is_template'].initial = self.object.is_template
context['change_storage_form'] = change_storage_form
context['delete_storage_error'] = None
@@ -139,6 +147,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
verbose_name=f.cleaned_data.get('verbose_name'),
parent_storage=self.object,
responsible=f.cleaned_data['responsible'],
+ expand_sub_storage_stocks=f.cleaned_data['expand_sub_storage_stocks'],
is_template=f.cleaned_data['is_template'],
template=f.cleaned_data.get('template'))
except ValidationError as v_err:
@@ -148,12 +157,13 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
return self.render_to_response(context)
def handle_change_storage_post(self, request, **kwargs):
- f = ChangeStorageForm(data=request.POST)
+ f = ChangeStorageForm(data=request.POST, prefix='change_storage')
if f.is_valid():
try:
self.object.name = f.cleaned_data['storage_name']
self.object.verbose_name = f.cleaned_data.get('verbose_name')
self.object.responsible = f.cleaned_data['responsible']
+ self.object.expand_sub_storage_stocks = f.cleaned_data['expand_sub_storage_stocks']
self.object.is_template = f.cleaned_data['is_template']
self.object.save()
except ValidationError as v_err:
@@ -205,6 +215,27 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
+ def handle_update_lot(self, request, **kwargs):
+ edit_form = EditLotForm(data=request.POST)
+ if edit_form.is_valid():
+ edit_form.save()
+ else:
+ pass # Todo: Handle error
+
+ context = self.get_context_data(**kwargs)
+ return self.render_to_response(context)
+
+ def handle_relocate_stock(self, request, **kwargs):
+ instance = Stock.objects.get(id=request.POST['prefix'])
+ edit_form = RelocateStockForm(instance=instance, data=request.POST, prefix=request.POST['prefix'])
+ if edit_form.is_valid():
+ edit_form.save()
+ else:
+ pass # Todo: Handle error
+
+ context = self.get_context_data(**kwargs)
+ return self.render_to_response(context)
+
def handle_amount_change_post(self, request, increase, **kwargs):
edit_form = EditStockAmountForm(data=request.POST)
if edit_form.is_valid():
@@ -244,6 +275,10 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
return self.handle_del_stock_post(request, **kwargs)
elif 'submit-edit-watermark' in request.POST:
return self.handle_update_watermark(request, **kwargs)
+ elif 'submit-edit-lot' in request.POST:
+ return self.handle_update_lot(request, **kwargs)
+ elif 'submit-relocate-stock' in request.POST:
+ return self.handle_relocate_stock(request, **kwargs)
elif 'submit-amount-reduce' in request.POST:
return self.handle_amount_change_post(request, False, **kwargs)
elif 'submit-amount-increase' in request.POST:
diff --git a/shimatta_kenkyusho/templates/base.html b/shimatta_kenkyusho/templates/base.html
index ac6acad..ef28046 100644
--- a/shimatta_kenkyusho/templates/base.html
+++ b/shimatta_kenkyusho/templates/base.html
@@ -83,6 +83,13 @@
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
+
+