zusätzliche Informationen anhand des Feld-Namens

Django, Flask, Bottle, WSGI, CGI…
Antworten
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

Ich weiß gar nicht ob das jetzt besser hier hin passt oder eher in das Datenbank Subforum.


Meine Produkt-Daten in meinem Django-Projekt sollen jetzt nach etim-bmecat exportiert werden. Zusätzlich sollen die Produkte auch nach ETIM klassifiziert werden. Dafür wird jedes Produkt einer bestimmten Klasse zugeordnet z. B. Wand- und Deckenleuchten wäre Klasse EC002892. Das ist jetzt kein Problem, das ist nur ein zusätzliches Feld bzw. Tabelle in der DB ;-).

Mein Problem jetzt: Die einzelnen Eigenschaften des Produkts bekommen auch jeweils einen eigenen Code. So bekommt der Lichtstrom (bei mir in der Datenbank auf zwei Felder aufgeteilt luminous_flux_min und luminous_flux_max) im ETIM-System dann den Code EF009349. Mir fehlt allerdings eine Idee wie ich eine ordentliche Zuweisung zwischen der jeweiligen Eigenschaft und dem Featurecode hinbekomme

Das einzige was mir eingefallen ist, dass ich eine separate Tabelle erstelle in der ich zu jedem Feldnamen einen Eintrag mit dem jeweiligen Featurecode habe. Dann würde ich mit getattr den Feldnamen "ermitteln" und dann eben in der Tabelle suchen. Da es sich hier (bis jetzt) nur um ca. 30 - 40 Codes handelt wäre der Aufwand (auch bei Änderungen) noch halbwegs erträglich.

Gibts vielleicht eine bessere Lösung?

Danke schonmal
Benutzeravatar
sparrow
User
Beiträge: 4403
Registriert: Freitag 17. April 2009, 10:28

Das klingt, als müsste die Eigenschaft ein Objekt sein dass den Code als Feld hat. Oder über eine 1:n Beziehung mehrere Codes.

Wenn man Feldnamen dynamisch ermitteln muss, ist das oft ein Warnzeichen für kaputtes Design.
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

Hmmm...Aber ich hab ja nicht nur einen Datentyp.. Wenn alles z. B. Integer wären also ungefähr so was in der Art:

Code: Alles auswählen

class ProductProperty(models.Model):
    text = models.CharField()
    value = models.IntegerField()
    feature_code = models.CharField()
In der jeweiligen Klasse mach ich dann ein

Code: Alles auswählen

class Product(models.Model):
    ...
    luminous_flux = models.ForeignKey(ProductProperty, ....)
    ...
Oder hab ich dich jetzt falsch verstanden.
Wie oben schon geschrieben hab ich aber auch andere Datentypen... :wink: . Dann müsste ich ja auch für jeden Datentyp eine eigene Tabelle erstellen :?
Bei meinem vorherigen "Problem" mit den verschiedenen Dateitypen war das ja noch recht einfach ;-). Aber hier :?
Benutzeravatar
sparrow
User
Beiträge: 4403
Registriert: Freitag 17. April 2009, 10:28

Und wenn es 200 verschiedene Eigenschaften gibt, das legst du 200 Felde dafür an? Das klingt komisch.

Das was hier erklärt ist, umfasst ja nur einen Teil des Problems, aber so wie ichd as verstehe würde ich gar kein Feld für die Eigenschaften im Product haben sondern eine Zwischtabelle für den Wert.

Symbolcode:

Code: Alles auswählen

class Property(models.Model):
    name = models.CharField
    code = models.CharField
    # Möglichkeiten den "Typ" zu prüfen, falls das nötig ist:
    # valueType = `choices of types`
    # regex = models.CharField (

class ProductProperty(models.Model):
    product = models.ForeignKey("Product")
    property = models.ForeignKey("Property")
    value = models.CharField()

class Product(models.Model):
    ...
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

