diff --git a/lelcsc/core/apps.py b/lelcsc/core/apps.py index 1edb924..c9d6b73 100644 --- a/lelcsc/core/apps.py +++ b/lelcsc/core/apps.py @@ -1,4 +1,86 @@ from django.apps import AppConfig +from django.db.models.signals import post_migrate + + +def create_properties(sender, **kwargs): + from .models import Property + + Property.objects.get_or_create( + name="Capacitance", + defaults={ + "filterable": True, + "type": Property.Type.QUANTITY, + "unit": "F", + "scale": -12, + "searchable": False, + }, + ) + + Property.objects.get_or_create( + name="Footprint", + defaults={ + "filterable": True, + "type": Property.Type.TEXT, + "unit": "", + "scale": 0, + "searchable": False, + }, + ) + + Property.objects.get_or_create( + name="LCSC part number", + defaults={ + "filterable": False, + "type": Property.Type.TEXT, + "unit": "", + "scale": 0, + "searchable": True, + }, + ) + + Property.objects.get_or_create( + name="Inductance", + defaults={ + "filterable": True, + "type": Property.Type.QUANTITY, + "unit": "H", + "scale": -6, + "searchable": False, + }, + ) + + Property.objects.get_or_create( + name="Rated power", + defaults={ + "filterable": True, + "type": Property.Type.QUANTITY, + "unit": "W", + "scale": -4, + "searchable": False, + }, + ) + + Property.objects.get_or_create( + name="Rated voltage", + defaults={ + "filterable": True, + "type": Property.Type.QUANTITY, + "unit": "V", + "scale": -3, + "searchable": False, + }, + ) + + Property.objects.get_or_create( + name="Resistance", + defaults={ + "filterable": True, + "type": Property.Type.QUANTITY, + "unit": "Ω", + "scale": -3, + "searchable": False, + }, + ) class CoreConfig(AppConfig): @@ -6,3 +88,6 @@ class CoreConfig(AppConfig): label = "lelcsc_core" name = "lelcsc.core" verbose_name = "core" + + def ready(self): + post_migrate.connect(create_properties, sender=self) diff --git a/lelcsc/core/migrations/0002_component_property_stock_value_component_properties.py b/lelcsc/core/migrations/0002_component_property_stock_value_component_properties.py new file mode 100644 index 0000000..2394bf9 --- /dev/null +++ b/lelcsc/core/migrations/0002_component_property_stock_value_component_properties.py @@ -0,0 +1,156 @@ +# Generated by Django 5.1.1 on 2024-09-16 22:00 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lelcsc_core", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Component", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("part_number", models.CharField(max_length=30)), + ], + ), + migrations.CreateModel( + name="Property", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=50)), + ( + "filterable", + models.BooleanField( + help_text="Whether this property can be used to filter components" + ), + ), + ( + "type", + models.CharField( + choices=[("quantity", "Quantity"), ("text", "Text")], + max_length=8, + ), + ), + ( + "searchable", + models.BooleanField( + help_text="Whether this property should be used in search" + ), + ), + ( + "unit", + models.CharField( + help_text="Symbol of this quantity's unit of measurement", + max_length=3, + ), + ), + ( + "scale", + models.SmallIntegerField( + help_text="Exponent of the smallest possible value" + ), + ), + ], + ), + migrations.CreateModel( + name="Stock", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("quantity", models.PositiveIntegerField()), + ("original_quantity", models.PositiveIntegerField()), + ("total_value", models.DecimalField(decimal_places=2, max_digits=8)), + ("location", models.TextField()), + ("owner_name", models.CharField(max_length=150)), + ( + "component", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="lelcsc_core.component", + ), + ), + ( + "owner", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Value", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("text", models.TextField()), + ("integer", models.BigIntegerField()), + ( + "component", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="lelcsc_core.component", + ), + ), + ( + "property", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="lelcsc_core.property", + ), + ), + ], + options={ + "unique_together": {("component", "property")}, + }, + ), + migrations.AddField( + model_name="component", + name="properties", + field=models.ManyToManyField( + through="lelcsc_core.Value", to="lelcsc_core.property" + ), + ), + ] diff --git a/lelcsc/core/models/__init__.py b/lelcsc/core/models/__init__.py new file mode 100644 index 0000000..6ef5565 --- /dev/null +++ b/lelcsc/core/models/__init__.py @@ -0,0 +1,3 @@ +from .auth import OIDCUser, User +from .component import Component, Property, Value +from .stock import Stock diff --git a/lelcsc/core/models.py b/lelcsc/core/models/auth.py similarity index 100% rename from lelcsc/core/models.py rename to lelcsc/core/models/auth.py diff --git a/lelcsc/core/models/component.py b/lelcsc/core/models/component.py new file mode 100644 index 0000000..0ce960b --- /dev/null +++ b/lelcsc/core/models/component.py @@ -0,0 +1,62 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class Property(models.Model): + class Type(models.TextChoices): + QUANTITY = "quantity", _("Quantity") + TEXT = "text", _("Text") + + name = models.CharField(max_length=50) + filterable = models.BooleanField( + help_text=_("Whether this property can be used to filter components") + ) + type = models.CharField(choices=Type, max_length=8) + searchable = models.BooleanField( + help_text=_("Whether this property should be used in search") + ) + unit = models.CharField( + max_length=3, help_text=_("Symbol of this quantity's unit of measurement") + ) + scale = models.SmallIntegerField( + help_text=_("Exponent of the smallest possible value") + ) + + def __str__(self): + return ( + self.name + f" [{self.unit}]" if self.type == Property.Type.QUANTITY else "" + ) + + +class Component(models.Model): + part_number = models.CharField(max_length=30) + properties = models.ManyToManyField(Property, through="Value") + + def __str__(self): + return self.part_number + + +class Value(models.Model): + class Meta: + unique_together = ("component", "property") + + property = models.ForeignKey(Property, on_delete=models.CASCADE) + component = models.ForeignKey(Component, on_delete=models.CASCADE) + text = models.TextField() + integer = models.BigIntegerField() + + def __str__(self): + match self.property.type: + case Property.Type.QUANTITY: + s = str(integer) + + scale = self.property.scale + if scale > 0: + s += "0" * scale + elif scale < 0: + s = str(integer).rjust(abs(scale) + 1, "0") + s = s[:scale] + "." + s[scale:] + + return f"{s} {self.property.unit}" + case Property.Type.TEXT: + return self.text diff --git a/lelcsc/core/models/mixins.py b/lelcsc/core/models/mixins.py new file mode 100644 index 0000000..c498dec --- /dev/null +++ b/lelcsc/core/models/mixins.py @@ -0,0 +1,9 @@ +from django.db import models + + +class HasTimestamps(models.Model): + class Meta: + abstract = True + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) diff --git a/lelcsc/core/models/stock.py b/lelcsc/core/models/stock.py new file mode 100644 index 0000000..00bef49 --- /dev/null +++ b/lelcsc/core/models/stock.py @@ -0,0 +1,15 @@ +from django.db import models + +from .auth import User +from .component import Component +from .mixins import HasTimestamps + + +class Stock(HasTimestamps): + component = models.ForeignKey(Component, on_delete=models.PROTECT) + quantity = models.PositiveIntegerField() + original_quantity = models.PositiveIntegerField() + total_value = models.DecimalField(max_digits=8, decimal_places=2) + location = models.TextField() + owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) + owner_name = models.CharField(max_length=150)