shimatta-kenkyusho/shimatta_kenkyusho/parts/models.py
2021-08-07 17:37:36 +02:00

272 lines
9.0 KiB
Python

from django.db import models
from shimatta_modules import RandomFileName
from django.db.models import F, Sum
from django.contrib.auth.models import User as AuthUser
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.dispatch import receiver
import os
import uuid
storage_name_validator = RegexValidator(r'^[^/]*$', 'Slashes are not allowed in storage names')
# Create your models here.
class ComponentParameterType(models.Model):
class Meta:
ordering = ['parameter_name']
parameter_name = models.CharField(max_length=50, unique=True)
parameter_description = models.TextField(null=True, blank=True)
unit = models.CharField(max_length=10)
freetext_parameter = models.BooleanField()
engineering_unit = models.BooleanField()
it_unit = models.BooleanField()
def __str__(self):
return self.parameter_name + ' in ' + self.unit
class ComponentType(models.Model):
class Meta:
ordering = ['class_name']
class_name = models.CharField(max_length=50, unique=True)
passive = models.BooleanField()
possible_parameter = models.ManyToManyField(ComponentParameterType)
def __str__(self):
return '[' + self.class_name + ']'
class Storage(models.Model):
class Meta:
# permissions = ()
# Allow only one child with the same name
unique_together = ('name', 'parent_storage')
ordering = ['name']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=100, validators=[storage_name_validator])
parent_storage = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
responsible = models.ForeignKey(AuthUser, on_delete=models.SET_NULL, blank=True, null=True)
def get_path_components(self):
chain = []
iterator = self
chain.append(self)
while iterator.parent_storage is not None:
chain.append(iterator.parent_storage)
iterator = iterator.parent_storage
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].name
return output
def get_qr_code(self):
qrdata = '[stor_uuid]' + str(self.id)
return qrdata
def __str__(self):
return self.get_full_path()
def get_children(self):
if self is None: # Root node
return Storage.objects.filter(parent_storage=None)
else:
return 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:
raise ValidationError('The fields name, parent_storage must make a unique set')
super(Storage, self).validate_unique(exclude)
def get_total_stock_amount(self):
stocks = Stock.objects.filter(storage=self)
return stocks.aggregate(Sum('amount'))['amount__sum']
def save(self, *args, **kwargs):
self.validate_unique()
super(Storage, self).save(*args, **kwargs)
class Distributor(models.Model):
class Meta:
ordering = ['name']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=100, unique=True)
website = models.CharField(max_length=200, null=True, blank=True)
image = models.ImageField(upload_to=RandomFileName.RandomFileName('distributor-logos'), null=True, blank=True)
component_link_pattern = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return self.name
class Package(models.Model):
class Meta:
ordering = ['name']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=50, unique=True)
pin_count = models.PositiveIntegerField()
smd = models.BooleanField()
image = models.ImageField(upload_to=RandomFileName.RandomFileName('package-images'), blank=True, null=True)
def __str__(self):
return self.name
class Manufacturer(models.Model):
class Meta:
ordering = ['name']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=100, unique=True)
website = models.CharField(max_length=200, null=True, blank=True)
image = models.ImageField(upload_to=RandomFileName.RandomFileName('manufacturer-images'), blank=True, null=True)
def __str__(self):
return str(self.name)
class Component(models.Model):
class Meta:
unique_together = ('name', 'manufacturer', 'package')
ordering = ['name', 'manufacturer']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=100)
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET_NULL, blank=True, null=True)
component_type = models.ForeignKey(ComponentType, on_delete=models.SET_NULL, blank=True, null=True)
pref_distri = models.ForeignKey(Distributor, on_delete=models.SET_NULL, blank=True, null=True)
description = models.TextField(null=True, blank=True)
datasheet_link = models.CharField(max_length=300, null=True, blank=True)
package = models.ForeignKey(Package, on_delete=models.SET_NULL, blank=True, null=True)
image = models.ImageField(upload_to=RandomFileName.RandomFileName('component-images'), blank=True, null=True)
def __str__(self):
pack_name = ''
man_name = ''
if self.package:
pack_name = ' in ' + self.package.name
if self.manufacturer:
man_name = ' by ' + self.manufacturer.name
return self.name + pack_name + man_name
def get_resolved_image(self):
media_url = ''
url = None
if self.image:
url = '%s%s' % (media_url, self.image.url)
else:
if self.package:
if self.package.image:
url = '%s%s' % (media_url, self.package.image.url)
return url
def get_qr_code(self):
qrdata = '[comp_uuid]' + str(self.id)
return qrdata
class ComponentParameter(models.Model):
class Meta:
unique_together = ('component', 'parameter_type')
ordering = ['id']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
component = models.ForeignKey(Component, on_delete=models.CASCADE) # A target component is required!
parameter_type = models.ForeignKey(ComponentParameterType, on_delete=models.CASCADE)
value = models.FloatField()
text_value = models.TextField(null=True, blank=True)
def __str__(self):
return str(self.parameter_type) + ': ' + str(self.value)
class Stock(models.Model):
class Meta:
unique_together = ('component', 'storage')
ordering = ['id']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
component = models.ForeignKey(Component, on_delete=models.PROTECT, blank=True, null=True)
storage = models.ForeignKey(Storage, on_delete=models.PROTECT, blank=True, null=True)
amount = models.PositiveIntegerField()
watermark = models.IntegerField() # negative is no watermark
def atomic_increment(self, increment):
if self.amount + increment < 0:
return False
self.amount = F('amount') + increment
self.save(update_fields=['amount'])
return True
def get_qr_code(self):
qr_data = '[stock]'+str(self.id)
return qr_data
def __str__(self):
return str(self.component) + ' @ ' + str(self.amount) + ' in ' + str(
self.storage)
def get_under_watermark():
return Stock.objects.filter(amount__lt = F('watermark'))
class DistributorNum(models.Model):
class Meta:
unique_together = ('component', 'distributor')
ordering = ['distributor_part_number']
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
distributor_part_number = models.CharField(max_length=100)
distributor = models.ForeignKey(Distributor, on_delete=models.CASCADE)
component = models.ForeignKey(Component, on_delete=models.CASCADE)
def __str__(self):
return self.component.name + '@' + self.distributor.name + ': ' + self.distributor_part_number
# These functions ensure that the uploaded images are deleted if the model is deleted or the image file is changed.
@receiver(models.signals.post_delete, sender=Component)
@receiver(models.signals.post_delete, sender=Distributor)
@receiver(models.signals.post_delete, sender=Package)
@receiver(models.signals.post_delete, sender=Manufacturer)
def auto_delete_file_on_delete(sender, instance, **kwargs):
"""
Deletes file from filesystem
when corresponding `MediaFile` object is deleted.
"""
if instance.image:
if os.path.isfile(instance.image.path):
os.remove(instance.image.path)
@receiver(models.signals.pre_save, sender=Component)
@receiver(models.signals.pre_save, sender=Distributor)
@receiver(models.signals.pre_save, sender=Package)
@receiver(models.signals.pre_save, sender=Manufacturer)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding `MediaFile` object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_file = sender.objects.get(pk=instance.pk).image
except sender.DoesNotExist:
return False
new_file = instance.image
if not old_file == new_file:
if not old_file:
return True
if os.path.isfile(old_file.path):
os.remove(old_file.path)