OK, ich glaube grob zu verstehen...teilweise ;-).
Ich verstehe aber immernoch nicht wie ich dann die verschiedenen Typen abbilde?!
Oder speicher ich einfach alles als Char und wenn ich die Daten dann weiterverarbeiten will greif ich auf den "valueType" zurück und wandle die Daten dann entsprechend um? Genauso dann Date(Time)Fields. Am Ende hätte ich also "nur noch" meine ForeignKey oder ManyToMany Felder in der Klasse und der Rest würde in der Property Klasse landen?

Danke schonmal bis hierhin..
Benutzeravatar
sparrow
User
Beiträge: 4403
Registriert: Freitag 17. April 2009, 10:28

Da gibt es verschiedene Möglichkeiten.

Du könntest den "type" in einem Feld speichern und dann zum Beispiel in einer Property doe Konvertierung durchführen. Das macht aber zum Beispiel nur Sinn, wenn ud auf Datenbankebene nie mit den Daten arbetien willst. Also wenn es nur um eine Validierung geht.

Sollte man das doch auf Datenbanklevel brauchen, dann könnte man je Typ eine Property anlegen.

Last but not least:
Je Typ ein Feld.

Kann man ohne das Projekt zu kennen schwierig beantworten.

Code: Alles auswählen

class Property(models.Model):
    name = models.CharField
    code = models.CharField
    # Möglichkeiten den "Typ" zu prüfen, falls das nötig ist:
    # valueType = `choices of types`
    # regex = models.CharField (

class ProductProperty(models.Model):
    product = models.ForeignKey("Product")
    property = models.ForeignKey("Property")
    char_value = models.CharField()
    int_value = models.IntField()
    datetime_value = models.DateTime()
    
    @property
     def value(self):
        if self.property.type == "Integer":
            return self.int_value
        ....

class Product(models.Model):
    ...
Vielleicht kann man _da_ dann auch magisch die Werte aus den Feldern holen, aber in dem ORM steckt schon so viel Magie, dass das kaputt gehen könnte.
Müsste mane in bisschen probieren.
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

So wie es aussieht müsste ich dann wohl mein gesamtes model umbauen :?.

Bis jetzt ist eben alles über einige Klasse/Tabellen verteilt (Datenbank besteht jetzt aus ca. 60 Tabellen...ohne die von Django-eigenen). Das heisst, dass die Felder, denen ein Featurecode zugeordnet werden soll über diese Klassen verstreut liegen und sich imho auch nur schwer "enger" zusammenfassen lassen. Die Klassifizierung war bis letzte Woche auch noch nicht relevant bzw. mir komplett unbekannt.
Das jetzt alles SO umzubauen, dass die vorherige Funktionalität bestehen bleibt UND die Codezuordnung so funktioniert wie es soll übersteigt mein Können dann scheinbar doch um einiges...Also werd ich dieses Teilprojekt wohl als gescheitert erklären

Trotzdem vielen Dank für deine Hilfe
Benutzeravatar
sparrow
User
Beiträge: 4403
Registriert: Freitag 17. April 2009, 10:28

60 Tabellen zu haben ist nicht unbedingt ungewöhnlich.
60 Modelle zu haben, könnte der Hinweis darauf sein, dass bei der Normalisierung etwas grundsätzlich schief gegangen ist. Ein oft gemachter Fehler: Man denkt wie in einer Tabellenkalkulation. Oft wird man dazu verleitet, weil das Wort "Tabelle" bzw. "Table" verwendet wird. Dabei heißt die Tabelle in einer Tabellenkalkulation "Sheet".

Falls du dich fragst, ob du die Normalform deiner Daten gefunden hast (der Thread deutet auf Nein hin): der Wikipedia Artikel zur Normalisierung ist wirklich gut.
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

Für mich war bisher eine Model Klasse auch immer 1 table in der Datenbank. zzgl natürlich noch die jeweiligen join-tables für Many-to-Many. Der Unterschied zwischen einer Tabellenkalkulation und einer Datenbank ist mir schon klar ;-) und ich dachte bisher eigentlich ich hätte halbwegs auf die Normalisierung aufgepasst ;-).

Ich hab übrigens etwas übertrieben. Im Groben sind alle benötigten Daten auf mein "Product" Model und dessen Kind-Klassen aufgeteilt. Einige zusätzliche Daten (die halt eben nicht direkt in die "Product"-Klasse gehören) sind dann in separaten Klassen. Nachdem was ich sonst so im Netz gefunden hab imho nichts ungewöhnliches ;-).
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

