def manufacturer_changed_listener(sender, **kwargs): # filtered lists of products assigned to manufacturer used at manufacturer page cache.delete("%s-manufacturer-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, sender.slug)) # list of all manufacturer products cache.delete("%s-manufacturer-all-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, sender.pk)) # if manufacturer assignment was changed then product navigation might be different too invalidate_cache_group_id('product_navigation')
def add_property(request, product_id): """Adds a new property to the product with given product id. """ product = Product.objects.get(pk=product_id) property_form = PropertyForm(data=request.POST) if property_form.is_valid(): property = property_form.save(commit=False) property.title = property.name property.type = PROPERTY_SELECT_FIELD property.local = True # it doesn't make sense to filter by local properties as every local # property has an own id. Maybe we can do this with an grouping id or # something like that property.filterable = False property.save() product_property = ProductsPropertiesRelation(product=product, property=property, position=999) product_property.save() # Refresh positions for i, product_property in enumerate(product.productsproperties.all()): product_property.position = i product_property.save() product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id("properties-%s" % pid) html = [["#variants", manage_variants(request, product_id, as_string=True)]] result = json.dumps({"html": html, "message": _(u"Property has been added.")}, cls=LazyEncoder) return HttpResponse(result, content_type="application/json")
def change_property_position(request): """Changes property position based on parameters passed by request body. """ product_id = request.GET.get("product_id") property_id = int(request.GET.get("property_id")) direction = request.GET.get("direction") try: product_property = ProductsPropertiesRelation.objects.get(product=product_id, property=property_id) except ObjectDoesNotExist: pass else: if direction == "up": product_property.position -= 3 else: product_property.position += 3 product_property.save() _refresh_property_positions(product_id) pid = Product.objects.get(pk=product_id).get_parent().pk invalidate_cache_group_id("properties-%s" % pid) html = (("#variants", manage_variants(request, product_id, as_string=True)),) result = json.dumps({"html": html}, cls=LazyEncoder) return HttpResponse(result, content_type="application/json")
def change_property_position(request): """Changes property position based on parameters passed by request body. """ product_id = request.GET.get("product_id") property_id = int(request.GET.get("property_id")) direction = request.GET.get("direction") try: product_property = ProductsPropertiesRelation.objects.get( product=product_id, property=property_id) except ObjectDoesNotExist: pass else: if direction == "up": product_property.position -= 3 else: product_property.position += 3 product_property.save() _refresh_property_positions(product_id) pid = Product.objects.get(pk=product_id).get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = (("#variants", manage_variants(request, product_id, as_string=True)), ) result = json.dumps({ "html": html, }, cls=LazyEncoder) return HttpResponse(result, content_type='application/json')
def delete_property(request, product_id, property_id): """Deletes property with passed property id. """ try: property = Property.objects.get(pk=property_id) product = Product.objects.get(pk=product_id) except ObjectDoesNotExist: pass else: property.delete() product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = (("#variants", manage_variants(request, product_id, as_string=True)), ) result = json.dumps( { "html": html, "message": _(u"Property has been deleted."), "close-dialog": True, }, cls=LazyEncoder) return HttpResponse(result, content_type='application/json')
def update_product_cache(instance): # If the instance is a product with variant or a variant we have to # delete also the parent and all other variants if instance.is_variant(): parent = instance.parent else: parent = instance # if product was changed then we have to clear all product_navigation caches invalidate_cache_group_id('product_navigation') invalidate_cache_group_id('properties-%s' % parent.id) delete_cache("%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) delete_cache("%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.slug)) delete_cache("%s-product-images-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) delete_cache("%s-related-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) delete_cache("%s-product-categories-%s-False" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) delete_cache("%s-product-categories-%s-True" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) delete_cache("%s-default-variant-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) if parent.manufacturer: delete_cache( "%s-manufacturer-all-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.manufacturer.pk)) delete_cache( "%s-manufacturer-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.manufacturer.slug)) try: c = cache.get("%s-shipping-delivery-time" % settings.CACHE_MIDDLEWARE_KEY_PREFIX) del c["%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.slug)] cache.set( "%s-shipping-delivery-time" % settings.CACHE_MIDDLEWARE_KEY_PREFIX, c) except (KeyError, TypeError): pass for variant in parent.get_variants(): delete_cache("%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) delete_cache("%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.slug)) delete_cache("%s-product-images-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) delete_cache("%s-related-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) delete_cache("%s-product-categories-%s-False" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) delete_cache("%s-product-categories-%s-True" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) delete_cache("%s-product-shipping-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.slug))
def manufacturer_changed_listener(sender, **kwargs): # filtered lists of products assigned to manufacturer used at manufacturer page delete_cache("%s-manufacturer-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, sender.slug)) # list of all manufacturer products delete_cache("%s-manufacturer-all-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, sender.pk)) # if manufacturer assignment was changed then product navigation might be different too invalidate_cache_group_id('product_navigation')
def update_product_cache(instance): # If the instance is a product with variant or a variant we have to # delete also the parent and all other variants if instance.is_variant(): parent = instance.parent else: parent = instance # if product was changed then we have to clear all product_navigation caches invalidate_cache_group_id('product_navigation') invalidate_cache_group_id('properties-%s' % parent.id) cache_key = "%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id) cache.delete(cache_key) cache_key_hash = hashlib.md5(cache_key).hexdigest() cache.delete(cache_key_hash) cache_key_slug = "%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.slug) cache_key_slug_hash = hashlib.md5(cache_key_slug).hexdigest() cache.delete(cache_key_slug) cache.delete(cache_key_slug_hash) cache.delete("%s-product-images-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) cache.delete("%s-related-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) cache.delete("%s-product-categories-%s-False" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) cache.delete("%s-product-categories-%s-True" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) cache.delete("%s-default-variant-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.id)) if parent.manufacturer: cache.delete("%s-manufacturer-all-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.manufacturer.pk)) cache.delete("%s-manufacturer-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.manufacturer.slug)) try: c = cache.get("%s-shipping-delivery-time" % settings.CACHE_MIDDLEWARE_KEY_PREFIX) del c["%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.slug)] cache.set("%s-shipping-delivery-time" % settings.CACHE_MIDDLEWARE_KEY_PREFIX, c) except (KeyError, TypeError): pass for variant in parent.get_variants(): cache_key = "%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id) cache.delete(cache_key) cache_key_hash = hashlib.md5(cache_key).hexdigest() cache.delete(cache_key_hash) cache.delete("%s-product-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, parent.slug)) cache.delete("%s-product-images-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) cache.delete("%s-related-products-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) cache.delete("%s-product-categories-%s-False" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) cache.delete("%s-product-categories-%s-True" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.id)) cache.delete("%s-product-shipping-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, variant.slug))
def add_property_option(request, product_id): """Adds a new option to the property with given property id. NOTE: The reason why to pass the product id here is to be able to redirect to the product. Properties can belong to more than one product. TODO: Do this with REFERER """ property_option_form = PropertyOptionForm(data=request.POST) if property_option_form.is_valid(): names = request.POST.get("name").split(",") position = 999 property_id = request.POST.get("property_id") for name in names: property_option = PropertyOption(name=name) property_option.property_id = property_id property_option.position = position property_option.save() position += 1 # Refresh positions for i, option in enumerate( PropertyOption.objects.filter(property=property_id)): option.position = i option.save() message = _(u'Option has been added.') else: message = _(u'Invalid data. Correct it and try again.') product = Product.objects.get(pk=product_id) product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = [[ "#variants", manage_variants(request, product_id, as_string=True) ]] result = json.dumps({ "html": html, "message": message, }, cls=LazyEncoder) return HttpResponse(result, content_type='application/json')
def add_property(request, product_id): """Adds a new property to the product with given product id. """ product = Product.objects.get(pk=product_id) property_form = PropertyForm(data=request.POST) if property_form.is_valid(): property = property_form.save(commit=False) property.title = property.name property.type = PROPERTY_SELECT_FIELD property.local = True # it doesn't make sense to filter by local properties as every local # property has an own id. Maybe we can do this with an grouping id or # something like that property.filterable = False property.save() product_property = ProductsPropertiesRelation(product=product, property=property, position=999) product_property.save() # Refresh positions for i, product_property in enumerate(product.productsproperties.all()): product_property.position = i product_property.save() product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = [[ "#variants", manage_variants(request, product_id, as_string=True) ]] result = simplejson.dumps( { "html": html, "message": _(u"Property has been added."), }, cls=LazyEncoder) return HttpResponse(result)
def add_property_option(request, product_id): """Adds a new option to the property with given property id. NOTE: The reason why to pass the product id here is to be able to redirect to the product. Properties can belong to more than one product. TODO: Do this with REFERER """ property_option_form = PropertyOptionForm(data=request.POST) if property_option_form.is_valid(): names = request.POST.get("name").split(",") position = 999 property_id = request.POST.get("property_id") for name in names: property_option = PropertyOption(name=name) property_option.property_id = property_id property_option.position = position property_option.save() position += 1 # Refresh positions for i, option in enumerate(PropertyOption.objects.filter(property=property_id)): option.position = i option.save() message = _(u'Option has been added.') else: message = _(u'Invalid data. Correct it and try again.') product = Product.objects.get(pk=product_id) product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = [["#variants", manage_variants(request, product_id, as_string=True)]] result = json.dumps({ "html": html, "message": message, }, cls=LazyEncoder) return HttpResponse(result, mimetype='application/json')
def delete_property_option(request, product_id, option_id): """Deletes property option with passed option id. """ try: property_option = PropertyOption.objects.get(pk=option_id) product = Product.objects.get(pk=product_id) except ObjectDoesNotExist: pass else: property_option.delete() product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id("properties-%s" % pid) html = (("#variants", manage_variants(request, product_id, as_string=True)),) result = json.dumps( {"html": html, "message": _(u"Property has been deleted."), "close-dialog": True}, cls=LazyEncoder ) return HttpResponse(result, content_type="application/json")
def delete_property(request, product_id, property_id): """Deletes property with passed property id. """ try: property = Property.objects.get(pk=property_id) product = Product.objects.get(pk=product_id) except ObjectDoesNotExist: pass else: property.delete() product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = (("#variants", manage_variants(request, product_id, as_string=True)),) result = simplejson.dumps({ "html": html, "message": _(u"Property has been deleted."), "close-dialog": True, }, cls=LazyEncoder) return HttpResponse(result)
def update_variants(request, product_id): """Updates/Deletes variants with passed ids (via request body) dependent on given action (also via request body). """ product = lfs_get_object_or_404(Product, pk=product_id) message = '' action = request.POST.get("action") if action == "delete": message = _(u"Variants have been deleted.") for key in request.POST.keys(): if key.startswith("delete-"): try: prop_id = key.split("-")[1] variant = Product.objects.get(pk=prop_id) except (IndexError, ObjectDoesNotExist): continue else: if product.default_variant == variant: product.default_variant = None product.save() variant.delete() elif action == "update": # TODO: change all of these to formsets or something that will allow for error hangling/messages message = _(u"Variants have been saved.") for key, value in request.POST.items(): if key.startswith("variant-"): prop_id = key.split("-")[1] try: variant = Product.objects.get(pk=prop_id) except ObjectDoesNotExist: continue else: for name in ("sku", "price"): value = request.POST.get("%s-%s" % (name, prop_id)) if value != "": if name == 'price': value = float(value) setattr(variant, name, value) # handle slug - ensure it is unique slug = request.POST.get("slug-%s" % prop_id) if variant.slug != slug: counter = 1 new_slug = slug while Product.objects.exclude(pk=variant.pk).filter( slug=new_slug).exists(): new_slug = '%s-%s' % (slug[:( 79 - len(str(counter)))], counter) counter += 1 variant.slug = new_slug # name variant.name = request.POST.get("name-%s" % prop_id) # active active = request.POST.get("active-%s" % prop_id) if active: variant.active = True else: variant.active = False # active attributes for name in ("active_price", "active_sku", "active_name"): value = request.POST.get("%s-%s" % (name, prop_id)) if value: setattr(variant, name, True) else: setattr(variant, name, False) # position position = request.POST.get("position-%s" % prop_id) try: variant.variant_position = int(position) except ValueError: variant.variant_position = 10 # default variant try: product.default_variant_id = int( request.POST.get("default_variant")) except TypeError: pass else: product.save() variant.save() elif key.startswith("property"): # properties are marshalled as: property-variant_id|property_id temp = key.split("-")[1] variant_id, property_id = temp.split("|") try: variant = Product.objects.get(pk=variant_id) except Product.DoesNotExist: continue prop = Property.objects.get(pk=property_id) ppv = None ppv_filterable = None try: ppv = ProductPropertyValue.objects.get( product=variant, property_id=property_id, type=PROPERTY_VALUE_TYPE_VARIANT) except ProductPropertyValue.DoesNotExist: pass if prop.filterable: try: ppv_filterable = ProductPropertyValue.objects.get( product=variant, property_id=property_id, type=PROPERTY_VALUE_TYPE_FILTER) except ProductPropertyValue.DoesNotExist: pass if value != '': if not ppv: ppv = ProductPropertyValue.objects.create( product=variant, property_id=property_id, type=PROPERTY_VALUE_TYPE_VARIANT, value=value) else: ppv.value = value ppv.save() if prop.filterable: if not ppv_filterable: ProductPropertyValue.objects.create( product=variant, property_id=property_id, value=value, type=PROPERTY_VALUE_TYPE_FILTER) else: ppv_filterable.value = value ppv_filterable.save() elif ppv: ppv.delete() if ppv_filterable: ppv_filterable.delete() # Refresh variant positions for i, variant in enumerate(product.variants.order_by("variant_position")): variant.variant_position = (i + 1) * 10 variant.save() # Send a signal to update cache product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = ( ("#variants", manage_variants(request, product_id, as_string=True)), ("#selectable-products-inline", _selectable_products_inline(request, product)), ) result = simplejson.dumps({ "html": html, "message": message, }, cls=LazyEncoder) return HttpResponse(result)
def update_variants(request, product_id): """Updates/Deletes variants with passed ids (via request body) dependent on given action (also via request body). """ product = lfs_get_object_or_404(Product, pk=product_id) message = "" action = request.POST.get("action") if action == "delete": message = _(u"Variants have been deleted.") for key in request.POST.keys(): if key.startswith("delete-"): try: prop_id = key.split("-")[1] variant = Product.objects.get(pk=prop_id) except (IndexError, ObjectDoesNotExist): continue else: if product.default_variant == variant: product.default_variant = None product.save() variant.delete() elif action == "update": # TODO: change all of these to formsets or something that will allow for error hangling/messages message = _(u"Variants have been saved.") for key, value in request.POST.items(): if key.startswith("variant-"): prop_id = key.split("-")[1] try: variant = Product.objects.get(pk=prop_id) except ObjectDoesNotExist: continue else: for name in ("sku", "price"): value = request.POST.get("%s-%s" % (name, prop_id)) if value != "": if name == "price": value = float(value) setattr(variant, name, value) # handle slug - ensure it is unique slug = request.POST.get("slug-%s" % prop_id) if variant.slug != slug: counter = 1 new_slug = slug[:80] while Product.objects.exclude(pk=variant.pk).filter(slug=new_slug).exists(): new_slug = "%s-%s" % (slug[: (79 - len(str(counter)))], counter) counter += 1 variant.slug = new_slug # name variant.name = request.POST.get("name-%s" % prop_id) # active active = request.POST.get("active-%s" % prop_id) if active: variant.active = True else: variant.active = False # active attributes for name in ("active_price", "active_sku", "active_name"): value = request.POST.get("%s-%s" % (name, prop_id)) if value: setattr(variant, name, True) else: setattr(variant, name, False) # position position = request.POST.get("position-%s" % prop_id) try: variant.variant_position = int(position) except ValueError: variant.variant_position = 10 # default variant try: product.default_variant_id = int(request.POST.get("default_variant")) except TypeError: pass else: product.save() variant.save() elif key.startswith("property"): # properties are marshalled as: property-variant_id|property_group_id|property_id temp = key.split("-")[1] variant_id, property_group_id, property_id = temp.split("|") if property_group_id == "0": # local properties are not bound to property groups property_group_id = None try: variant = Product.objects.get(pk=variant_id) except Product.DoesNotExist: continue prop = Property.objects.get(pk=property_id) ppv = None try: ppv = ProductPropertyValue.objects.get( product=variant, property_id=property_id, property_group_id=property_group_id, type=PROPERTY_VALUE_TYPE_VARIANT, ) except ProductPropertyValue.DoesNotExist: pass if prop.filterable: # it is possible that multiple values are selected for filter ppv_filterables = ProductPropertyValue.objects.filter( product=variant, property_group_id=property_group_id, property_id=property_id, type=PROPERTY_VALUE_TYPE_FILTER, ) if value != "": is_changed = True if not ppv: ppv = ProductPropertyValue.objects.create( product=variant, property_group_id=property_group_id, property_id=property_id, type=PROPERTY_VALUE_TYPE_VARIANT, value=value, ) else: is_changed = ppv.value != value ppv.value = value ppv.save() if prop.filterable and is_changed: ppv_filterables.delete() ProductPropertyValue.objects.create( product=variant, property_group_id=property_group_id, property_id=property_id, value=value, type=PROPERTY_VALUE_TYPE_FILTER, ) elif ppv: ppv.delete() ppv_filterables.delete() # Refresh variant positions for i, variant in enumerate(product.variants.order_by("variant_position")): variant.variant_position = (i + 1) * 10 variant.save() # Send a signal to update cache product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id("properties-%s" % pid) html = ( ("#variants", manage_variants(request, product_id, as_string=True)), ("#selectable-products-inline", _selectable_products_inline(request, product)), ) result = json.dumps({"html": html, "message": message}, cls=LazyEncoder) return HttpResponse(result, content_type="application/json")
def update_variants(request, product_id): """Updates/Deletes variants with passed ids (via request body) dependent on given action (also via request body). """ product = lfs_get_object_or_404(Product, pk=product_id) message = '' action = request.POST.get("action") if action == "delete": message = _(u"Variants have been deleted.") for key in request.POST.keys(): if key.startswith("delete-"): try: id = key.split("-")[1] variant = Product.objects.get(pk=id) except (IndexError, ObjectDoesNotExist): continue else: if product.default_variant == variant: product.default_variant = None product.save() variant.delete() elif action == "update": message = _(u"Variants have been saved.") for key, value in request.POST.items(): if key.startswith("variant-"): id = key.split("-")[1] try: variant = Product.objects.get(pk=id) except ObjectDoesNotExist: continue else: for name in ("slug", "sku", "price"): value = request.POST.get("%s-%s" % (name, id)) if value != "": if name == 'price': value = float(value) setattr(variant, name, value) # name variant.name = request.POST.get("name-%s" % id) # active active = request.POST.get("active-%s" % id) if active: variant.active = True else: variant.active = False # active attributes for name in ("active_price", "active_sku", "active_name"): value = request.POST.get("%s-%s" % (name, id)) if value: setattr(variant, name, True) else: setattr(variant, name, False) # position position = request.POST.get("position-%s" % id) try: variant.variant_position = int(position) except ValueError: variant.variant_position = 10 # default variant try: product.default_variant_id = int(request.POST.get("default_variant")) except TypeError: pass else: product.save() variant.save() elif key.startswith("property"): # properties are marshalled as: property-variant_id|property_id temp = key.split("-")[1] variant_id, property_id = temp.split("|") variant = Product.objects.get(pk=variant_id) prop = Property.objects.get(pk=property_id) ppv = None ppv_filterable = None try: ppv = ProductPropertyValue.objects.get(product=variant, property_id=property_id, type=PROPERTY_VALUE_TYPE_VARIANT) except ProductPropertyValue.DoesNotExist: pass if prop.filterable: try: ppv_filterable = ProductPropertyValue.objects.get(product=variant, property_id=property_id, type=PROPERTY_VALUE_TYPE_FILTER) except ProductPropertyValue.DoesNotExist: pass if value != '': if not ppv: ppv = ProductPropertyValue.objects.create(product=variant, property_id=property_id, type=PROPERTY_VALUE_TYPE_VARIANT, value=value) else: ppv.value = value ppv.save() if prop.filterable: if not ppv_filterable: ProductPropertyValue.objects.create(product=variant, property_id=property_id, value=value, type=PROPERTY_VALUE_TYPE_FILTER) else: ppv_filterable.value = value ppv_filterable.save() elif ppv: ppv.delete() if ppv_filterable: ppv_filterable.delete() # Refresh variant positions for i, variant in enumerate(product.variants.order_by("variant_position")): variant.variant_position = (i + 1) * 10 variant.save() # Send a signal to update cache product_changed.send(product) pid = product.get_parent().pk invalidate_cache_group_id('properties-%s' % pid) html = ( ("#variants", manage_variants(request, product_id, as_string=True)), ("#selectable-products-inline", _selectable_products_inline(request, product)), ) result = simplejson.dumps({ "html": html, "message": message, }, cls=LazyEncoder) return HttpResponse(result)