From 171b6b83f4c9dc7eedc0a883b014ac346f804679 Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 25 Jan 2025 14:50:48 +0100 Subject: [PATCH 01/18] added option to expand sub-storage stocks to display all components from sub_storages this comes in handy for assortment boxes using sub storages for each individual compartment which usually only holds a single stock --- shimatta_kenkyusho/parts/forms.py | 3 ++- .../0013_storage_expand_sub_storage_stocks.py | 18 ++++++++++++++++++ shimatta_kenkyusho/parts/models.py | 9 +++++++++ .../parts/views/storage_views.py | 10 +++++++++- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 shimatta_kenkyusho/parts/migrations/0013_storage_expand_sub_storage_stocks.py diff --git a/shimatta_kenkyusho/parts/forms.py b/shimatta_kenkyusho/parts/forms.py index 44752e6..3c4cd5f 100644 --- a/shimatta_kenkyusho/parts/forms.py +++ b/shimatta_kenkyusho/parts/forms.py @@ -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', diff --git a/shimatta_kenkyusho/parts/migrations/0013_storage_expand_sub_storage_stocks.py b/shimatta_kenkyusho/parts/migrations/0013_storage_expand_sub_storage_stocks.py new file mode 100644 index 0000000..8f7edea --- /dev/null +++ b/shimatta_kenkyusho/parts/migrations/0013_storage_expand_sub_storage_stocks.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.3 on 2025-01-25 13:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parts', '0012_storage_verbose_name'), + ] + + operations = [ + migrations.AddField( + model_name='storage', + name='expand_sub_storage_stocks', + field=models.BooleanField(default=False), + ), + ] diff --git a/shimatta_kenkyusho/parts/models.py b/shimatta_kenkyusho/parts/models.py index f5d4ec4..2f19cf4 100644 --- a/shimatta_kenkyusho/parts/models.py +++ b/shimatta_kenkyusho/parts/models.py @@ -65,6 +65,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) @@ -106,6 +107,13 @@ class Storage(models.Model): else: return self.storage_set.all() + def get_tree(self): + self.sub_storages = list(self.storage_set.all()) + for storage in self.sub_storages: + self.sub_storages += storage.get_tree() + + return self.sub_storages + 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: @@ -407,5 +415,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/views/storage_views.py b/shimatta_kenkyusho/parts/views/storage_views.py index 1e1e402..283ce92 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_tree()) + 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 @@ -139,6 +145,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: @@ -154,6 +161,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): 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: From f6a878460d5d61b37c4094d360c22e2dbc1797f3 Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 25 Jan 2025 14:53:22 +0100 Subject: [PATCH 02/18] changed the prefix of stock uuid qr codes --- shimatta_kenkyusho/parts/models.py | 2 +- shimatta_kenkyusho/parts/qr_parser.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/shimatta_kenkyusho/parts/models.py b/shimatta_kenkyusho/parts/models.py index 2f19cf4..4355cdf 100644 --- a/shimatta_kenkyusho/parts/models.py +++ b/shimatta_kenkyusho/parts/models.py @@ -316,7 +316,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): 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) From 39b64aeb716b41eb915f6969e761658c06d13d3f Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 25 Jan 2025 15:01:50 +0100 Subject: [PATCH 03/18] fixed migrations --- ...ge_stocks.py => 0014_storage_expand_sub_storage_stocks.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename shimatta_kenkyusho/parts/migrations/{0013_storage_expand_sub_storage_stocks.py => 0014_storage_expand_sub_storage_stocks.py} (76%) diff --git a/shimatta_kenkyusho/parts/migrations/0013_storage_expand_sub_storage_stocks.py b/shimatta_kenkyusho/parts/migrations/0014_storage_expand_sub_storage_stocks.py similarity index 76% rename from shimatta_kenkyusho/parts/migrations/0013_storage_expand_sub_storage_stocks.py rename to shimatta_kenkyusho/parts/migrations/0014_storage_expand_sub_storage_stocks.py index 8f7edea..9dc3c30 100644 --- a/shimatta_kenkyusho/parts/migrations/0013_storage_expand_sub_storage_stocks.py +++ b/shimatta_kenkyusho/parts/migrations/0014_storage_expand_sub_storage_stocks.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.3 on 2025-01-25 13:20 +# Generated by Django 5.1.3 on 2025-01-25 14:01 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('parts', '0012_storage_verbose_name'), + ('parts', '0013_packageparameter'), ] operations = [ From 3aa4225acb99e6109549ba0d08bfe55f78aca343 Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 25 Jan 2025 15:24:59 +0100 Subject: [PATCH 04/18] added form to change stock lot --- shimatta_kenkyusho/parts/forms.py | 54 +++++++++---------- .../parts/views/storage_views.py | 12 +++++ .../parts/modals/update-stock-modal.html | 11 +++- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/shimatta_kenkyusho/parts/forms.py b/shimatta_kenkyusho/parts/forms.py index 3c4cd5f..56fa7f5 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) @@ -113,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") @@ -134,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'] @@ -145,32 +147,26 @@ class EditWatermarkForm(forms.Form): stock.watermark = watermark stock.save() -class EditStockAmountForm(forms.Form): +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 EditStockAmountForm(EditStockBaseForm): stock_uuid = forms.UUIDField() 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) @@ -195,7 +191,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') @@ -254,7 +250,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: @@ -323,7 +319,7 @@ class ComponentParameterCreateForm(forms.Form): if not ptype: raise ValidationError('No valid parameter type selected') - + if not value: raise ValidationError('No valid parameter value') @@ -335,7 +331,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': @@ -364,6 +360,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/views/storage_views.py b/shimatta_kenkyusho/parts/views/storage_views.py index 283ce92..7ee8ed1 100644 --- a/shimatta_kenkyusho/parts/views/storage_views.py +++ b/shimatta_kenkyusho/parts/views/storage_views.py @@ -213,6 +213,16 @@ 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_amount_change_post(self, request, increase, **kwargs): edit_form = EditStockAmountForm(data=request.POST) if edit_form.is_valid(): @@ -252,6 +262,8 @@ 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-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/parts/modals/update-stock-modal.html b/shimatta_kenkyusho/templates/parts/modals/update-stock-modal.html index 6729c98..c15c067 100644 --- a/shimatta_kenkyusho/templates/parts/modals/update-stock-modal.html +++ b/shimatta_kenkyusho/templates/parts/modals/update-stock-modal.html @@ -39,7 +39,7 @@ needs following context:
{% csrf_token %} -
+
= 0 %}checked{%endif%}>
@@ -47,7 +47,16 @@ needs following context:
+
+ {% csrf_token %} + +
+ + +
+
+ \ No newline at end of file From 6eaef98c86f6dbaa3feb83e40f5f25069822d7e1 Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 25 Jan 2025 15:25:26 +0100 Subject: [PATCH 05/18] showing stock uuid in stocks detail list --- shimatta_kenkyusho/templates/parts/stocks-detail.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shimatta_kenkyusho/templates/parts/stocks-detail.html b/shimatta_kenkyusho/templates/parts/stocks-detail.html index e80609f..b051097 100644 --- a/shimatta_kenkyusho/templates/parts/stocks-detail.html +++ b/shimatta_kenkyusho/templates/parts/stocks-detail.html @@ -36,7 +36,7 @@ {% for storage in storages %}
  • -
    +
    {{storage.name}}{% if storage.verbose_name %} ({{storage.verbose_name}}){% endif %}
    Responsible: {{ storage.responsible }}
    @@ -84,6 +84,9 @@
    Lot: {{stock.lot}}
    {% endif %}
    +
    + {% qr_from_text stock.get_qr_code size="6" image_format="svg" %} +
    Amount: {{stock.amount}} {% if stock.watermark >= 0 %} From e4e7456a5d65c74afcbd8c1b040c3833624649be Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 25 Jan 2025 15:40:08 +0100 Subject: [PATCH 06/18] added link to template in stocks detail view --- .../templates/parts/stocks-detail.html | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/shimatta_kenkyusho/templates/parts/stocks-detail.html b/shimatta_kenkyusho/templates/parts/stocks-detail.html index b051097..30fd93b 100644 --- a/shimatta_kenkyusho/templates/parts/stocks-detail.html +++ b/shimatta_kenkyusho/templates/parts/stocks-detail.html @@ -21,13 +21,28 @@ {% qr_from_text object.get_qr_code size="m" image_format="svg" %}
    -

    {{storage.name}}{% if storage.verbose_name %} ({{storage.verbose_name}}){% endif %}

    +

    {{storage.name}} + {% if storage.verbose_name %} + + ({{storage.verbose_name}}) + + {% endif %} + {% if storage.is_template %} + + (template) + + {% endif %} +

    {% if object.parent_storage %} -

    Sub-Storages Parent Storage {% else %} +

    Sub-Storages Parent Storage + {% else %}

    Sub-Storages Stock Overview {% endif %} + {% if storage.template %} + Template + {% endif %} From 3ec11cf092cfaba70bbe3b8194bf35ebae275e25 Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 25 Jan 2025 16:23:55 +0100 Subject: [PATCH 07/18] typos --- shimatta_kenkyusho/parts/forms.py | 1 - shimatta_kenkyusho/templates/parts/stocks-detail.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/shimatta_kenkyusho/parts/forms.py b/shimatta_kenkyusho/parts/forms.py index 56fa7f5..d366146 100644 --- a/shimatta_kenkyusho/parts/forms.py +++ b/shimatta_kenkyusho/parts/forms.py @@ -158,7 +158,6 @@ class EditLotForm(EditStockBaseForm): stock.save() class EditStockAmountForm(EditStockBaseForm): - stock_uuid = forms.UUIDField() amount = forms.IntegerField(min_value=0) def save(self, increase: bool): diff --git a/shimatta_kenkyusho/templates/parts/stocks-detail.html b/shimatta_kenkyusho/templates/parts/stocks-detail.html index 30fd93b..c0938c7 100644 --- a/shimatta_kenkyusho/templates/parts/stocks-detail.html +++ b/shimatta_kenkyusho/templates/parts/stocks-detail.html @@ -133,7 +133,7 @@ {% with add_storage_form as form %} {% include 'parts/modals/add-substorage-modal.html' %} {% endwith %} - + {% with change_storage_form as form %} {% include 'parts/modals/change-storage-modal.html' %} {% endwith %} From aefcc472eaeff5761960c14fd0a198dc316846ce Mon Sep 17 00:00:00 2001 From: stefan Date: Mon, 27 Jan 2025 22:55:05 +0100 Subject: [PATCH 08/18] added feature to relocate stocks to a different storage ...had to deal with form prefixes at some places ...storage search does not work as expected yet :( --- shimatta_kenkyusho/api/serializers.py | 2 +- shimatta_kenkyusho/parts/forms.py | 11 +++++++++++ shimatta_kenkyusho/parts/models.py | 12 ++++++++++-- .../parts/views/storage_views.py | 19 +++++++++++++++++-- .../parts/modals/update-stock-modal.html | 6 ++++++ .../templates/parts/stocks-detail.html | 4 ++-- 6 files changed, 47 insertions(+), 7 deletions(-) diff --git a/shimatta_kenkyusho/api/serializers.py b/shimatta_kenkyusho/api/serializers.py index 183e134..93f785b 100644 --- a/shimatta_kenkyusho/api/serializers.py +++ b/shimatta_kenkyusho/api/serializers.py @@ -80,7 +80,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/parts/forms.py b/shimatta_kenkyusho/parts/forms.py index d366146..8eaaa2f 100644 --- a/shimatta_kenkyusho/parts/forms.py +++ b/shimatta_kenkyusho/parts/forms.py @@ -157,6 +157,17 @@ class EditLotForm(EditStockBaseForm): 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) diff --git a/shimatta_kenkyusho/parts/models.py b/shimatta_kenkyusho/parts/models.py index 4355cdf..bb1280e 100644 --- a/shimatta_kenkyusho/parts/models.py +++ b/shimatta_kenkyusho/parts/models.py @@ -94,6 +94,14 @@ class Storage(models.Model): 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 @@ -108,8 +116,8 @@ class Storage(models.Model): return self.storage_set.all() def get_tree(self): - self.sub_storages = list(self.storage_set.all()) - for storage in self.sub_storages: + self.sub_storages = [self] + for storage in self.storage_set.all(): self.sub_storages += storage.get_tree() return self.sub_storages diff --git a/shimatta_kenkyusho/parts/views/storage_views.py b/shimatta_kenkyusho/parts/views/storage_views.py index 7ee8ed1..dc17ad4 100644 --- a/shimatta_kenkyusho/parts/views/storage_views.py +++ b/shimatta_kenkyusho/parts/views/storage_views.py @@ -121,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 @@ -155,7 +157,7 @@ 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'] @@ -223,6 +225,17 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): 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(): @@ -264,6 +277,8 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): 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/parts/modals/update-stock-modal.html b/shimatta_kenkyusho/templates/parts/modals/update-stock-modal.html index c15c067..50c6c0e 100644 --- a/shimatta_kenkyusho/templates/parts/modals/update-stock-modal.html +++ b/shimatta_kenkyusho/templates/parts/modals/update-stock-modal.html @@ -55,6 +55,12 @@ needs following context:

    +
    + {% csrf_token %} + + {{ relocate_form|crispy }} + +
    diff --git a/shimatta_kenkyusho/templates/parts/stocks-detail.html b/shimatta_kenkyusho/templates/parts/stocks-detail.html index c0938c7..832c720 100644 --- a/shimatta_kenkyusho/templates/parts/stocks-detail.html +++ b/shimatta_kenkyusho/templates/parts/stocks-detail.html @@ -126,8 +126,8 @@ {% include 'paginator.html' with paginator=stocks get_param='stock_page' aria_label='Stock Page Navigation' %} - {% for stock in stocks %} - {% include 'parts/modals/update-stock-modal.html' with stock=stock form=change_stock_form %} + {% for stock in stocks_with_forms %} + {% include 'parts/modals/update-stock-modal.html' with stock=stock.object form=change_stock_form relocate_form=stock.relocate_form %} {% endfor %} {% with add_storage_form as form %} From 6ae94e9ea49c17fac4dfd5fa50e8f306c0299caf Mon Sep 17 00:00:00 2001 From: stefan Date: Tue, 28 Jan 2025 22:38:17 +0100 Subject: [PATCH 09/18] made the stock view more ugly by adding badges showing the number of stored parts, lots and substorages --- shimatta_kenkyusho/parts/models.py | 27 ++++++++++++++----- .../parts/views/storage_views.py | 2 +- shimatta_kenkyusho/templates/base.html | 7 +++++ .../templates/parts/stocks-detail.html | 4 ++- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/shimatta_kenkyusho/parts/models.py b/shimatta_kenkyusho/parts/models.py index bb1280e..e1f9e8c 100644 --- a/shimatta_kenkyusho/parts/models.py +++ b/shimatta_kenkyusho/parts/models.py @@ -75,6 +75,9 @@ class Storage(models.Model): null=True, related_name='template_of') + # caching variable for subtrees + storage_list = None + def get_path_components(self): chain = [] iterator = self @@ -115,12 +118,18 @@ class Storage(models.Model): else: return self.storage_set.all() - def get_tree(self): - self.sub_storages = [self] - for storage in self.storage_set.all(): - self.sub_storages += storage.get_tree() + @classmethod + def get_substorage_list(cls, sub_storages): + sub_sub_storages = cls.objects.filter(parent_storage__in=sub_storages) - return self.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(): @@ -132,12 +141,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): ''' diff --git a/shimatta_kenkyusho/parts/views/storage_views.py b/shimatta_kenkyusho/parts/views/storage_views.py index dc17ad4..e8b0614 100644 --- a/shimatta_kenkyusho/parts/views/storage_views.py +++ b/shimatta_kenkyusho/parts/views/storage_views.py @@ -76,7 +76,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): def search_stock_queryset(self, search): if self.object.expand_sub_storage_stocks: - stocks_in_storage = Stock.objects.filter(storage__in=self.object.get_tree()) + stocks_in_storage = Stock.objects.filter(storage__in=self.object.get_storage_list()) else: stocks_in_storage = Stock.objects.filter(storage=self.object) 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)) + +