In diesem Artikel erkläre ich Ihnen, wie Djangos JSONField (ein JSON- und JSONB-Wrapper) zum Modellieren halbstrukturierter Daten verwendet werden kann und wie Sie darauf ein Schema erzwingen können Daten mithilfe von Pydantic – ein Ansatz, der für einen Python-Webentwickler selbstverständlich sein sollte.
Betrachten wir ein System, das Zahlungen verarbeitet, zum Beispiel die Transaktionstabelle. Es wird so aussehen:
from django.db import models class Transaction(models.Model): # Other relevant fields... payment_method = models.JSONField(default=dict, null=True, blank=True)
Unser Fokus liegt auf dem Feld payment_method. In einer realen Situation werden wir über bestehende Methoden zur Zahlungsabwicklung verfügen:
Kreditkarte
PayPal
Jetzt kaufen, später bezahlen
Kryptowährung
Unser System muss anpassbar sein, um die für jede Zahlungsmethode erforderlichen spezifischen Daten zu speichern und gleichzeitig eine konsistente und validierbare Struktur beizubehalten.
Wir verwenden Pydantic, um genaue Schemata für verschiedene Zahlungsmethoden zu definieren:
from typing import Optional from pydantic import BaseModel class CreditCardSchema(BaseModel): last_four: str expiry_month: int expiry_year: int cvv: str class PayPalSchema(BaseModel): email: EmailStr account_id: str class CryptoSchema(BaseModel): wallet_address: str network: Optional[str] = None class BillingAddressSchema(BaseModel): street: str city: str country: str postal_code: str state: Optional[str] = None class PaymentMethodSchema(BaseModel): credit_card: Optional[CreditCardSchema] = None paypal: Optional[PayPalSchema] = None crypto: Optional[CryptoSchema] = None billing_address: Optional[BillingAddressSchema] = None
Dieser Ansatz bietet mehrere wesentliche Vorteile:
Es kann jeweils nur eine Zahlungsmethode einen Wert ungleich Null haben.
Es ist einfach zu erweitern oder zu ändern, ohne dass komplexe Datenbankmigrationen erforderlich sind.
Gewährleistet die Datenintegrität auf Modellebene.
Um ein Schema für unser payment_method-Feld zu erzwingen, nutzen wir das Pydantic-Modell, um sicherzustellen, dass alle an das Feld übergebenen Daten mit dem von uns definierten Schema übereinstimmen.
from typing import Optional, Mapping, Type, NoReturn from pydantic import ValidationError as PydanticValidationError from django.core.exceptions import ValidationError def payment_method_validator(value: Optional[dict]) -> Optional[Type[BaseModel] | NoReturn]: if value is None: return if not isinstance(value, Mapping): raise TypeError("Payment method must be a dictionary") try: PaymentMethodSchema(**value) except (TypeError, PydanticValidationError) as e: raise ValidationError(f"Invalid payment method: {str(e)}")
Hier führen wir einige Überprüfungen durch, um sicherzustellen, dass die in unseren Validator eingegebenen Daten vom richtigen Typ sind, damit Pydantic sie validieren kann. Wir unternehmen nichts für Nullable-Werte und lösen einen Typfehler aus, wenn der übergebene Wert keine Unterklasse eines Mapping-Typs ist, wie etwa ein Dict oder ein OrderedDict.
Wenn wir eine Instanz des Pydantic-Modells mit dem Wert erstellen, den wir an den Konstruktor übergeben. Wenn die Struktur des Werts nicht zum definierten Schema für PaymentMethodSchema passt, löst Pydantic einen Validierungsfehler aus. Wenn wir beispielsweise einen ungültigen E-Mail-Wert für das E-Mail-Feld in PayPalSchema übergeben, löst Pydantic einen Validierungsfehler wie diesen aus:
ValidationError: 1 validation error for PaymentMethodSchema paypal.email value is not a valid email address: An email address must have an @-sign. [type=value_error, input_value='Check me out on LinkedIn: https://linkedin.com/in/daniel-c-olah', input_type=str]
Wir können diese Validierung auf zwei Arten erzwingen:
Benutzerdefinierte Validierungsmethode
Während des Speichervorgangs rufen wir die Validierungsfunktion auf, um sicherzustellen, dass die Zahlungsmethode dem erwarteten Schema entspricht.
from django.db import models class Transaction(models.Model): # ... other fields ... payment_method = models.JSONField(null=True, blank=True) def save(self, *args, **kwargs): # Override save method to include custom validation payment_method_validator(self.payment_method) super().save(*args, **kwargs)
Dieser Ansatz ist zwar effektiv, kann aber in Django umständlich und weniger idiomatisch werden. Wir könnten die Funktion sogar durch eine Klassenmethode ersetzen, die das Gleiche tut, um den Code sauberer zu machen.
Feldvalidatoren verwenden
Diese Methode nutzt den integrierten Feldvalidierungsmechanismus von Django:
from django.db import models class Transaction(models.Model): # Other relevant fields... payment_method = models.JSONField(default=dict, null=True, blank=True)
Dieser Ansatz vereint Flexibilität und Kontrolle über die im Feld payment_method gespeicherten Werte. Es ermöglicht uns, uns an zukünftige Änderungen der Anforderungen anzupassen, ohne die Integrität der vorhandenen Daten in diesem Bereich zu gefährden. Beispielsweise könnten wir ein Paystack-ID-Feld in unser Paystack-Schema aufnehmen. Diese Änderung würde nahtlos erfolgen, da wir uns nicht mit komplexen Datenbankmigrationen befassen müssten.
Wir könnten in Zukunft sogar problemlos eine pay_later-Methode hinzufügen. Auch die Feldtypen könnten sich ändern, und es gäbe keine Einschränkungen bei der Migration von Datenbankfeldern, wie sie bei der Migration von ganzzahligen Primärschlüsseln zu UUID-Primärschlüsseln auftreten. Sie können sich den vollständigen Code hier ansehen, um das Konzept vollständig zu verstehen.
Denormalisierung beinhaltet die bewusste Duplizierung von Daten über mehrere Dokumente oder Sammlungen hinweg, um die Leistung und Skalierbarkeit zu optimieren. Dieser Ansatz steht im Gegensatz zur strikten Normalisierung, die in herkömmlichen relationalen Datenbanken verwendet wird, und NoSQL-Datenbanken haben durch die Einführung flexibler, dokumentorientierter Speicherparadigmen maßgeblich zur Popularisierung der Denormalisierung beigetragen.
Stellen Sie sich ein E-Commerce-Szenario mit separaten Tabellen für Produkte und Bestellungen vor. Wenn ein Kunde eine Bestellung aufgibt, ist es wichtig, eine Momentaufnahme der im Warenkorb enthaltenen Produktdetails zu erfassen. Anstatt auf die aktuellen Produktdatensätze zu verweisen, die sich im Laufe der Zeit aufgrund von Aktualisierungen oder Löschungen ändern können, speichern wir die Produktinformationen direkt in der Bestellung. Dadurch wird sichergestellt, dass die Bestellung ihren ursprünglichen Kontext und ihre Integrität behält und den genauen Zustand der Produkte zum Zeitpunkt des Kaufs widerspiegelt. Die Denormalisierung spielt eine entscheidende Rolle beim Erreichen dieser Konsistenz.
Ein möglicher Ansatz könnte darin bestehen, einige Produktfelder in der Bestelltabelle zu duplizieren. Allerdings kann diese Methode zu Skalierbarkeitsproblemen führen und die Kohärenz des Bestellschemas gefährden. Eine effektivere Lösung besteht darin, die relevanten Produktfelder in einer JSON-Struktur zu serialisieren, sodass der Auftrag eine eigenständige Aufzeichnung der Produkte führen kann, ohne auf externe Abfragen angewiesen zu sein. Der folgende Code veranschaulicht diese Technik:
from typing import Optional from pydantic import BaseModel class CreditCardSchema(BaseModel): last_four: str expiry_month: int expiry_year: int cvv: str class PayPalSchema(BaseModel): email: EmailStr account_id: str class CryptoSchema(BaseModel): wallet_address: str network: Optional[str] = None class BillingAddressSchema(BaseModel): street: str city: str country: str postal_code: str state: Optional[str] = None class PaymentMethodSchema(BaseModel): credit_card: Optional[CreditCardSchema] = None paypal: Optional[PayPalSchema] = None crypto: Optional[CryptoSchema] = None billing_address: Optional[BillingAddressSchema] = None
Da wir die meisten Konzepte im vorherigen Abschnitt behandelt haben, sollten Sie beginnen, die Rolle von Pydantic bei all dem zu schätzen. Im obigen Beispiel verwenden wir Pydantic, um eine Liste von Produkten zu validieren, die mit einer Bestellung verknüpft sind. Durch die Definition eines Schemas für die Produktstruktur stellt Pydantic sicher, dass jedes zur Bestellung hinzugefügte Produkt die erwarteten Anforderungen erfüllt. Wenn die bereitgestellten Daten nicht dem Schema entsprechen, löst Pydantic einen Validierungsfehler aus.
Wir können JSONField-Schlüssel auf die gleiche Weise abfragen, wie wir Suchen in Django-Feldern durchführen. Hier sind ein paar Beispiele basierend auf unserem Anwendungsfall.
from typing import Optional, Mapping, Type, NoReturn from pydantic import ValidationError as PydanticValidationError from django.core.exceptions import ValidationError def payment_method_validator(value: Optional[dict]) -> Optional[Type[BaseModel] | NoReturn]: if value is None: return if not isinstance(value, Mapping): raise TypeError("Payment method must be a dictionary") try: PaymentMethodSchema(**value) except (TypeError, PydanticValidationError) as e: raise ValidationError(f"Invalid payment method: {str(e)}")
Sie können sich die Dokumentation ansehen, um mehr über das Filtern von JSON-Feldern zu erfahren.
Die Verwendung von JSON und JSONB in PostgreSQL bietet große Flexibilität für die Arbeit mit halbstrukturierten Daten in relationalen Datenbanken. Tools wie Pydantic und JSONField von Django helfen dabei, Regeln für die Datenstruktur durchzusetzen, wodurch es einfacher wird, die Genauigkeit aufrechtzuerhalten und sich an Änderungen anzupassen. Diese Flexibilität muss jedoch mit Bedacht genutzt werden. Ohne ordnungsgemäße Planung kann es zu einer langsameren Leistung oder unnötiger Komplexität kommen, wenn sich Ihre Daten im Laufe der Zeit ändern.
In Django werden Feldvalidatoren nur ausgelöst, wenn full_clean() explizit aufgerufen wird – dies geschieht normalerweise, wenn Django Forms verwendet oder is_valid() auf DRF-Serialisierern aufgerufen wird. Weitere Einzelheiten finden Sie in der Dokumentation zum Django-Validator.
Ein fortgeschrittenerer Ansatz zur Lösung dieses Problems wäre die Implementierung eines benutzerdefinierten Django-Felds, das Pydantic integriert, um sowohl die Serialisierung als auch die Validierung von JSON-Daten intern zu verwalten. Obwohl dies einen eigenen Artikel erfordert, können Sie vorerst Bibliotheken erkunden, die vorgefertigte Lösungen für dieses Problem bieten, zum Beispiel: django-pydantic-jsonfield
Das obige ist der detaillierte Inhalt vonSo erstellen Sie flexible Datenmodelle in Django mit JSONField und Pydantic. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!