From 03f7bb86f576fee047df40556b9d491c5b2b3d13 Mon Sep 17 00:00:00 2001 From: agp8x Date: Thu, 22 Feb 2018 17:34:28 +0100 Subject: [PATCH] =?UTF-8?q?brandedpart=20can=20now=20have=20multiple=20par?= =?UTF-8?q?ts=20(spelling=20variants,=20=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- partdoc/parts/admin.py | 6 +- partdoc/parts/models.py | 85 ++++++++++++++++++-------- partdoc/parts/templates/parts/add.html | 4 +- partdoc/parts/views.py | 51 ++++++++++------ 4 files changed, 98 insertions(+), 48 deletions(-) diff --git a/partdoc/parts/admin.py b/partdoc/parts/admin.py index 67c6220..0b7d4e4 100644 --- a/partdoc/parts/admin.py +++ b/partdoc/parts/admin.py @@ -10,11 +10,11 @@ class BrandedPartInline(admin.StackedInline): model = BrandedPart extra = 1 search_fields = ["number", "name"] - autocomplete_fields = ["part"] + autocomplete_fields = ["parts"] class PartAdmin(admin.ModelAdmin): - inlines = [BrandedPartInline] + #inlines = [BrandedPartInline] search_fields = ["name"] @@ -48,7 +48,7 @@ class UsageInline(admin.StackedInline): class BrandedPartAdmin(admin.ModelAdmin): inlines = [UsageInline] search_fields = ["number"] - autocomplete_fields = ["part"] + autocomplete_fields = ["parts"] admin.site.register(Usage, UsageAdmin) diff --git a/partdoc/parts/models.py b/partdoc/parts/models.py index e700b2c..1c8370d 100644 --- a/partdoc/parts/models.py +++ b/partdoc/parts/models.py @@ -1,16 +1,17 @@ -from django.db import models +from django.db import models, transaction -class PartModel(models. Model): + +class PartModel(models.Model): creation = models.DateTimeField(auto_now_add=True) modification = models.DateTimeField(auto_now=True) - + class Meta: abstract = True class Brand(PartModel): name = models.CharField(max_length=256, db_index=True) - + def __str__(self): return self.name @@ -18,7 +19,7 @@ class Brand(PartModel): class Product(PartModel): name = models.CharField(max_length=256, db_index=True) brand = models.ForeignKey(Brand, on_delete=models.CASCADE) - + def __str__(self): return self.name + " (" + self.brand.name + ")" @@ -26,18 +27,20 @@ class Product(PartModel): class Sketch(PartModel): class Meta: verbose_name_plural = "sketches" + name = models.CharField(max_length=256) brand = models.ForeignKey(Brand, blank=True, null=True, on_delete=models.CASCADE, db_index=True) product = models.ForeignKey(Product, blank=True, null=True, on_delete=models.CASCADE) image = models.FileField(upload_to="sketches/", blank=True, null=True) - + def __str__(self): return "Sketch: " + self.name + " (" + str(self.brand) + ")" - + def get_product(self): if self.product: return [self.product] + list(self.usage_set.values('productusage__product').distinct()) return self.usage_set.values('productusage__product').distinct() + def get_brand(self): if self.brand: return [self.brand] + list(self.usage_set.values('productusage__product__brand').distinct()) @@ -50,31 +53,60 @@ class Version(PartModel): replaced_by = models.ForeignKey('self', blank=True, null=True, related_name='replaces', on_delete=models.SET_NULL) start = models.DateField(blank=True, null=True) end = models.DateField(blank=True, null=True) - + additional_sketch = models.ManyToManyField(Sketch, blank=True, related_name='added') unused_sketch = models.ManyToManyField(Sketch, blank=True, related_name='replaced') - + def __str__(self): return "Version: " + self.name + " (" + str(self.product) + ")" class Part(PartModel): name = models.CharField(max_length=256, db_index=True) - + def __str__(self): return self.name + class BrandedPart(PartModel): - part = models.ForeignKey(Part, null=True, on_delete=models.SET_NULL, db_index=True) + parts = models.ManyToManyField(Part, db_index=True, related_name="branded_parts") brand = models.ForeignKey(Brand, on_delete=models.CASCADE) number = models.CharField(max_length=64, unique=True, blank=True, null=True, db_index=True) - - def get_name(self): - return self.part.name if self.part else "--None--" - + + @transaction.atomic + def _merge(self, other): + """add other part's usages and delete it""" + for p in other.parts.all(): + if not p in self.parts.all(): + self.parts.add(p) + self.save() + for u in other.usage_set.all(): + u.part = self + u.save() + other.delete() + + @staticmethod + @transaction.atomic + def merge(number1, number2): + self = BrandedPart.objects.filter(number=number1) + other = BrandedPart.objects.filter(number=number2) + if not (self and other): + raise ValueError("invalid arguments! {} {}".format(self, other)) + self[0]._merge(other[0]) + + def get_part_name(self): + return self.parts.first().name if len(self.parts) else "" + + def get_name(self, full=False, first=False): + if full: + return ", ".join([i.name for i in self.parts.all()]) + if first: + return self.parts.first().name if self.parts else "--(parts)--" + return "{} ({})".format(self.parts.first().name, self.parts.count()) if self.parts else "--(parts)--" + def __str__(self): - na = self.part.name if self.part else "--(part)--" - return str(self.number) +": "+ na + " @ " + self.brand.name + na = self.get_name() if self.parts else "--(part)--" + return str(self.number) + ": " + na + " @ " + self.brand.name class Usage(PartModel): @@ -82,28 +114,29 @@ class Usage(PartModel): sketch = models.ForeignKey(Sketch, on_delete=models.CASCADE, db_index=True) sketch_number = models.CharField(max_length=32) exact_sketch = models.BooleanField(default=True) - #quantity = models.ManyToManyField(ProductUsage) - + + # quantity = models.ManyToManyField(ProductUsage) + def __str__(self): no = self.part.number if self.part else "--(part.number)--" - na = self.part.part.name if self.part.part else "--(part.part.name)--" - return self.sketch_number +"; " + str(no) +"; "+ na + "; " + str(self.sketch) + na = self.part.get_name() if self.part else "--(part.part.name)--" + return self.sketch_number + "; " + str(no) + "; " + na + "; " + str(self.sketch) class ProductUsage(PartModel): - usage = models.ForeignKey(Usage, null=True, on_delete=models.SET_NULL, db_index=True) + usage = models.ForeignKey(Usage, on_delete=models.CASCADE, db_index=True) product = models.ForeignKey(Product, on_delete=models.CASCADE, db_index=True) quantity = models.IntegerField(null=True) on_demand = models.BooleanField(default=False) obsolete = models.BooleanField(default=False) used_until = models.ForeignKey(Version, null=True, blank=True, related_name='introduces', on_delete=models.SET_NULL) used_since = models.ForeignKey(Version, null=True, blank=True, related_name='dissmisses', on_delete=models.SET_NULL) - #replaced_by = models.ForeignKey('self', null=True, blank=True, related_name='replaces', on_delete=models.SET_NULL) + # replaced_by = models.ForeignKey('self', null=True, blank=True, related_name='replaces', on_delete=models.SET_NULL) replaced_by = models.CharField(max_length=512, null=True, blank=True) - #alternative = models.ForeignKey('self', null=True, blank=True, related_name='alternatives', on_delete=models.SET_NULL) + # alternative = models.ForeignKey('self', null=True, blank=True, related_name='alternatives', on_delete=models.SET_NULL) note = models.CharField(max_length=1024, blank=True) internal_note = models.CharField(max_length=2, blank=True) - + def __str__(self): no = self.usage.part.number if self.usage else "--(part.number)--" - return "ProductUsage: " + self.product.name + " @ " + str(self.quantity) + "; "+ str(no) + return "ProductUsage: " + self.product.name + " @ " + str(self.quantity) + "; " + str(no) diff --git a/partdoc/parts/templates/parts/add.html b/partdoc/parts/templates/parts/add.html index e2feecd..834b553 100644 --- a/partdoc/parts/templates/parts/add.html +++ b/partdoc/parts/templates/parts/add.html @@ -15,6 +15,7 @@ {% csrf_token %}

+ @@ -34,7 +35,7 @@ - + @@ -47,7 +48,6 @@ {% endif %}
Bild-Nr.{{ usage.sketch_number }} {{ usage.part.number }} {{ productusage.internal_note }}{{ usage.part.part.name }}{{ usage.part.get_name }} {{ productusage.quantity }} {{ productusage.used_until }} {{ productusage.used_since }}
- diff --git a/partdoc/parts/views.py b/partdoc/parts/views.py index f518dc4..7d561b3 100644 --- a/partdoc/parts/views.py +++ b/partdoc/parts/views.py @@ -6,24 +6,28 @@ from django.db import transaction from .models import * -ADD_SKETCH_REQUIRES = ['sketch_name','sketch_number',''] +ADD_SKETCH_REQUIRES = ['sketch_name', 'sketch_number', ''] + def index(request): product_list = Product.objects.all() context = {'product_list': product_list} return render(request, 'parts/products.html', context) + def product(request, product_id): product = Product.objects.get(id=product_id) sketches = Sketch.objects.filter(usage__productusage__product=product) context = {'product': product, 'sketches': sketches} return render(request, 'parts/product.html', context) + def sketch(request, sketch_id): sketch = Sketch.objects.get(id=sketch_id) context = {'sketch': sketch} return render(request, 'parts/sketch.html', context) + def part_search(request): if "term" in request.GET: term = request.GET.get("term", '') @@ -32,16 +36,18 @@ def part_search(request): json.dumps( [ { - 'id':part.id, - 'label':part.number + " - " + part.get_name(), - 'value':part.number, - 'name':part.get_name() + 'id': part.id, + 'label': part.number + " - " + part.get_name(full=True), + 'value': part.number, + 'name': part.get_name(first=True) } - for part in parts], + for part in parts + ], indent=1) - ) + ) return HttpResponse("404") + @transaction.atomic def sketch_add(request, product_id, sketch_id=None): product = get_object_or_404(Product, pk=product_id) @@ -64,20 +70,30 @@ def sketch_add(request, product_id, sketch_id=None): usage.exact_sketch = is_exact_sketchnumber(usage.sketch_number) part_number = request.POST.getlist('partnumber')[i] - if len(part_number)<1: + if len(part_number) < 1: continue branded_part, created = BrandedPart.objects.get_or_create(brand=product.brand, number=part_number) + _part_name = request.POST.getlist('name')[i].strip() if created: try: - part, _ = Part.objects.get_or_create(name=request.POST.getlist('name')[i]) + part, _ = Part.objects.get_or_create(name=_part_name) except Part.MultipleObjectsReturned: - part = Part.objects.filter(name=request.POST.getlist('name')[i]).first() - branded_part.part = part + part = Part.objects.filter(name=_part_name).first() + branded_part.parts.add(part) branded_part.brand = product.brand branded_part.save() + else: + if len(_part_name): + part = Part.objects.filter(name=_part_name) + if not (part and part.first() in branded_part.parts.all()): + if part: + branded_part.parts.add(part.first()) + else: + part, _ = Part.objects.get_or_create(name=_part_name) + branded_part.parts.add(part) usage.part = branded_part usage.save() - + prod = ProductUsage() prod.usage = usage prod.product = product @@ -95,14 +111,14 @@ def sketch_add(request, product_id, sketch_id=None): if len(note): prod.note = note if len(replaced): - #replacement, _ = ProductUsage.objects.get_or_create(product=product, usage__part__number=replaced) - #prod.replaced_by = replacement - prod.note+= "{REP:"+str(replaced)+"}" #TODO + # replacement, _ = ProductUsage.objects.get_or_create(product=product, usage__part__number=replaced) + # prod.replaced_by = replacement + prod.note += "{REP:" + str(replaced) + "}" # TODO internal = request.POST.getlist('internal')[i] if len(internal): prod.internal = internal prod.save() - + return HttpResponseRedirect(reverse('parts:sketch', args=(sketch.id,))) @@ -112,6 +128,7 @@ def get_quantity(qnty_str): qnty = int(qnty_str) return qnty + def is_exact_sketchnumber(sketch_number): approximate = ("ähn", "vgl", "~", "zu") - return not any([i in sketch_number for i in approximate]) \ No newline at end of file + return not any([i in sketch_number for i in approximate])