Skip to content

Instantly share code, notes, and snippets.

@jerinisready
Last active March 25, 2021 05:56
Show Gist options
  • Save jerinisready/416fd959007d9a7342c9823585637121 to your computer and use it in GitHub Desktop.
Save jerinisready/416fd959007d9a7342c9823585637121 to your computer and use it in GitHub Desktop.
Django Oscar Product fetching (my personal) recommendation for API. This follows assumption that, Main screen Displays Parent / Standalone Products. Parent Products will have a dropdown from where, the card item can replace parent product with details of child product. This also devices into a logic that, Area of delivery is divided into differe…
from typing import Any, Union
from django.db.models import Q, QuerySet
from django.http import HttpRequest
from apps.api_set_v2.serializers.catalogue import ProductSimpleListSerializer
from apps.catalogue.models import Product
from apps.partner.models import StockRecord
def get_optimized_product_dict(
request: HttpRequest,
qs_filter: Q = None,
qs: Union[QuerySet, list] = None,
offset: int = None,
limit: int = None,
needs_stock: bool = True,
product_serializer_class: type = ProductSimpleListSerializer) -> dict:
assert qs is not None or qs_filter is not None, "Either one is required!"
"""
Zone can be explained as a model with a pointer to partner who delivers to a geographical region.
zone: = None
class Zone(models.Model):
name = models.CharField(max_length=128)
zone = models.PolygonField()
partner = models.ForeignKey('partner.Partner', related_name='zone', on_delete=models.CASCADE)
stock record -> partner -> zone
"""
zone: int = request.session.get('zone') # zone => Zone.pk
if qs is not None:
if not qs: return {}
product_set = qs
else:
product_set = Product.objects.filter(
qs_filter, is_public=True,
stockrecords__isnull=False
)
if offset and limit:
product_set = product_set[offset:limit]
elif limit:
product_set = product_set[:limit]
elif offset:
product_set = product_set[offset:]
sr_set = StockRecord.objects.filter(
product__parent__in=product_set,
product__structure__in=[Product.CHILD, Product.STANDALONE],
num_in_stock__gt=0 if needs_stock else -1,
).select_related(
'product', 'product__product_class', 'product__parent', 'product__parent__product_class'
).prefetch_related('product__images', 'product__parent__images')
if zone:
sr_set = sr_set.filter(partner__zone__id=zone)
product_data = {}
for sr in sr_set:
sr.product.selected_stock_record = sr
if sr.product.is_child:
if sr.product.parent not in product_data.keys():
product_data[sr.product.parent] = product_serializer_class(instance=sr.product.parent,
context={'request': request}).data
product_data[sr.product.parent]['variants'] = []
product_data[sr.product.parent]['variants'].append(
product_serializer_class(instance=sr.product, context={'request': request}).data)
elif sr.product.is_standalone: # parent or standalone
product_data[sr.product] = product_serializer_class(instance=sr.product,
context={'request': request}).data
product_data[sr.product]['variants'] = []
return product_data
from apps.api_set_v2.serializers.mixins import ProductPrimaryImageFieldMixin, ProductPriceFieldMixinLite
class ProductSimpleListSerializer(ProductPrimaryImageFieldMixin, ProductPriceFieldMixinLite,
serializers.ModelSerializer):
primary_image = serializers.SerializerMethodField()
price = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ('id', 'title', 'primary_image', 'price')
from apps.utils import cache_key
class empty:
""" faking request object just for the purpose of generating complete url """
def __init__(self, **kwargs):
for key in kwargs:
setattr(self, key, kwargs[key])
def build_absolute_uri(self, path):
return path
class ProductPrimaryImageFieldMixin(object):
""" Serializer mixin to facilitate fetching primary image for Product Serializers """
def get_primary_image(self, instance):
if instance.is_child:
return None # planning to deliver images only to parent image.
request = (self.context or {}).get('request', empty()) # noqa: mixin assured
req = request.build_absolute_uri
img = instance.primary_image()
img_mob = img['original'] if type(img) is dict else img.thumbnail_mobile_listing
return {
# 'web': req.build_absolute_uri(img_web),
'mobile': req(img_mob or image_not_found()),
}
class ProductAttributeFieldMixin(object):
attribute_values_filter = {}
def get_attributes(self, instance):
def _inner():
"""
Executing 5 Queries!
"""
attrs_value = instance.attribute_values.filter(
**self.attribute_values_filter
).annotate(
att_name=F('attribute__name'),
att_code=F('attribute__code'),
)
return [{ # saves model mapping and another 5 queries
'name': attr.att_name,
'value': attr.value_as_text,
'code': attr.att_code,
} for attr in attrs_value]
# cache.delete(cache_key.product_attribute__key(instance.id))
return cache_library(cache_key.product_attribute__key(instance.id), cb=_inner)
class SibblingProductAttributeFieldMixin(ProductAttributeFieldMixin):
# attribute_values_filter = {'attribute__is_varying': True}
pass
class ProductPriceFieldMixin(object):
def get_price(self, product):
if product.is_parent:
return
request = (self.context or {}).get('request')
purchase_info = get_purchase_info(product, request) # noqa
if purchase_info:
from oscarapi.serializers.product import AvailabilitySerializer
availability = AvailabilitySerializer(purchase_info.availability).data
else:
availability = {
"is_available_to_buy": False,
"message": "Unavailable"
}
return purchase_info_as_dict(purchase_info, **availability)
# cache.delete(cache_key.product_price_data__key(product.id))
# return cache_library(
# cache_key.product_price_data__key(product.id),
# cb=product._recalculate_price_cache
# )
class ProductPriceFieldMixinLite(object):
def get_price(self, product):
key = 'ProductPriceFieldMixinLite__{0}__{1}'
def _inner():
if product.is_parent:
return dummy_purchase_info_lite_as_dict(availability=True, availability_message='')
purchase_info = get_purchase_info(
product,
request=self.context['request'] # noqa: mixin ensures
)
addittional_informations = {
"availability": bool(purchase_info.availability.is_available_to_buy),
"availability_message": purchase_info.availability.short_message,
}
return purchase_info_lite_as_dict(purchase_info, **addittional_informations)
return _inner()
# cache.delete(cache_key.product_price_data__key(product.id))
# return cache_library(
# cache_key.product_price_data_lite__key(product.id),
# cb=product._recalculate_price_cache
# )
product_price_data__key = "product_price_data_key__{}".format
product_attribute__key = "product_attribute__key__{}".format
product_price_data_lite__key = "product_price_data_lite__key__{}".format
THUMBNAIL_KVSTORE = 'sorl.thumbnail.kvstores.redis_kvstore.KVStore'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment