brandedpart can now have multiple parts (spelling variants, …)
parent
772afa0016
commit
03f7bb86f5
|
|
@ -10,11 +10,11 @@ class BrandedPartInline(admin.StackedInline):
|
||||||
model = BrandedPart
|
model = BrandedPart
|
||||||
extra = 1
|
extra = 1
|
||||||
search_fields = ["number", "name"]
|
search_fields = ["number", "name"]
|
||||||
autocomplete_fields = ["part"]
|
autocomplete_fields = ["parts"]
|
||||||
|
|
||||||
|
|
||||||
class PartAdmin(admin.ModelAdmin):
|
class PartAdmin(admin.ModelAdmin):
|
||||||
inlines = [BrandedPartInline]
|
#inlines = [BrandedPartInline]
|
||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ class UsageInline(admin.StackedInline):
|
||||||
class BrandedPartAdmin(admin.ModelAdmin):
|
class BrandedPartAdmin(admin.ModelAdmin):
|
||||||
inlines = [UsageInline]
|
inlines = [UsageInline]
|
||||||
search_fields = ["number"]
|
search_fields = ["number"]
|
||||||
autocomplete_fields = ["part"]
|
autocomplete_fields = ["parts"]
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Usage, UsageAdmin)
|
admin.site.register(Usage, UsageAdmin)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
creation = models.DateTimeField(auto_now_add=True)
|
||||||
modification = models.DateTimeField(auto_now=True)
|
modification = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class Brand(PartModel):
|
class Brand(PartModel):
|
||||||
name = models.CharField(max_length=256, db_index=True)
|
name = models.CharField(max_length=256, db_index=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ class Brand(PartModel):
|
||||||
class Product(PartModel):
|
class Product(PartModel):
|
||||||
name = models.CharField(max_length=256, db_index=True)
|
name = models.CharField(max_length=256, db_index=True)
|
||||||
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
|
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name + " (" + self.brand.name + ")"
|
return self.name + " (" + self.brand.name + ")"
|
||||||
|
|
||||||
|
|
@ -26,18 +27,20 @@ class Product(PartModel):
|
||||||
class Sketch(PartModel):
|
class Sketch(PartModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = "sketches"
|
verbose_name_plural = "sketches"
|
||||||
|
|
||||||
name = models.CharField(max_length=256)
|
name = models.CharField(max_length=256)
|
||||||
brand = models.ForeignKey(Brand, blank=True, null=True, on_delete=models.CASCADE, db_index=True)
|
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)
|
product = models.ForeignKey(Product, blank=True, null=True, on_delete=models.CASCADE)
|
||||||
image = models.FileField(upload_to="sketches/", blank=True, null=True)
|
image = models.FileField(upload_to="sketches/", blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Sketch: " + self.name + " (" + str(self.brand) + ")"
|
return "Sketch: " + self.name + " (" + str(self.brand) + ")"
|
||||||
|
|
||||||
def get_product(self):
|
def get_product(self):
|
||||||
if self.product:
|
if self.product:
|
||||||
return [self.product] + list(self.usage_set.values('productusage__product').distinct())
|
return [self.product] + list(self.usage_set.values('productusage__product').distinct())
|
||||||
return self.usage_set.values('productusage__product').distinct()
|
return self.usage_set.values('productusage__product').distinct()
|
||||||
|
|
||||||
def get_brand(self):
|
def get_brand(self):
|
||||||
if self.brand:
|
if self.brand:
|
||||||
return [self.brand] + list(self.usage_set.values('productusage__product__brand').distinct())
|
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)
|
replaced_by = models.ForeignKey('self', blank=True, null=True, related_name='replaces', on_delete=models.SET_NULL)
|
||||||
start = models.DateField(blank=True, null=True)
|
start = models.DateField(blank=True, null=True)
|
||||||
end = models.DateField(blank=True, null=True)
|
end = models.DateField(blank=True, null=True)
|
||||||
|
|
||||||
additional_sketch = models.ManyToManyField(Sketch, blank=True, related_name='added')
|
additional_sketch = models.ManyToManyField(Sketch, blank=True, related_name='added')
|
||||||
unused_sketch = models.ManyToManyField(Sketch, blank=True, related_name='replaced')
|
unused_sketch = models.ManyToManyField(Sketch, blank=True, related_name='replaced')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Version: " + self.name + " (" + str(self.product) + ")"
|
return "Version: " + self.name + " (" + str(self.product) + ")"
|
||||||
|
|
||||||
|
|
||||||
class Part(PartModel):
|
class Part(PartModel):
|
||||||
name = models.CharField(max_length=256, db_index=True)
|
name = models.CharField(max_length=256, db_index=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class BrandedPart(PartModel):
|
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)
|
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
|
||||||
number = models.CharField(max_length=64, unique=True, blank=True, null=True, db_index=True)
|
number = models.CharField(max_length=64, unique=True, blank=True, null=True, db_index=True)
|
||||||
|
|
||||||
def get_name(self):
|
@transaction.atomic
|
||||||
return self.part.name if self.part else "--None--"
|
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):
|
def __str__(self):
|
||||||
na = self.part.name if self.part else "--(part)--"
|
na = self.get_name() if self.parts else "--(part)--"
|
||||||
return str(self.number) +": "+ na + " @ " + self.brand.name
|
return str(self.number) + ": " + na + " @ " + self.brand.name
|
||||||
|
|
||||||
|
|
||||||
class Usage(PartModel):
|
class Usage(PartModel):
|
||||||
|
|
@ -82,28 +114,29 @@ class Usage(PartModel):
|
||||||
sketch = models.ForeignKey(Sketch, on_delete=models.CASCADE, db_index=True)
|
sketch = models.ForeignKey(Sketch, on_delete=models.CASCADE, db_index=True)
|
||||||
sketch_number = models.CharField(max_length=32)
|
sketch_number = models.CharField(max_length=32)
|
||||||
exact_sketch = models.BooleanField(default=True)
|
exact_sketch = models.BooleanField(default=True)
|
||||||
#quantity = models.ManyToManyField(ProductUsage)
|
|
||||||
|
# quantity = models.ManyToManyField(ProductUsage)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
no = self.part.number if self.part else "--(part.number)--"
|
no = self.part.number if self.part else "--(part.number)--"
|
||||||
na = self.part.part.name if self.part.part else "--(part.part.name)--"
|
na = self.part.get_name() if self.part else "--(part.part.name)--"
|
||||||
return self.sketch_number +"; " + str(no) +"; "+ na + "; " + str(self.sketch)
|
return self.sketch_number + "; " + str(no) + "; " + na + "; " + str(self.sketch)
|
||||||
|
|
||||||
|
|
||||||
class ProductUsage(PartModel):
|
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)
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, db_index=True)
|
||||||
quantity = models.IntegerField(null=True)
|
quantity = models.IntegerField(null=True)
|
||||||
on_demand = models.BooleanField(default=False)
|
on_demand = models.BooleanField(default=False)
|
||||||
obsolete = 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_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)
|
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)
|
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)
|
note = models.CharField(max_length=1024, blank=True)
|
||||||
internal_note = models.CharField(max_length=2, blank=True)
|
internal_note = models.CharField(max_length=2, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
no = self.usage.part.number if self.usage else "--(part.number)--"
|
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)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="submit"/>
|
<input type="submit"/>
|
||||||
<p><label for="sketch_name">Name:</label><input type="text" name="sketch_name" value="{{ sketch.name }}"/></p>
|
<p><label for="sketch_name">Name:</label><input type="text" name="sketch_name" value="{{ sketch.name }}"/></p>
|
||||||
|
<button type="button" onclick="addRow()">add row</button>
|
||||||
<table id="table">
|
<table id="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Bild-Nr.</th>
|
<th>Bild-Nr.</th>
|
||||||
|
|
@ -34,7 +35,7 @@
|
||||||
<td>{{ usage.sketch_number }}</td>
|
<td>{{ usage.sketch_number }}</td>
|
||||||
<td>{{ usage.part.number }}</td>
|
<td>{{ usage.part.number }}</td>
|
||||||
<td>{{ productusage.internal_note }}</td>
|
<td>{{ productusage.internal_note }}</td>
|
||||||
<td>{{ usage.part.part.name }}</td>
|
<td>{{ usage.part.get_name }}</td>
|
||||||
<td>{{ productusage.quantity }}</td>
|
<td>{{ productusage.quantity }}</td>
|
||||||
<td>{{ productusage.used_until }}</td>
|
<td>{{ productusage.used_until }}</td>
|
||||||
<td>{{ productusage.used_since }}</td>
|
<td>{{ productusage.used_since }}</td>
|
||||||
|
|
@ -47,7 +48,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
<button type="button" onclick="addRow()">add row</button>
|
|
||||||
<table style="display:none;">
|
<table style="display:none;">
|
||||||
<tr id="template">
|
<tr id="template">
|
||||||
<td><input type="text" size="8" name="fignumber"/></td>
|
<td><input type="text" size="8" name="fignumber"/></td>
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,28 @@ from django.db import transaction
|
||||||
|
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
ADD_SKETCH_REQUIRES = ['sketch_name','sketch_number','']
|
ADD_SKETCH_REQUIRES = ['sketch_name', 'sketch_number', '']
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
product_list = Product.objects.all()
|
product_list = Product.objects.all()
|
||||||
context = {'product_list': product_list}
|
context = {'product_list': product_list}
|
||||||
return render(request, 'parts/products.html', context)
|
return render(request, 'parts/products.html', context)
|
||||||
|
|
||||||
|
|
||||||
def product(request, product_id):
|
def product(request, product_id):
|
||||||
product = Product.objects.get(id=product_id)
|
product = Product.objects.get(id=product_id)
|
||||||
sketches = Sketch.objects.filter(usage__productusage__product=product)
|
sketches = Sketch.objects.filter(usage__productusage__product=product)
|
||||||
context = {'product': product, 'sketches': sketches}
|
context = {'product': product, 'sketches': sketches}
|
||||||
return render(request, 'parts/product.html', context)
|
return render(request, 'parts/product.html', context)
|
||||||
|
|
||||||
|
|
||||||
def sketch(request, sketch_id):
|
def sketch(request, sketch_id):
|
||||||
sketch = Sketch.objects.get(id=sketch_id)
|
sketch = Sketch.objects.get(id=sketch_id)
|
||||||
context = {'sketch': sketch}
|
context = {'sketch': sketch}
|
||||||
return render(request, 'parts/sketch.html', context)
|
return render(request, 'parts/sketch.html', context)
|
||||||
|
|
||||||
|
|
||||||
def part_search(request):
|
def part_search(request):
|
||||||
if "term" in request.GET:
|
if "term" in request.GET:
|
||||||
term = request.GET.get("term", '')
|
term = request.GET.get("term", '')
|
||||||
|
|
@ -32,16 +36,18 @@ def part_search(request):
|
||||||
json.dumps(
|
json.dumps(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id':part.id,
|
'id': part.id,
|
||||||
'label':part.number + " - " + part.get_name(),
|
'label': part.number + " - " + part.get_name(full=True),
|
||||||
'value':part.number,
|
'value': part.number,
|
||||||
'name':part.get_name()
|
'name': part.get_name(first=True)
|
||||||
}
|
}
|
||||||
for part in parts],
|
for part in parts
|
||||||
|
],
|
||||||
indent=1)
|
indent=1)
|
||||||
)
|
)
|
||||||
return HttpResponse("404")
|
return HttpResponse("404")
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def sketch_add(request, product_id, sketch_id=None):
|
def sketch_add(request, product_id, sketch_id=None):
|
||||||
product = get_object_or_404(Product, pk=product_id)
|
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)
|
usage.exact_sketch = is_exact_sketchnumber(usage.sketch_number)
|
||||||
|
|
||||||
part_number = request.POST.getlist('partnumber')[i]
|
part_number = request.POST.getlist('partnumber')[i]
|
||||||
if len(part_number)<1:
|
if len(part_number) < 1:
|
||||||
continue
|
continue
|
||||||
branded_part, created = BrandedPart.objects.get_or_create(brand=product.brand, number=part_number)
|
branded_part, created = BrandedPart.objects.get_or_create(brand=product.brand, number=part_number)
|
||||||
|
_part_name = request.POST.getlist('name')[i].strip()
|
||||||
if created:
|
if created:
|
||||||
try:
|
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:
|
except Part.MultipleObjectsReturned:
|
||||||
part = Part.objects.filter(name=request.POST.getlist('name')[i]).first()
|
part = Part.objects.filter(name=_part_name).first()
|
||||||
branded_part.part = part
|
branded_part.parts.add(part)
|
||||||
branded_part.brand = product.brand
|
branded_part.brand = product.brand
|
||||||
branded_part.save()
|
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.part = branded_part
|
||||||
usage.save()
|
usage.save()
|
||||||
|
|
||||||
prod = ProductUsage()
|
prod = ProductUsage()
|
||||||
prod.usage = usage
|
prod.usage = usage
|
||||||
prod.product = product
|
prod.product = product
|
||||||
|
|
@ -95,14 +111,14 @@ def sketch_add(request, product_id, sketch_id=None):
|
||||||
if len(note):
|
if len(note):
|
||||||
prod.note = note
|
prod.note = note
|
||||||
if len(replaced):
|
if len(replaced):
|
||||||
#replacement, _ = ProductUsage.objects.get_or_create(product=product, usage__part__number=replaced)
|
# replacement, _ = ProductUsage.objects.get_or_create(product=product, usage__part__number=replaced)
|
||||||
#prod.replaced_by = replacement
|
# prod.replaced_by = replacement
|
||||||
prod.note+= "{REP:"+str(replaced)+"}" #TODO
|
prod.note += "{REP:" + str(replaced) + "}" # TODO
|
||||||
internal = request.POST.getlist('internal')[i]
|
internal = request.POST.getlist('internal')[i]
|
||||||
if len(internal):
|
if len(internal):
|
||||||
prod.internal = internal
|
prod.internal = internal
|
||||||
prod.save()
|
prod.save()
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse('parts:sketch', args=(sketch.id,)))
|
return HttpResponseRedirect(reverse('parts:sketch', args=(sketch.id,)))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -112,6 +128,7 @@ def get_quantity(qnty_str):
|
||||||
qnty = int(qnty_str)
|
qnty = int(qnty_str)
|
||||||
return qnty
|
return qnty
|
||||||
|
|
||||||
|
|
||||||
def is_exact_sketchnumber(sketch_number):
|
def is_exact_sketchnumber(sketch_number):
|
||||||
approximate = ("ähn", "vgl", "~", "zu")
|
approximate = ("ähn", "vgl", "~", "zu")
|
||||||
return not any([i in sketch_number for i in approximate])
|
return not any([i in sketch_number for i in approximate])
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue