class Modifier(TranslatableModel, CatalogModel):
    """
    Defines different amounts to be applied to a product or a category.
    """
    KIND_STANDARD = 'standard'
    KIND_DISCOUNT = 'discount'
    KIND_CART_MODIFIER = 'cart_modifier'
    KIND_CHOICES = (
        (KIND_STANDARD, _('Standard')),
        (KIND_DISCOUNT, _('Discount')),
        (KIND_CART_MODIFIER, _('Cart modifier')),
    )

    code = models.SlugField(
        _('Code'), max_length=128, unique=True,
        help_text=scs.SLUG_FIELD_HELP_TEXT)

    amount = CurrencyField(
        verbose_name=_('Amount'),
        help_text=_('Absolute amount that should be applied. '
                    'Can be negative.'))

    percent = models.DecimalField(
        _('Percent'), blank=True, null=True,
        max_digits=4, decimal_places=2,
        help_text=_('If percent is set, it will override the absolute '
                    'amount. Can be negative.'))

    kind = models.CharField(
        _('Kind'), max_length=128, choices=KIND_CHOICES, default=KIND_STANDARD,
        help_text=_('Select a modifier kind. If "Cart modifier" is selected, '
                    'modifier will be constant and affect entire cart.'))

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=128),
    )

    objects = CatalogManager()

    class Meta:
        db_table = 'catalog_modifiers'
        verbose_name = _('Modifier')
        verbose_name_plural = _('Modifiers')

    def get_name(self):
        return self.safe_translation_getter('name')

    def get_slug(self):
        return self.code

    def get_extra_cart_item_price_field(self, cart_item, request=None):
        """
        Returns extra price field for the given cart item.
        """
        if self.can_be_applied(cart_item=cart_item, request=request):
            return (self.get_name(), self.calculate_add_price(
                cart_item.current_total, cart_item.quantity))

    def get_extra_cart_price_field(self, cart, request=None):
        """
        Returns extra price field for entire cart.
        """
        if self.can_be_applied(cart=cart, request=request):
            return (self.get_name(), self.calculate_add_price(
                cart.current_total))

    def calculate_add_price(self, price, quantity=1):
        """
        Calculates and returns an amount to be added to the given price.
        """
        if self.percent:
            add_price = (self.percent / 100) * price
        else:
            add_price = self.amount * quantity
        return add_price

    def can_be_applied(self, cart_item=None, cart=None, request=None):
        """
        Checks if this modifier can be applied or not.
        """
        # Check that product is eligible if cart_item is specified.
        if cart_item and not self.is_eligible_product(cart_item.product):
            return False

        # Check that all conditions are met.
        for con in self.conditions.select_related().all():
            if not con.is_met(cart_item=cart_item, cart=cart, request=request):
                return False

        cart_id = cart_item.cart_id if cart_item else cart.id if cart else None
        return self.is_code_applied(cart_id)

    def is_code_applied(self, cart_id):
        """
        Check for codes on this modifier, and if they exist make sure
        that at least one is applied to the given cart. Codes have
        alerady been validated so we just have to make sure they are
        active and exist.
        """
        codes = self.codes.active()

        if codes.exists():
            cart_codes = CartModifierCode.objects.filter(cart_id=cart_id)
            cart_codes = list(set(cart_codes.values_list('code', flat=True)))
            if cart_codes:
                for code in cart_codes:
                    if codes.filter(code=code).exists():
                        return True

            # Codes exist but not one is applied, return False.
            return False

        # Codes don't exist, return True.
        return True

    def is_eligible_product(self, product):
        """
        Returns if modifier can be applied to a given product.
        """
        if self.kind == self.KIND_DISCOUNT:
            return product.is_discountable
        return self.kind != self.KIND_CART_MODIFIER

    @classmethod
    def get_cart_modifiers(cls):
        """
        Returns all active cart modifiers.
        """
        return cls.objects.active(kind=cls.KIND_CART_MODIFIER) class ModifierCode(models.Model):
    modifier = models.ForeignKey(
        Modifier, related_name='codes', verbose_name=_('Modifier'))

    code = models.SlugField(
        _('Code'), max_length=30, unique=True,
        help_text=scs.SLUG_FIELD_HELP_TEXT)

    active = models.BooleanField(
        _('Active'), default=True, help_text=scs.ACTIVE_FIELD_HELP_TEXT)
    valid_from = models.DateTimeField(_('Valid from'), default=datetime.now)
    valid_until = models.DateTimeField(_('Valid until'), blank=True, null=True)

    objects = ModifierCodeManager()

    class Meta:
        db_table = 'catalog_modifier_codes'
        verbose_name = _('Code')
        verbose_name_plural = _('Codes')

    def __str__(self):
        return '{}'.format(self.code) class CartModifierCode(models.Model):
    cart = models.ForeignKey(get_model_string('Cart'), editable=False)
    code = models.CharField(_('Code'), max_length=30)

    class Meta:
        db_table = 'catalog_cart_modifier_codes'
        verbose_name = _('Cart modifier code')
        verbose_name_plural = _('Cart modifier codes')

    def __str__(self):
        return '{}'.format(self.code) class Category(CategoryBase, TranslatableModel):
    """
    A categorization layer inherited from CategoryBase.
    """
    featured_image = FilerImageField(
        blank=True, null=True, verbose_name=_('Featured image'))

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=128),
        slug=models.SlugField(
            _('Slug'), max_length=128, help_text=scs.SLUG_FIELD_HELP_TEXT),
        description=models.TextField(_('Description'), blank=True),
        meta={'unique_together': [('slug', 'language_code')]},
    )

    body = PlaceholderField(
        'catalog_category_body', related_name='category_body_set')

    objects = CatalogManager()

    class Meta:
        db_table = 'catalog_categories'
        verbose_name = _('Category')
        verbose_name_plural = _('Categories')

    def get_absolute_url(self):
        return reverse('catalog_category_detail', args=[self.get_slug()])

    def get_name(self):
        return self.safe_translation_getter('name')

    def get_slug(self):
        return self.safe_translation_getter('slug') class Brand(CategoryBase, TranslatableModel):
    """
    A categorization layer inherited from CategoryBase.
    """
    featured_image = FilerImageField(
        blank=True, null=True, verbose_name=_('Featured image'))

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=128),
        slug=models.SlugField(
            _('Slug'), max_length=128, help_text=scs.SLUG_FIELD_HELP_TEXT),
        description=models.TextField(_('Description'), blank=True),
        meta={'unique_together': [('slug', 'language_code')]},
    )

    body = PlaceholderField(
        'catalog_brand_body', related_name='brand_body_set')

    objects = CatalogManager()

    class Meta:
        db_table = 'catalog_brands'
        verbose_name = _('Brand')
        verbose_name_plural = _('Brands')

    def get_absolute_url(self):
        return reverse('catalog_brand_detail', args=[self.get_slug()])

    def get_name(self):
        return self.safe_translation_getter('name')

    def get_slug(self):
        return self.safe_translation_getter('slug') class Manufacturer(CategoryBase, TranslatableModel):
    """
    A categorization layer inherited from CategoryBase.
    """
    featured_image = FilerImageField(
        blank=True, null=True, verbose_name=_('Featured image'))

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=128),
        slug=models.SlugField(
            _('Slug'), max_length=128, help_text=scs.SLUG_FIELD_HELP_TEXT),
        description=models.TextField(_('Description'), blank=True),
        meta={'unique_together': [('slug', 'language_code')]},
    )

    body = PlaceholderField(
        'catalog_manufacturer_body', related_name='manufacturer_body_set')

    objects = CatalogManager()

    class Meta:
        db_table = 'catalog_manufacturers'
        verbose_name = _('Manufacturer')
        verbose_name_plural = _('Manufacturers')

    def get_absolute_url(self):
        return reverse('catalog_manufacturer_detail', args=[self.get_slug()])

    def get_name(self):
        return self.safe_translation_getter('name')

    def get_slug(self):
        return self.safe_translation_getter('slug') class Product(ProductBase, ModifierModel, TranslatableModel):
    """
    Inherits from ProductBase and adds more specific fields like
    categorization etc.
    """
    featured_image = FilerImageField(
        blank=True, null=True, related_name='featured_images',
        verbose_name=_('Featured image'))

    category = TreeForeignKey(
        Category, blank=True, null=True, related_name='products',
        verbose_name=_(u'Category'))
    brand = TreeForeignKey(
        Brand, blank=True, null=True, related_name='products',
        verbose_name=_(u'Brand'))
    manufacturer = TreeForeignKey(
        Manufacturer, blank=True, null=True, related_name='products',
        verbose_name=_(u'Manufacturer'))

    attributes = models.ManyToManyField(
        'Attribute', through='ProductAttributeValue',
        related_name='attributes', verbose_name=_('Attributes'))

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=128),
        slug=models.SlugField(
            _('Slug'), max_length=128, help_text=scs.SLUG_FIELD_HELP_TEXT),
        description=models.TextField(_('Description'), blank=True),
        meta={'unique_together': [('slug', 'language_code')]},
    )

    media = PlaceholderField(
        'catalog_product_media', related_name='product_media_set')
    body = PlaceholderField(
        'catalog_product_body', related_name='product_body_set')

    objects = ProductManager()

    class Meta:
        db_table = 'catalog_products'
        verbose_name = _('Product')
        verbose_name_plural = _('Products')

    def get_absolute_url(self):
        return reverse('catalog_product_detail', args=[self.get_slug()])

    def get_name(self):
        return self.safe_translation_getter('name')

    def get_slug(self):
        return self.safe_translation_getter('slug')

    def get_featured_image(self):
        if self.is_featured_image_inherited:
            return self.parent.get_featured_image()
        return self.featured_image

    def get_description(self):
        if self.is_description_inherited:
            return self.parent.get_description()
        return self.safe_translation_getter('description')

    def get_extra_dict(self):
        data = dict(
            description=self.get_description(),
            is_description_inherited=self.is_description_inherited,
            is_featured_image_inherited=self.is_featured_image_inherited,
            is_media_inherited=self.is_media_inherited,
            is_body_inherited=self.is_body_inherited,
            measurements=self.get_measurements(),
            currencies=self.get_currencies(),
            flags=self.get_flags(),
            related_products=self.get_related_products(),
        )
        data.update(self.get_categorization())
        return data

    def get_modifiers(self, distinct=True):
        """
        Returns all modifiers from products gategorization.
        """
        mods = self.modifiers.select_related().active()

        if self.is_variant:
            mods = mods | self.parent.get_modifiers(distinct=False)
        else:
            if self.category:
                mods = mods | self.category.get_modifiers(distinct=False)
            if self.brand:
                mods = mods | self.brand.get_modifiers(distinct=False)
            if self.manufacturer:
                mods = mods | self.manufacturer.get_modifiers(distinct=False)

        return mods.distinct() if distinct else mods

    @property
    def is_description_inherited(self):
        return (self.is_variant and not
                self.safe_translation_getter('description'))

    @property
    def is_featured_image_inherited(self):
        return self.is_variant and not self.featured_image

    @property
    def is_media_inherited(self):
        return self.is_variant and not self.media.get_plugins().exists()

    @property
    def is_body_inherited(self):
        return self.is_variant and not self.body.get_plugins().exists()

    def get_currencies(self):
        """
        Calculates prices for all currencies and returns them in a dict.
        """
        currencies = {}

        for currency in Currency.objects.filter(is_active=True):
            price = calculate_price(self.get_price(), currency.code)
            unit_price = calculate_price(self.get_unit_price(), currency.code)

            currencies[currency.code] = dict(
                code=force_str(currency.code),
                name=force_str(currency.name),
                symbol=force_str(currency.symbol),
                factor=force_str(currency.factor),
                is_base=currency.is_base,
                is_default=currency.is_default,
                price=force_str(price),
                unit_price=force_str(unit_price),
            )

        return dict(**currencies)

    def get_categorization(self):
        """
        Returns categorization dict, making sure to return a parents
        categorization if product is a variant.
        """
        if self.is_variant:
            return self.parent.get_categorization()

        category = self.category.as_dict if self.category else None
        brand = self.brand.as_dict if self.brand else None
        manufacturer = self.manufacturer.as_dict if self.manufacturer else None

        return dict(
            category=category,
            brand=brand,
            manufacturer=manufacturer,
        )

    def get_measurements(self):
        """
        Check if measure is inherited and returns correct dict of all
        product measurements.
        """
        measurements = self.measurements.select_related().all()

        if self.is_variant:
            kinds = measurements.values_list('kind', flat=True)
            parent_mesurements = self.parent.measurements.select_related()
            parent_mesurements = parent_mesurements.exclude(kind__in=kinds)
            measurements = measurements | parent_mesurements

        measurements_dict = {}
        for kind in dict(MeasurementBase.KIND_CHOICES).keys():
            try:
                measurements_dict[kind] = measurements.get(kind=kind).as_dict
            except ProductMeasurement.DoesNotExist:
                measurements_dict[kind] = None
        return measurements_dict

    def get_flags(self):
        """
        Checks for parent flags and returns correct list of dictionaries
        for product flags.
        """
        flags = self.flags.select_related().all()

        if self.is_variant:
            flag_ids = flags.values_list('flag_id', flat=True)
            parent_flags = self.parent.flags.select_related()
            parent_flags = parent_flags.exclude(flag_id__in=flag_ids)
            flags = flags | parent_flags
        return dict((x.get_code(), x.as_dict) for x in flags)

    def get_related_products(self):
        """
        Returns all related products for this product.
        """
        if self.is_variant:
            return self.parent.get_related_products()

        products_dict = dict(
            (x[0], {'name': force_str(x[1]), 'products': []})
            for x in scs.RELATED_PRODUCT_KIND_CHOICES)

        for obj in self.related_products.select_related().all():
            if obj.kind in products_dict:
                products_dict[obj.kind]['products'].append(
                    force_str(obj.product.pk))
        return products_dict class Attribute(TranslatableModel):
    """
    Used to define different types of attributes to be assigned on a
    Product variant. Eg. For a t-shirt attributes could be size, color,
    pattern etc.
    """
    KIND_OPTION = 'option'
    KIND_FILE = 'file'
    KIND_IMAGE = 'image'
    KIND_INTEGER = 'integer'
    KIND_BOOLEAN = 'boolean'
    KIND_FLOAT = 'float'
    KIND_DATE = 'date'
    KIND_CHOICES = (
        (KIND_INTEGER, _('Integer')),
        (KIND_BOOLEAN, _('True / False')),
        (KIND_FLOAT, _('Float')),
        (KIND_DATE, _('Date')),
        (KIND_OPTION, _('Option')),
        (KIND_FILE, _('File')),
        (KIND_IMAGE, _('Image')),
    )

    code = models.SlugField(
        _('Code'), max_length=128, unique=True,
        help_text=scs.SLUG_FIELD_HELP_TEXT)

    kind = models.CharField(
        max_length=20, choices=KIND_CHOICES, default=KIND_CHOICES[0][0],
        verbose_name=_('Type'),
        help_text=_('Select data type. If you choose "Option" data type, '
                    'specify the options below.'))

    template = models.CharField(
        _('Template'), max_length=255, blank=True, null=True,
        choices=scs.ATTRIBUTE_TEMPLATE_CHOICES,
        help_text=_('You can select a template for rendering this attribute '
                    'or leave it empty for the default (dropdown) look.'))

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=128),
    )

    class Meta:
        db_table = 'catalog_attributes'
        ordering = ('code', )
        verbose_name = _('Attribute')
        verbose_name_plural = _('Attributes')

    def __str__(self):
        return '{} ({})'.format(
            self.get_slug(), dict(self.KIND_CHOICES)[self.kind])

    def get_name(self):
        return self.safe_translation_getter('name')

    def get_slug(self):
        return self.code

    @property
    def type(self):
        return self.kind

    @property
    def is_option(self):
        return self.kind == self.KIND_OPTION

    @property
    def is_file(self):
        return self.kind in (self.KIND_FILE, self.KIND_IMAGE)

    @property
    def as_dict(self):
        template = force_str(self.template) if self.template else None
        return dict(
            code=force_str(self.get_slug()),
            name=force_str(self.get_name()),
            type=force_str(self.kind),
            template=template,
        )

    def get_values(self):
        return list(set([x.value for x in self.values.select_related().all()]))

    @classmethod
    def is_nullable(cls, attr_code, obj):
        """
        If any of 'obj.variants' miss the given attribute means that
        this attribute can be null and returns True.

        If all 'obj.variants' have defined this attribute, then it's
        required and returns False.
        """
        if obj.is_group:
            for variant in obj.variants.select_related().all():
                if attr_code not in [x['code'] for x in variant.get_attrs()]:
                    return True
            return False

    @classmethod
    def template_for(cls, attr_code):
        """
        Returns an attribute template for attribute with a given code.
        """
        try:
            return cls.objects.get(code=attr_code).template
        except cls.DoesNotExist:
            return None

    @classmethod
    def filter_dict(cls, dictionary):
        """
        Filters the given dictionary, removes items where key is not
        an attribute code.
        """
        codes = [x.code for x in cls.objects.all()]
        return dict((k, v) for k, v in dictionary.items() if k in codes and v) class ProductAttributeValue(AttributeValueBase):
    """
    Through model for Product M2M relation to Attribute.
    """
    product = models.ForeignKey(
        Product, related_name='attribute_values', verbose_name=_('Product'))

    class Meta:
        db_table = 'catalog_product_attribute_values'
        verbose_name = _('Attribute')
        verbose_name_plural = _('Attributes')
        unique_together = ('attribute', 'product') class RelatedProduct(models.Model):
    """
    Product relations model.
    """
    base_product = models.ForeignKey(
        Product, related_name='related_products',
        verbose_name=_('Base product'))

    product = models.ForeignKey(
        Product, verbose_name=_('Product'))

    kind = UnderscoreField(
        _('Kind'), max_length=255,
        choices=scs.RELATED_PRODUCT_KIND_CHOICES,
        default=scs.RELATED_PRODUCT_KIND_CHOICES[0][0])

    class Meta:
        db_table = 'catalog_related_products'
        verbose_name = _('Related Product')
        verbose_name_plural = _('Related Products')
        unique_together = ('base_product', 'product', 'kind')

    def __str__(self):
        return self.product.get_name()

    @property
    def as_dict(self):
        return dict(
            product=force_str(self.product_id),
            kind=force_str(self.kind),
        ) class AttributeValueKindsMapSelect(forms.Select):
    """
    A custom Select widget for generating 'data-choices' attribute when
    selected attributes kind is Attribute.KIND_OPTION.
    """
    def render_option(self, selected_choices, option_value, option_label):
        option_value = force_text(option_value)
        if option_value in selected_choices:
            selected_html = mark_safe(' selected="selected"')
            if not self.allow_multiple_selected:
                selected_choices.remove(option_value)
        else:
            selected_html = ''

        if option_value == Attribute.KIND_OPTION:
            opts = AttributeOption.objects.translated().filter(
                attribute__id=option_label).values_list('value', flat=True)
            choices = mark_safe(' data-choices="{0}"'.format(','.join(opts)))
        else:
            choices = ''

        return format_html('<option value="{0}"{1}{2}>{3}</option>',
                           option_value,
                           selected_html,
                           choices,
                           force_text(option_label))
from django import forms
from django.forms.models import BaseInlineFormSet
from django.utils.translation import get_language
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_str
from parler.forms import TranslatableModelForm
from catalog.models import (
    Modifier, ModifierCode, CartModifierCode, Category, Brand, Manufacturer,
    Product, Attribute, ProductAttributeValue, RelatedProduct)
from catalog.widgets import AttributeValueKindsMapSelect
# -*- coding: utf-8 -*-
from __future__ import unicode_literals






class CatalogModelFormBase(TranslatableModelForm):
    """
    Objects that inherit from CatalogModel should have their form
    inherit from this one for some modified fields.
    """
    class Meta:
        model = None

    def clean(self):
        """
        Check that object with this slug on this language
        doesn't exists.
        """
        instance = getattr(self, 'instance', None)
        if instance is not None and instance.pk is None:
            slug = self.cleaned_data.get('slug', None)
            if slug is not None:
                language = getattr(self, 'language', get_language()[:2])
                try:
                    self._meta.model.objects.get_by_slug(slug, language)
                    raise forms.ValidationError(
                        _('%s with this slug already exists on this language.')
                        % self._meta.model.__name__)
                except self._meta.model.DoesNotExist:
                    pass
        return super(CatalogModelFormBase, self).clean()


class ModifierModelForm(CatalogModelFormBase):
    class Meta:
        model = Modifier


class CartModifierCodeModelForm(forms.ModelForm):
    class Meta:
        model = CartModifierCode

    def clean_code(self):
        instance = getattr(self, 'instance', None)
        code = self.cleaned_data.get('code')
        if instance is not None:

            if CartModifierCode.objects.filter(cart=instance.cart, code=code):