Python Django Model Design

RegShoe

Ensign Pro
Registriert
Aug. 2023
Beiträge
142
Ich bin grade an einem Django-Projekt dran mit dem ich alle unsere Produktdaten (techn. Daten, Dokumente, Chargen-Verwaltung.. halt alles was dazugehört) zusammenfassen möchte. Erstmal damit wir alle Daten zentral an einem Platz haben und später auch um verschiedene weitere Arbeitsabläufe zu erleichtern.

Grob haben wir 3 Produktgruppen: Leuchten, Sicherheitsbeleuchtung und Zubehör. In der Produktgruppe Leuchten kommt es recht oft vor, dass es von einem Produkt mehrere Varianten gibt, die sich z. B. nur in der Farbe unterscheiden oder noch eine zusätzliche Option dabei haben. Die sonstigen Produktdetails und leuchtenspezifischen Daten sind aber identisch.

Mein Problem jetzt...Wie mache ich die Verbindung zwischen ProductVariant, Lamp, EmergencyLighting

Eine Idee war Vererbung. Also etwas in der Art wie

Python:
class ProductVariant(models.Model):
    ...
    product = models.ForeignKey(Product)


class ProductDetails(models.Model)
    ...


class Product(models.Model):
    ...
    product_details = models.ForeignKey(ProductDetails)


class Lamp(Product):
    ...


class Emergencylighting(Product):
    ...
Nur lese ich (fast) überall nur, dass man Vererbung in Django eigentlich vermeiden soll..


Mein Model sieht jetzt grob vereinfacht so aus (Kommentare sind jetzt nur fürs Forum drin ;-))

Python:
# Daten die sich von Variante zu Variante ändern (können)
class ProductVariant(models.Model):
    item_id = models.Charfield()
    case_colour = models.ForeignKey(Colour)
    ...
    options = models.ManyToManyField('ProductVariant', through='OptionDetails', related_name='option_details')


#techn. Daten die bei allen Produkten benötigt werden
class ProductDetails(models.Model):
    length = models.IntegerField()
    width = models.IntegerField()
    height = models.IntegerField()
    diameter = models.IntegerField()
    weight = models.DecimalField()
    ...


#halt die Leuchtenpezifischen Daten ;-).
class Lamp(models.Model)
    driver_included = models.BooleanField()
    external_driver = models.BooleanField()
    changeable_driver = models.BooleanField()
    average_lifespan = models.IntegerField()
    ugr = models.IntegerField()
    cri = models.IntegerField()
    ...
    product_details = models.ForeignKey(ProductDetails)


# dto für Sicherheitsbeleuchtung
class EmergencyLighting(models.Model)
    lum_flux_on_wire = models.IntegerField()
    lum_flux_on_accu = models.IntegerField()
    autotest = models.BooleanField()
    ...
    product_details = models.ForeignKey(ProductDetails)

Wenn also jemand eine Idee hat.. Wäre das wirklich super :daumen:

Danke schonmal
 
Ich hab letzte Nacht (bevor ich das gepostet hab) einiges gelesen und sehr oft war der Tenor, dass es unter anderem zu viele 'unnötige' selects und joins bedeuten würde. Dazu soll es wohl auch nicht so gut wartbar/erweiterbar sein. Aufgrund der doch schon sehr fortgeschrittenen Uhrzeit hab ich dann auch nicht mehr soo genau weitergelesen ;-).
Allerdings hab ich jetzt heute Vormittag nochmal etwas weiter gelesen und werde es dann jetzt wohl doch mal damit versuchen. Dazu guck ich mir dann jetzt auch mal Django-Polymorphic etwas genauer an...
 
SO, das Thema passt eigentlich immernoch also mach ich hier mal weiter ;-).

Jetzt hat sich mitten im Entwickeln noch ein neues Thema aufgetan. Wir haben uns überlegt unsere Produktdaten demnächst als ETIM-BMECAT (Das erstellen einer validen XML-Datei ist soweit auch kein Problem.) an unsere Kunden weitergeben. Dazu werden die Produkte in Klassen eingeteilt und verschiedene Eigenschaften haben feste Feature-Codes. Beispiel. HIER fängt aber mein Problem an. Irgendwie muß ich jetzt den einzelnen Eigenschaften auch diese Feature Codes bzw. zusätzlich auch noch bei einigen die Value-Codes zuordnen.

Ich hab wo anders den Tip bekommen, dass ich die ganzen Eigenschaften "auslagere"...Was mir aber etwas zu hoch ist... Hab jetzt zwar eine Zeit "rumprobiert" aber das Ergebnis ist jetzt imho nicht so wirklich gut..

Python:
class ProductType(models.Model):
    type_text = models.CharField(verbose_name=_('Produkt-Kategorie'), null=False, max_length=100)
    main_type = models.ForeignKey('self', verbose_name=_('Hauptkategorie'), null=True, on_delete=models.PROTECT)
    etim_class = models.ForeignKey('ETIMClass', verbose_name=_('ETIM Klasse'), null=True, blank=True,
on_delete=models.PROTECT)
    product_properties = models.ManyToManyField('Property')

class PropertyType(models.Model):
    type = models.CharField(verbose_name=_('Feldtyp'), max_length=25)
    
    def __repr__(self):
        return self.type

class ETIMClass(models.Model):
    code = models.CharField(verbose_name=_('ETIM Class Code'), max_length=25)
    short_description = models.CharField(verbose_name=_('Kurzbeschreibung'), max_length=100, default='')
    version = models.IntegerField(verbose_name='Version')
    etim_feature_codes = models.ManyToManyField('ETIMCode', through='ETIMFeatureCodeAdditions', verbose_name=_('ETIM Codes'))

class ETIMFeatureCode(models.Model):
    feature_code = models.CharField(verbose_name=_('ETIM Feature Code'), max_length=20)
    value_codes = models.ManyToManyField('ETIMValueCode', verbose_name=_('ETIM Value Code'))

class ETIMValueCode(models.Model):
    value_code = models.CharField(verbose_name=_('ETIM Value Code'), max_length=20)

class Property(models.Model):
    text = models.CharField(verbose_name=_('Eigenschafts-Text'), max_length=150, null=True, blank=True)
    etim_code = models.ForeignKey(ETIMFeatureCode, verbose_name=_('ETIM Code'), null=True, blank=True, on_delete=models.PROTECT)
    type = models.ForeignKey(PropertyType, verbose_name=_('Feldtyp'), on_delete=models.PROTECT, null=False)

class ProductProperty(models.Model):
    property = models.ForeignKey(Property, verbose_name=_('Eigenschaft'), on_delete=models.PROTECT, null=False)
    char_value = models.CharField()
    int_value = models.IntegerField()
    datetime_value = models.DateTimeField()
    bool_value = models.BooleanField()
    decimal_value = models.DecimalField(max_digits=7, decimal_places=3)
    etim_value_code = models.ForeignKey(ETIMValueCode, verbose_name=_('ETIM Value Code'), on_delete=models.PROTECT, null=True, blank=True)
    @property
    def get_value(self):
        if self.product_property.type == 'char':
            return self.char_value

class Product(models.Model
    product_properties = models.ManyToManyField(ProductProperty, verbose_name=_('Eigenschaften'))
    product_type = models.ForeignKey(ProductType, verbose_name=_('Produktkategorie'), on_delete=models.PROTECT)

    manufacturer = models.ForeignKey('Manufacturer', verbose_name=_('Hersteller'), null=True, blank=True, default=None, on_delete=models.PROTECT)

    trademark = models.ForeignKey('Trademark', verbose_name=_('Market'), on_delete=models.PROTECT, null=True, blank=True)

    harmonised_standards = models.ManyToManyField('HarmonisedStandards', verbose_name=_('gültige Normen'))

    customs_tariff_number = models.ForeignKey(CustomsTariffNumber, verbose_name=_('Zolltarifnummer'), null=True,
on_delete=models.PROTECT)

    integrated_products = models.ManyToManyField('Product',
verbose_name=_('integriertes Produkt (falls vorhanden)'))

    options = models.ManyToManyField('Product', through='OptionDetails', related_name='option_details')

    catalogue_page = models.ManyToManyField('CataloguePage', verbose_name=_('Katalog-Seite'), related_name='products')

    created_time = models.DateTimeField(verbose_name=_('Erstellungsdatum'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('Änderungsdatum'), auto_now=True)

    on_market_start_date = models.DateField(verbose_name=_('Startdatum'), null=True, blank=True)
    on_market_end_date = models.DateField(verbose_name=_('Enddatum'), null=True, blank=True)
    ...

Die ETIM-Klassen und damit ja auch die Feature-Codes sind ja mit dem ProductType verknüpft. Wie aber kann ich die Eigenschaften die keiner ETIM-Klasse zugeordnet sind einem ProductType zuordnen? Mein erster Gedanke wäre, dass ich in Property noch ein ForeignKey-Feld packe was auf ProductType verweist.
Das sieht aber irgendwie "falsch" aus, weil ich dann ja eigentlich 2 Verweise von ProductType auf Property hab (einmal über die ETIM-Klasse und die Feature-Codes und einmal dann direkt).
Hierdurch könnte ich mir dann z. B. die ganze Vererbungssache sparen ;-).

Hat vielleicht jemand eine "bessere" Idee? Oder kann ich das wirklich so machen ohne, dass es mir bald auf die Füße fällt?

Danke schonmal
 
So, es war leider wenig bis keine Zeit um hiermit weiterzumachen... Das "Auslagern" der Properties in eine eigene Property-Klasse stellt mich jetzt leider vor einige zusätzliche Probleme/Fallstricke.

Auf der einen Seite hätte ich so zwar alle Daten gebündelt in einer Klasse. Auf der anderen Seite weiß ich jetzt aber nicht, wie ich hier sinnvolle und konsistente Verbindungen zu den anderen Klassen herstellen soll.
Ich habe ja z. B. auch eine Klasse "Luminaire" die von Product erbt. In dieser Klasse bräuchte ich ja theoretisch auch einen Verweis auf die Property-Klasse, weil ich da ja auch einige Leuchten-spezifische Daten drin habe die nicht in die Product-Klasse bzw. die anderen Klassen die von Product erben gehören. Zusätzlich hab ich bei dieser Klasse mindestens eine One-To-Many Relation in der auch Daten drin sind die ich für die Klassifizierung benötige.

Python:
class Luminaire(Product):
    driver_included = models.BooleanField(verbose_name=_('Treiber inklusive?'), default=True)
    external_driver = models.BooleanField(verbose_name=_('Treiber extern?'), default=True)
    driver_changeable = models.BooleanField(verbose_name=_('Treiber austauschbar?'), default=True)
    led_changeable = models.BooleanField(verbose_name=_('LEDs austauschbar?'), default=True)
    average_lifespan = models.IntegerField(verbose_name=_('durchschnittliche Lebensdauer'), null=True, blank=True)
    ...

class PhotometricData(models.Model):
    lum_flux = models.IntegerField(verbose_name=_('Lichtstrom min. (lm)'))
    power = models.DecimalField(verbose_name=_('Leistung max. (W)'), max_digits=6, decimal_places=2, null=True)
    current = models.IntegerField(verbose_name=_('Stromstärke (mA)'), null=True)
    ldt_file = models.FileField(verbose_name=_('LDT-Datei'), upload_to='data/ldt_files/', null=True)
    light_curve = models.FileField(verbose_name=_('Lichtverteilungskurve'), upload_to='data/light_curve/', null=True)
    reference_settings = models.BooleanField(verbose_name=_('Referenzeinstellungen?'), default=False)
    colour_consistency = models.IntegerField(verbose_name=_('Farbkonsistenz (McAdam Ellipsen)'),
                                             validators=[MaxValueValidator(7),
                                                         MinValueValidator(1)], )
    beam_angle = models.IntegerField(verbose_name=_('Abstrahlwinkel'),
                                     validators=[MaxValueValidator(360),
                                                 MinValueValidator(0)],
                                     null=True,
                                     blank=True)

    colour_temperature = models.ForeignKey(ColourTemperature, verbose_name=_('Lichtfarbe'), on_delete=models.PROTECT,
                                           null=True, blank=True)
    luminaire = models.ForeignKey(Luminaire, on_delete=models.CASCADE)

Kann mich vielleicht jemand in die richtige Richtung schubsen?

Zusätzliche Design-Frage: Ich habe einige verschiedene Properties die jeweils als One-To-Many einbinden würde. Übers Schreiben kam ich jetzt aber auf bestimmt 15 ForeignKey-Felder in einer Klasse... Spricht hier was dagegen?

Vielen Dank für eure Hilfe.
 
Zurück
Oben