Source code for shuup.admin.modules.products.forms.variable_variation_forms

# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2017, Shoop Commerce Ltd. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals

import json

import six
from django import forms
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Model
from django.db.transaction import atomic
from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _

from shuup.admin.forms.widgets import ProductChoiceWidget
from shuup.core.excs import ImpossibleProductModeException, Problem
from shuup.core.models import (
    Product, ProductVariationVariable, ProductVariationVariableValue
)
from shuup.utils.i18n import get_language_name
from shuup.utils.multilanguage_model_form import to_language_codes


[docs]class VariableVariationChildrenForm(forms.Form): def __init__(self, **kwargs): self.parent_product = kwargs.pop("parent_product") self.request = kwargs.pop("request", None) super(VariableVariationChildrenForm, self).__init__(**kwargs) self._build_fields() def _build_fields(self): for combo in self.parent_product.get_all_available_combinations(): field = forms.ModelChoiceField( queryset=Product.objects.all(), # TODO: Add a mode for ProductChoiceWidget to only allow eligible variation children to be selected widget=ProductChoiceWidget(clearable=True), required=False, initial=combo["result_product_pk"], label=_("variable combination") ) field.combo = combo self.fields["r_%s" % combo["hash"]] = field def _save_combo(self, combo): """ :param combo: Combo description dict, from `get_all_available_combinations` :type combo: dict """ new_product = self.cleaned_data.get("r_%s" % combo["hash"]) new_product_pk = getattr(new_product, "pk", None) old_product_pk = combo["result_product_pk"] if old_product_pk == new_product_pk: # Nothing to do return if old_product_pk: # Unlink old one try: old_product = Product.objects.get(variation_parent=self.parent_product, pk=old_product_pk) old_product.unlink_from_parent() except ObjectDoesNotExist: pass if new_product: try: new_product.link_to_parent(parent=self.parent_product, combination_hash=combo["hash"]) except ImpossibleProductModeException as ipme: six.raise_from( Problem(_("Unable to link %(product)s: %(error)s") % {"product": new_product, "error": ipme}), ipme )
[docs] def save(self): if not self.has_changed(): # See `CustomerGroupPricingForm.save()`. return with atomic(): for combo in self.parent_product.get_all_available_combinations(): self._save_combo(combo)
[docs]class VariationVariablesDataForm(forms.Form): data = forms.CharField(widget=forms.HiddenInput(), required=False) def __init__(self, **kwargs): self.parent_product = kwargs.pop("parent_product", None) super(VariationVariablesDataForm, self).__init__(**kwargs)
[docs] def get_variable_data(self): # This function is a little convoluted due to optimization. product = self.parent_product variables = {} # All relevant ProductVariationVariables. # All encountered ProductVariationVariableValues. # These are the same dicts as are added into `variables[x].values`, # so updating these (as is done later) will modify `variables` as well. all_values = {} # Populate `variables` and `all_values`, but don't bother with translatable text just yet. for var in ProductVariationVariable.objects.filter(product=product).prefetch_related("values").all(): values = [] variables[var.pk] = { "pk": var.pk, "ordering": var.ordering, "identifier": var.identifier, "names": {}, "values": values } for val in var.values.all(): val_dict = { "pk": val.pk, "ordering": val.ordering, "identifier": val.identifier, "texts": {} # The underlying field is "value", but that's confusing here } all_values[val.pk] = val_dict values.append(val_dict) # Now backfill in all translations. variable_xlate_model = ProductVariationVariable._parler_meta.root_model value_xlate_model = ProductVariationVariableValue._parler_meta.root_model assert issubclass(variable_xlate_model, Model) assert issubclass(value_xlate_model, Model) for var_id, language_code, name in ( variable_xlate_model.objects.filter(master__in=variables).values_list("master", "language_code", "name") ): variables[var_id]["names"][language_code] = name for val_id, language_code, text in ( value_xlate_model.objects.filter(master__in=all_values).values_list("master", "language_code", "value") ): all_values[val_id]["texts"][language_code] = text return sorted(variables.values(), key=lambda var: var["ordering"])
[docs] def get_editor_args(self): return { "languages": [ { "code": code, "name": get_language_name(code) } for code in to_language_codes( settings.LANGUAGES, getattr(settings, "PARLER_DEFAULT_LANGUAGE_CODE", "en")) ], "variables": self.get_variable_data() }
[docs] def process_var_datum(self, var_datum, ordering): pk = str(var_datum["pk"]) deleted = var_datum.get("DELETE") if pk.startswith("$"): # New variable. var = ProductVariationVariable(product=self.parent_product) else: var = ProductVariationVariable.objects.get(product=self.parent_product, pk=pk) if deleted: if var.pk: var.delete() return var.identifier = var_datum.get("identifier") or get_random_string() var.ordering = ordering var.save() for lang_code, name in var_datum["names"].items(): var.set_current_language(lang_code) var.name = name var.save_translations() for ordering, val_datum in enumerate(var_datum["values"]): self.process_val_datum(var, val_datum, ordering) if not var.values.exists(): # All values gone, so delete the skeleton variable too var.delete()
# thank mr skeltal
[docs] def process_val_datum(self, var, val_datum, ordering): """ :type var: ProductVariationVariable :type val_datum: dict """ pk = str(val_datum["pk"]) deleted = val_datum.get("DELETE") if pk.startswith("$"): # New value. val = ProductVariationVariableValue(variable=var) else: val = var.values.get(pk=pk) if deleted: if val.pk: val.delete() return val.identifier = val_datum.get("identifier") or get_random_string() val.ordering = ordering val.save() for lang_code, text in val_datum["texts"].items(): val.set_current_language(lang_code) val.value = text val.save_translations()
[docs] def save(self): var_data = self.cleaned_data.get("data") if not var_data: # No data means the Mithril side hasn't been touched at all return var_data = json.loads(var_data) for ordering, var_datum in enumerate(var_data): self.process_var_datum(var_datum, ordering)