SO, das lässt mir dann doch keine Ruhe... ;-)

ich habe jetzt mal deinen Vorschlag so übernommen.

Es gibt allerdings einige Eigenschaften die ich in meinen Daten etwas "genauer" habe. Z. B. gibt es für die Eigenschaft Lichtfarbe in dieser Klassifizierung nur ein Feld mit einer Range. In Wirklichkeit hängen da aber noch einige zusätzliche Daten dran, weshalb ich es einfacht nicht "einfach" mit in meine Product-Klasse packen kann.

VIelleicht hol ich einfach mal etwas aus ;-)
Wenn ich eine Leuchte mit einstellbarer Lichtfarbe (z. B. 3000K / 4000K / 5700K) habe, müßte ich in die XML-Datei

Code: Alles auswählen

<FVALUE>3000K</FVALUE>
<FVALUE>5700K</FVALUE>
einfügen..genauso auch bei der Leistung.

In meinem Modell habe ich aber eine eigene Klasse "PhotometricData" in der zu jeder Lichtfarbe/Leistung noch zusätzliche Daten hinterlegt sind (z. B. ändert sich je nach Lichtfarbe der Lichtstrom).
Ich habe dann also bei der oben genannten Leuchte pro Lichtfarbe jeweils eine Instanz von PhotometricData.
Der extremste Fall aktuell ist eine Leuchte bei der man Abstrahlwinkel, Lichtfarbe und Leistung auf jeweils 3 unterschiedliche Werte einstellen kann...und für jede Möglichkeit muß ich dann in meinem jetzigen Entwurf eine Instanz von PhotometricData erstellen.

Ich müßte dann also auch in dieser Klasse auf meine ProductProperty Klasse verweisen weil ja auch in dieser Klasse Properties sind zu denen es einen ETIM-Feature Code gibt.
Das Zusammenbauen des jeweiligen "Range-Strings" für die XML-Datei sollte dann ja kein Problem sein bzw. ist in diesem Stadium erstmal Nebensache ;-)

Hier mal der relevante Auszug. Die anderen Klassen sind glaube ich erstmal nicht so wichtig ;-)

Code: Alles auswählen

# Ich nutze diese Klasse für Product-Typ und Sub-Typ. z. B. haben wir eine Klasse LED-Leuchten, darunter gibts dann Sub-Klassen für LED-Panels, Wand-/Deckenleuchten usw...deshalb auch der ForeignKey auf self. Oder würde es mehr Sinn ergeben, wenn ich das in ProductType und (z. B.) ProductSubType aufteile?
class ProductType(models.Model):
    type_text = models.CharField(verbose_name=_('Produkt-Kategorie'), 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, on_delete=models.PROTECT)


# Die einzelnen Feature-Codes sollen laut Guidelines in einer festen Reihenfolge in der XML-Datei liegen, deshalb diese Intermediate-Klasse.
ETIMFeatureCodeAdditions(models.Model):
    etim_class = models.ForeignKey('ETIMClass', verbose_name=_('ETIM Klasse'), on_delete=models.PROTECT, null=True)
    etim_code = models.ForeignKey('ETIMFeatureCode', verbose_name=_('ETIM Code'), on_delete=models.PROTECT, null=True)
    order = models.PositiveIntegerField(verbose_name=_('Reihenfolge'))


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)
    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 PropertyType(models.Model):
    type = models.CharField(verbose_name=_('Feldtyp'), max_length=25)


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):
    product = models.ForeignKey('Product', verbose_name=_('Product'), on_delete=models.PROTECT)
    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)

    @property
    def value(self):
        if self.product_property.type == "integer":
            return self.int_value
        elif self.product_property.type == "char":
            return self.char_value
        elif self.product_property.type == "datetime":
            return self.datetime_value
        elif self.product_property.type == "boolean":
            return self.bool_value
        elif self.product_property.type == "decimal":
            return self.decimal_value
        elif self.product_property.type == "etim":
            return self.etim_value_code.value_code


class Product(models.Model):
    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=_('Marke'), on_delete=models.PROTECT, null=True,
                                  blank=True)

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


class PhotometricData(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    lum_flux_min = models.IntegerField(verbose_name=_('Lichtstrom min. (lm)'))
    power_min = models.DecimalField(verbose_name=_('Leistung min. (W)'), max_digits=6, decimal_places=2, null=True)
    amperage = models.IntegerField(verbose_name=_('Stromstärke (mA)'), null=True)
    reference_settings = models.BooleanField(verbose_name=_('Referenzeinstellungen?'), default=False)
    light_colour = models.ForeignKey(LightColour, verbose_name=_('Lichtfarbe'), on_delete=models.PROTECT)
    colour_consistency = models.DecimalField(verbose_name=_('Farbkonsistenz'), max_digits=2, decimal_places=1,
                                             null=True)
    beam_angle = models.ForeignKey('BeamAngles', verbose_name=_('Abstrahlwinkel'), null=True, blank=True,
                                   on_delete=models.PROTECT)
djangobernd
User
Beiträge: 2
Registriert: Samstag 18. Mai 2024, 11:05

Hi,

hast du das zwischenzeitlich gelöst? Ich stehe aktuell vor dem gleichen Problem. ich muß unsere Produktdaten auch als BMECAT Daten bereitstellen.
Zur Zeit stelle ich einen Teil der Daten bereits mit Django intern bereit damit unser Vertrieb (Innen- und Außendienst) die wichtigsten Daten schnell im Zugriff hat. Hiermit tu ich mich aber irgendwie sehr schwer und hab aktuell absolut null Plan wie ich das gelöst bekomme.

Danke

Bernd
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

Sorry, dass ich erst jetzt Antworte. Irgendwie hab ich nicht gesehen, dass hier eine Antwort kam.

Da mir auch nichts weiter eingefallen ist hab ich es jetzt komplett "zu Fuß" gelöst. ich habe nur bei ProductType die Zuweisung der jeweiligen ETIM-Klasse und die Zuweisung der jeweiligen Eigenschaften zu den einzelnen Feature- und Value-Codes mach ich jetzt von Hand in einer Methode.

Wartbarkeit lässt zu wünschen übrig... bzw. ist eher nicht vorhanden :roll: ... aber das Ergebnis sieht auf den ersten Blick richtig aus. In den betreffenden Produktgruppen gibt es allerdings auch nur recht wenig Änderungen. Und wenn, sind es im Moment nur Kleinigkeiten, wie das Hinzufügen eines weiteren Feature-Codes oder die Änderung eines Value-Types -> von "Number" zu "Range" also nichts, was in meinem Fall zu einem sehr großen Aufwand führen würde.

Ich habe gestern einem unserer Kunden einen Test mit einem Produkt je Gruppe zur Validierung zukommen lassen und warte jetzt mal was der sagt :?. Da wir aktuell noch kein Mitglied bei ETIM sind, haben wir leider keinen Zugriff auf deren Validator ;-)

Da wir erst wieder zum Jahreswechsel einen neuen Katalog rausgeben hab ich aber auch noch etwas Zeit um ggfs. Fehler zu beheben. :D

Achso, seit Februar hat ETIM wohl angefangen sich von BMECat zu verabschieden. Stattdessen soll es ein auf JSON basiertes Format ETIM xChange werden. Bis das aber produktiv eingesetzt wird, wirds wohl noch dauern.
djangobernd
User
Beiträge: 2
Registriert: Samstag 18. Mai 2024, 11:05

Hi,
kein Problem, hatte eh nicht viel Zeit und Ruhe um hier nochmal reinzugucken und mich um das Problem zu kümmer ;-).

So wie es aussieht werd ich das dann wohl ähnlich wie du machen. Programmieren ist eben nur ein Hobby und das hier soll uns auf der Arbeit das Leben etwas leichter machen. Einen externen möchte mein Chef für sowas nicht nehmen ;-). Hauptsache das Ergebnis passt nachher
paddie
User
Beiträge: 103
Registriert: Donnerstag 11. Oktober 2018, 18:09

Na dann. Viel Glück.

ich bin dann mal weg hier
Antworten