Skip to content

Commit

Permalink
fixup! Simplify from_django by relying in Pydantic. Add multiple leve…
Browse files Browse the repository at this point in the history
…l relationship support.
  • Loading branch information
phbernardes committed Mar 25, 2022
1 parent 2867a6c commit 943c1bd
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -131,4 +131,4 @@ dmypy.json
.idea
.DS_Store
*.sublime-workspace
.vscode
.vscode
110 changes: 109 additions & 1 deletion README.md
Expand Up @@ -114,4 +114,112 @@ print(user_schema.json(indent=2))
}
```

See https://pydantic-docs.helpmanual.io/usage/exporting_models/ for more.
See https://pydantic-docs.helpmanual.io/usage/exporting_models/ for more.

### Use multiple level relations

Djantic supports multiple level relations. Given the following models:

```python
class OrderUser(models.Model):
email = models.EmailField(unique=True)


class OrderUserProfile(models.Model):
address = models.CharField(max_length=255)
user = models.OneToOneField(OrderUser, on_delete=models.CASCADE, related_name='profile')


class Order(models.Model):
total_price = models.DecimalField(max_digits=8, decimal_places=5, default=0)
user = models.ForeignKey(
OrderUser, on_delete=models.CASCADE, related_name="orders"
)


class OrderItem(models.Model):
price = models.DecimalField(max_digits=8, decimal_places=5, default=0)
quantity = models.IntegerField(default=0)
order = models.ForeignKey(
Order, on_delete=models.CASCADE, related_name="items"
)


class OrderItemDetail(models.Model):
name = models.CharField(max_length=30)
order_item = models.ForeignKey(
OrderItem, on_delete=models.CASCADE, related_name="details"
)
```

Inverse ForeignKey relation (or M2M relation) type is a list of the Schema of this related object.

OneToOne relation type is the Schema of this related object.

```python
class OrderItemDetailSchema(ModelSchema):
class Config:
model = OrderItemDetail

class OrderItemSchema(ModelSchema):
details: List[OrderItemDetailSchema]

class Config:
model = OrderItem

class OrderSchema(ModelSchema):
items: List[OrderItemSchema]

class Config:
model = Order

class OrderUserProfileSchema(ModelSchema):
class Config:
model = OrderUserProfile

class OrderUserSchema(ModelSchema):
orders: List[OrderSchema]
profile: OrderUserProfileSchema
```

**Calling:**

```python
user = OrderUser.objects.first()
print(OrderUserSchema.from_orm(user).json(ident=4))
```

**Output:**
```json
{
"profile": {
"id": 1,
"address": "",
"user": 1
},
"orders": [
{
"items": [
{
"details": [
{
"id": 1,
"name": "",
"order_item": 1
}
],
"id": 1,
"price": 0.0,
"quantity": 0,
"order": 1
}
],
"id": 1,
"total_price": 0.0,
"user": 1
}
],
"id": 1,
"email": ""
}
```
8 changes: 7 additions & 1 deletion djantic/main.py
Expand Up @@ -43,7 +43,13 @@ def __new__(
and base == ModelSchema
):

config = namespace["Config"]
try:
config = namespace["Config"]
except KeyError as exc:
raise ConfigError(
f"{exc} (Is `Config` class defined?)"
)

include = getattr(config, "include", None)
exclude = getattr(config, "exclude", None)

Expand Down
7 changes: 7 additions & 0 deletions tests/test_main.py
Expand Up @@ -11,6 +11,13 @@ def test_config_errors():
Test the model config error exceptions.
"""

with pytest.raises(
ConfigError, match="(Is `Config` class defined?)"
):

class InvalidModelErrorSchema(ModelSchema):
pass

with pytest.raises(
ConfigError, match="(Is `Config.model` a valid Django model class?)"
):
Expand Down
44 changes: 43 additions & 1 deletion tests/test_multiple_level_relations.py
Expand Up @@ -3,7 +3,7 @@
from typing import List

import pytest
from testapp.order import Order, OrderItem, OrderItemDetail, OrderUser, OrderUserFactory
from testapp.order import Order, OrderItem, OrderItemDetail, OrderUser, OrderUserFactory, OrderUserProfile

from djantic import ModelSchema

Expand All @@ -26,8 +26,14 @@ class OrderSchema(ModelSchema):
class Config:
model = Order

class OrderUserProfileSchema(ModelSchema):

class Config:
model = OrderUserProfile

class OrderUserSchema(ModelSchema):
orders: List[OrderSchema]
profile: OrderUserProfileSchema

class Config:
model = OrderUser
Expand All @@ -39,6 +45,11 @@ class Config:
'first_name': '',
'last_name': None,
'email': '',
'profile': {
'id': 1,
'address': '',
'user': 1
},
'orders': [
{
'id': 1,
Expand Down Expand Up @@ -152,6 +163,9 @@ class Config:
"description": "OrderUser(id, first_name, last_name, email)",
"type": "object",
"properties": {
"profile": {
"$ref": "#/definitions/OrderUserProfileSchema"
},
"orders": {
"title": "Orders",
"type": "array",
Expand Down Expand Up @@ -184,11 +198,39 @@ class Config:
}
},
"required": [
"profile",
"orders",
"first_name",
"email"
],
"definitions": {
"OrderUserProfileSchema": {
"title": "OrderUserProfileSchema",
"description": "OrderUserProfile(id, address, user)",
"type": "object",
"properties": {
"id": {
"title": "Id",
"description": "id",
"type": "integer"
},
"address": {
"title": "Address",
"description": "address",
"maxLength": 255,
"type": "string"
},
"user": {
"title": "User",
"description": "id",
"type": "integer"
}
},
"required": [
"address",
"user"
]
},
"OrderItemDetailSchema": {
"title": "OrderItemDetailSchema",
"description": "OrderItemDetail(id, name, value, quantity, order_item)",
Expand Down
17 changes: 17 additions & 0 deletions tests/testapp/order.py
Expand Up @@ -8,6 +8,12 @@ class OrderUser(models.Model):
last_name = models.CharField(max_length=50, null=True, blank=True)
email = models.EmailField(unique=True)


class OrderUserProfile(models.Model):
address = models.CharField(max_length=255)
user = models.OneToOneField(OrderUser, on_delete=models.CASCADE, related_name='profile')


class Order(models.Model):
total_price = models.DecimalField(max_digits=8, decimal_places=5, default=0)
shipping_address = models.CharField(max_length=255)
Expand Down Expand Up @@ -81,6 +87,12 @@ def items(self, create, items, **kwargs):
for i in range(0, 2)]


class OrderUserProfileFactory(DjangoModelFactory):

class Meta:
model = OrderUserProfile


class OrderUserFactory(DjangoModelFactory):

class Meta:
Expand All @@ -90,3 +102,8 @@ class Meta:
def orders(self, create, orders, **kwargs):
if orders is None:
orders = [OrderFactory.create(user=self, **kwargs) for i in range(0, 2)]

@factory.post_generation
def profile(self, create, profile, **kwargs):
if profile is None:
profile = OrderUserProfileFactory.create(user=self, **kwargs)

0 comments on commit 943c1bd

Please sign in to comment.