Skip to content

Commit

Permalink
Fix many to many and don't break contrct for the create_object method
Browse files Browse the repository at this point in the history
  • Loading branch information
viggo-devries committed Nov 22, 2023
1 parent 7def2b1 commit 534a167
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 93 deletions.
55 changes: 16 additions & 39 deletions oscar_odin/mappings/_model_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,26 @@ def __new__(cls, name, bases, attrs):
mapping_type.many_to_one_fields = many_to_one_fields = []
mapping_type.many_to_many_fields = many_to_many_fields = []
mapping_type.foreign_key_fields = foreign_key_fields = []
meta = getmeta(mapping_type.to_obj)

meta = getmeta(mapping_type.to_obj)

for relation in meta.related_objects:
if relation.many_to_many:
one_to_one_fields.append(
(relation.related_name, relation.related_model._meta.db_table)
many_to_many_fields.append(
(relation.related_name, relation.field.attname)
)
elif relation.many_to_one:
many_to_one_fields.append(
(relation.related_name, relation.related_model._meta.db_table)
(relation.related_name, relation.field.attname)
)
elif relation.one_to_many:
many_to_many_fields.append(
(relation.related_name, relation.related_model._meta.db_table)
(relation.related_name, relation.field.attname)
)

for field in meta.fields:
if isinstance(field, ForeignKey):
print(field, field.name, field.related_model)
foreign_key_fields.append(
("%s_id" % field.name, field.related_model._meta.db_table)
)
foreign_key_fields.append(("%s_id" % field.name, field.attname))

return mapping_type

Expand All @@ -60,49 +57,29 @@ class ModelMapping(MappingBase, metaclass=ModelMappingMeta):

def create_object(self, **field_values):
"""Create a new product model."""

print("onetoone", self.one_to_one_fields)
print("manytomany", self.many_to_one_fields)
print("manytoone", self.many_to_many_fields)
print("foreignkey", self.foreign_key_fields)
print("fieldvalues", field_values)

one_to_one_items = [

self.context["one_to_one_items"] = one_to_one_items = [
(name, table_name, field_values.pop(name))
for name, table_name in self.one_to_one_fields
if name in field_values
]
many_to_one_items = [
self.context["many_to_one_items"] = many_to_one_items = [
(name, table_name, field_values.pop(name))
for name, table_name in self.many_to_one_fields
if name in field_values
]
many_to_many_items = [
self.context["many_to_many_items"] = many_to_many_items = [
(name, table_name, field_values.pop(name))
for name, table_name in self.many_to_many_fields
if name in field_values
]
foreign_key_items = [
self.context["foreign_key_items"] = foreign_key_items = [
(name, table_name, field_values.pop(name))
for name, table_name in self.foreign_key_fields
if name in field_values
]

obj = super().create_object(**field_values)

# for name, table_name, value in one_to_one_items:
# if value:
# self.context.setdefault(table_name, []).append(value)
# # getattr(obj, name).set(value)
#
# for name, table_name, value in many_to_one_items:
# if value:
# self.context.setdefault(table_name, []).extend(value)
# # getattr(obj, name).sadd(*value)
#
# for name, table_name, value in many_to_many_items:
# if value:
# self.context.setdefault(table_name, []).extend(value)
# # getattr(obj, name).add(*value)

return obj, one_to_one_items, many_to_one_items, many_to_many_items, foreign_key_items
setattr(obj, "odin_context", self.context)

return obj
40 changes: 24 additions & 16 deletions oscar_odin/mappings/catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from oscar.apps.partner.strategy import Default as DefaultStrategy
from oscar.core.loading import get_class, get_model

from datetime import datetime

from .. import resources
from ..resources.catalogue import Structure
from ._common import map_queryset
Expand Down Expand Up @@ -57,6 +59,13 @@ def original(self, value: str) -> str:
# TODO convert into a form that can be accepted by a model
return value

@odin.map_field
def date_created(self, value: datetime) -> datetime:
if value:
return value

return datetime.now()


class CategoryToResource(odin.Mapping):
"""Map from a category model to a resource."""
Expand Down Expand Up @@ -213,11 +222,10 @@ class ProductToModel(ModelMapping):
from_obj = resources.catalogue.Product
to_obj = ProductModel

@odin.map_list_field(from_field="images", to_field="images")
@odin.map_list_field
def images(self, values) -> List[ProductImageModel]:
"""Map related image."""
print("IMAGES VALUES", values)
return list(ProductImageToModel.apply(values, context=self.context))
"""Map related image. We save these later in bulk"""
return ProductImageToModel.apply(values)

@odin.map_list_field
def children(self, values) -> List[ProductModel]:
Expand Down Expand Up @@ -317,6 +325,7 @@ def product_to_model(
product: resources.catalogue.Product,
) -> ProductModel:
"""Map a product resource to a model."""

return ProductToModel.apply(product)


Expand All @@ -328,21 +337,20 @@ def product_to_db(
The method will handle the nested database saves required to store the entire resource
within a single transaction.
"""
model, one_to_one_items, many_to_one_items, many_to_many_items, foreign_key_items = product_to_model(product)
obj = product_to_model(product)

print(model)
print("one_to_one_items", one_to_one_items)
print("many_to_one_items", many_to_one_items)
print("many_to_many_items", many_to_many_items)
print("foreign_key_items", foreign_key_items)
context = obj.odin_context

with transaction.atomic():
for fk_name, fk_db_table, fk_instance in foreign_key_items:
fk_instance.full_clean()
for fk_name, fk_attname, fk_instance in context.get("foreign_key_items", []):
fk_instance.save()
setattr(model, fk_name, fk_instance.pk)
setattr(obj, fk_name, fk_instance.pk)

obj.save()

model.full_clean()
model.save()
for mtm_name, mtm_attname, instances in context.get("many_to_many_items", []):
for mtm_instance in instances:
setattr(mtm_instance, mtm_attname, obj.pk)
mtm_instance.save()

return model
return obj
1 change: 0 additions & 1 deletion oscar_odin/resources/catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class Meta:
verbose_name_plural = "Product images"

id: int
product_id: int
original: Url = odin.Options(validators=[])
caption: str = odin.Options(empty=True)
display_order: int = odin.Options(
Expand Down
22 changes: 11 additions & 11 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.8"
odin = "^2.9"
django-oscar = "^3.2.2"
django-oscar = "^3.2"

[tool.poetry.dev-dependencies]
coverage = "^7.3"
Expand Down
8 changes: 0 additions & 8 deletions tests/mappings/test_catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,3 @@ def test_product_to_resource__where_is_a_parent_product_include_children(self):
self.assertEqual(product.title, actual.title)
self.assertIsNotNone(actual.children)
self.assertEqual(3, len(actual.children))

def test_product_to_db__basic_model_to_resource(self):
product = Product.objects.first()

resource = catalogue.product_to_resource(product)
actual = catalogue.product_to_db(resource)

self.assertEqual(resource.title, actual.title)
36 changes: 19 additions & 17 deletions tests/reverse/test_catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,50 @@
from oscar.core.loading import get_model

from oscar_odin.mappings.catalogue import product_to_db
from oscar_odin.resources.catalogue import Product as ProductResource, Image as ImageResource, ProductClass as ProductClassResource
from oscar_odin.resources.catalogue import (
Product as ProductResource,
Image as ImageResource,
ProductClass as ProductClassResource,
)

Product = get_model("catalogue", "Product")


class ProductReverseTest(TestCase):
def test_simple_product(self):
def test_create_simple_product(self):
product_resource = ProductResource(
upc="1234323",
title="asdf1",
slug="asdf-asdfasdf",
description="description",
structure=Product.STANDALONE,
is_discountable=True,
product_class=ProductClassResource(name="Klaas", slug="klaas", requires_shipping=True, track_stock=True)
product_class=ProductClassResource(
name="Klaas", slug="klaas", requires_shipping=True, track_stock=True
),
)

prd = product_to_db(product_resource)

self.assertEquals(prd.title, "asdf1")
print(Product.objects.get(upc="1234323"))
self.assertTrue(Product.objects.filter(upc="1234323").exists())


def test_related_fields(self):
def test_create_product_with_related_fields(self):
product_resource = ProductResource(
upc="1234323-2",
title="asdf2",
slug="asdf-asdfasdf2",
description="description",
structure=Product.STANDALONE,
is_discountable=True,
product_class=ProductClassResource(name="Klaas", slug="klaas", requires_shipping=True, track_stock=True),
images=[
ImageResource(
caption="gekke caption",
display_order=0
)
]
product_class=ProductClassResource(
name="Klaas", slug="klaas", requires_shipping=True, track_stock=True
),
images=[ImageResource(caption="gekke caption", display_order=0), ImageResource(caption="gekke caption 2", display_order=1)],
)

prd = product_to_db(product_resource)

self.assertEquals(prd.title, "asdf2")

print(prd)

self.assertEquals(prd.title, "asdf2")
self.assertEquals(prd.images.count(), 2)

0 comments on commit 534a167

Please sign in to comment.