We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Sales with products applied to wrong products
- Create a sale in discounts and add a product to it.
- The discount is applied to an other product.

The system is using the product pk (not the product variant pk) for calculating the discount.
So the discount is applied to the product with variant pk equal to product pk.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `saleor/discount/models.py`
Content:
```
1 from __future__ import unicode_literals
2 from datetime import date
3 from decimal import Decimal
4 
5 from django.conf import settings
6 from django.db import models
7 from django.db.models import F
8 from django.utils.translation import pgettext, pgettext_lazy
9 from django.utils.encoding import python_2_unicode_compatible, smart_text
10 from django_countries import countries
11 from django_prices.models import PriceField
12 from django_prices.templatetags.prices_i18n import net
13 from prices import FixedDiscount, percentage_discount, Price
14 
15 from ..cart.utils import (
16     get_product_variants_and_prices, get_category_variants_and_prices)
17 
18 
19 class NotApplicable(ValueError):
20     pass
21 
22 
23 class VoucherQueryset(models.QuerySet):
24 
25     def active(self):
26         today = date.today()
27         queryset = self.filter(
28             models.Q(usage_limit__isnull=True) |
29             models.Q(used__lt=models.F('usage_limit')))
30         queryset = queryset.filter(
31             models.Q(end_date__isnull=True) | models.Q(end_date__gte=today))
32         queryset = queryset.filter(start_date__lte=today)
33         return queryset
34 
35     def increase_usage(self, voucher):
36         voucher.used = F('used') + 1
37         voucher.save(update_fields=['used'])
38 
39     def decrease_usage(self, voucher):
40         voucher.used = F('used') - 1
41         voucher.save(update_fields=['used'])
42 
43 
44 @python_2_unicode_compatible
45 class Voucher(models.Model):
46 
47     APPLY_TO_ONE_PRODUCT = 'one'
48     APPLY_TO_ALL_PRODUCTS = 'all'
49 
50     APPLY_TO_PRODUCT_CHOICES = (
51         (APPLY_TO_ONE_PRODUCT,
52          pgettext_lazy('voucher', 'Apply to a single item')),
53         (APPLY_TO_ALL_PRODUCTS,
54          pgettext_lazy('voucher', 'Apply to all matching products')))
55 
56     DISCOUNT_VALUE_FIXED = 'fixed'
57     DISCOUNT_VALUE_PERCENTAGE = 'percentage'
58 
59     DISCOUNT_VALUE_TYPE_CHOICES = (
60         (DISCOUNT_VALUE_FIXED,
61          pgettext_lazy('voucher', settings.DEFAULT_CURRENCY)),
62         (DISCOUNT_VALUE_PERCENTAGE, pgettext_lazy('voucher', '%')))
63 
64     PRODUCT_TYPE = 'product'
65     CATEGORY_TYPE = 'category'
66     SHIPPING_TYPE = 'shipping'
67     VALUE_TYPE = 'value'
68 
69     TYPE_CHOICES = (
70         (VALUE_TYPE, pgettext_lazy('voucher', 'All purchases')),
71         (PRODUCT_TYPE, pgettext_lazy('voucher', 'One product')),
72         (CATEGORY_TYPE, pgettext_lazy('voucherl', 'A category of products')),
73         (SHIPPING_TYPE, pgettext_lazy('voucher', 'Shipping')))
74 
75     type = models.CharField(
76         pgettext_lazy('voucher', 'discount for'), max_length=20,
77         choices=TYPE_CHOICES, default=VALUE_TYPE)
78     name = models.CharField(
79         pgettext_lazy('voucher', 'name'), max_length=255, null=True,
80         blank=True)
81     code = models.CharField(
82         pgettext_lazy('voucher', 'code'), max_length=12, unique=True,
83         db_index=True)
84     usage_limit = models.PositiveIntegerField(
85         pgettext_lazy('voucher', 'usage limit'), null=True, blank=True)
86     used = models.PositiveIntegerField(default=0, editable=False)
87     start_date = models.DateField(
88         pgettext_lazy('voucher', 'start date'), default=date.today)
89     end_date = models.DateField(
90         pgettext_lazy('voucher', 'end date'), null=True, blank=True)
91 
92     discount_value_type = models.CharField(
93         pgettext_lazy('voucher', 'discount type'), max_length=10,
94         choices=DISCOUNT_VALUE_TYPE_CHOICES, default=DISCOUNT_VALUE_FIXED)
95     discount_value = models.DecimalField(
96         pgettext_lazy('voucher', 'discount value'), max_digits=12,
97         decimal_places=2)
98 
99     # not mandatory fields, usage depends on type
100     product = models.ForeignKey('product.Product', blank=True, null=True)
101     category = models.ForeignKey('product.Category', blank=True, null=True)
102     apply_to = models.CharField(max_length=20, blank=True, null=True)
103     limit = PriceField(max_digits=12, decimal_places=2, null=True,
104                        blank=True, currency=settings.DEFAULT_CURRENCY)
105 
106     objects = VoucherQueryset.as_manager()
107 
108     @property
109     def is_free(self):
110         return (self.discount_value == Decimal(100) and
111                 self.discount_value_type == Voucher.DISCOUNT_VALUE_PERCENTAGE)
112 
113     def __str__(self):
114         if self.name:
115             return self.name
116         discount = '%s%s' % (
117             self.discount_value, self.get_discount_value_type_display())
118         if self.type == Voucher.SHIPPING_TYPE:
119             if self.is_free:
120                 return pgettext('voucher', 'Free shipping')
121             else:
122                 return pgettext('voucher', '%(discount)s off shipping') % {
123                     'discount': discount}
124         if self.type == Voucher.PRODUCT_TYPE:
125             return pgettext('voucher', '%(discount)s off %(product)s') % {
126                 'discount': discount, 'product': self.product}
127         if self.type == Voucher.CATEGORY_TYPE:
128             return pgettext('voucher', '%(discount)s off %(category)s') % {
129                 'discount': discount, 'category': self.category}
130         return pgettext('voucher', '%(discount)s off') % {'discount': discount}
131 
132     def get_apply_to_display(self):
133         if self.type == Voucher.SHIPPING_TYPE and self.apply_to:
134             return countries.name(self.apply_to)
135         if self.type == Voucher.SHIPPING_TYPE:
136             return pgettext('voucher', 'Any country')
137         if self.apply_to and self.type in {
138                 Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE}:
139             choices = dict(self.APPLY_TO_PRODUCT_CHOICES)
140             return choices[self.apply_to]
141 
142     def get_fixed_discount_for(self, amount):
143         if self.discount_value_type == self.DISCOUNT_VALUE_FIXED:
144             discount_price = Price(net=self.discount_value,
145                                    currency=settings.DEFAULT_CURRENCY)
146             discount = FixedDiscount(
147                 amount=discount_price, name=smart_text(self))
148         elif self.discount_value_type == self.DISCOUNT_VALUE_PERCENTAGE:
149             discount = percentage_discount(
150                 value=self.discount_value, name=smart_text(self))
151             fixed_discount_value = amount - discount.apply(amount)
152             discount = FixedDiscount(
153                 amount=fixed_discount_value, name=smart_text(self))
154         else:
155             raise NotImplementedError('Unknown discount value type')
156         if discount.amount > amount:
157             return FixedDiscount(amount, name=smart_text(self))
158         else:
159             return discount
160 
161     def get_discount_for_checkout(self, checkout):
162         if self.type == Voucher.VALUE_TYPE:
163             cart_total = checkout.cart.get_total()
164             limit = self.limit if self.limit is not None else cart_total
165             if cart_total < limit:
166                 msg = pgettext(
167                     'voucher',
168                     'This offer is only valid for orders over %(amount)s.')
169                 raise NotApplicable(msg % {'amount': net(limit)})
170             return self.get_fixed_discount_for(cart_total)
171 
172         elif self.type == Voucher.SHIPPING_TYPE:
173             if not checkout.is_shipping_required:
174                 msg = pgettext(
175                     'voucher', 'Your order does not require shipping.')
176                 raise NotApplicable(msg)
177             shipping_method = checkout.shipping_method
178             if not shipping_method:
179                 msg = pgettext(
180                     'voucher', 'Please select a shipping method first.')
181                 raise NotApplicable(msg)
182             if (self.apply_to and
183                     shipping_method.country_code != self.apply_to):
184                 msg = pgettext(
185                     'voucher', 'This offer is only valid in %(country)s.')
186                 raise NotApplicable(msg % {
187                     'country': self.get_apply_to_display()})
188             if self.limit is not None and shipping_method.price > self.limit:
189                 msg = pgettext(
190                     'voucher',
191                     'This offer is only valid for shipping over %(amount)s.')
192                 raise NotApplicable(msg % {'amount': net(self.limit)})
193             return self.get_fixed_discount_for(shipping_method.price)
194 
195         elif self.type in (Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE):
196             if self.type == Voucher.PRODUCT_TYPE:
197                 prices = list(
198                     (item[1] for item in get_product_variants_and_prices(
199                         checkout.cart, self.product)))
200             else:
201                 prices = list(
202                     (item[1] for item in get_category_variants_and_prices(
203                         checkout.cart, self.category)))
204             if len(prices) == 0:
205                 msg = pgettext(
206                     'voucher', 'This offer is only valid for selected items.')
207                 raise NotApplicable(msg)
208             if self.apply_to == Voucher.APPLY_TO_ALL_PRODUCTS:
209                 discounts = (
210                     self.get_fixed_discount_for(price) for price in prices)
211                 discount_total = sum(
212                     (discount.amount for discount in discounts),
213                     Price(0, currency=settings.DEFAULT_CURRENCY))
214                 return FixedDiscount(discount_total, smart_text(self))
215             else:
216                 product_total = sum(
217                     prices, Price(0, currency=settings.DEFAULT_CURRENCY))
218                 return self.get_fixed_discount_for(product_total)
219 
220         else:
221             raise NotImplementedError('Unknown discount type')
222 
223 
224 @python_2_unicode_compatible
225 class Sale(models.Model):
226     FIXED = 'fixed'
227     PERCENTAGE = 'percentage'
228 
229     DISCOUNT_TYPE_CHOICES = (
230         (FIXED, pgettext_lazy('discount_type', settings.DEFAULT_CURRENCY)),
231         (PERCENTAGE, pgettext_lazy('discount_type', '%')))
232 
233     name = models.CharField(max_length=255)
234     type = models.CharField(max_length=10, choices=DISCOUNT_TYPE_CHOICES,
235                             default=FIXED)
236     value = models.DecimalField(max_digits=12, decimal_places=2, default=0)
237     products = models.ManyToManyField('product.Product', blank=True)
238     categories = models.ManyToManyField('product.Category', blank=True)
239 
240     class Meta:
241         app_label = 'discount'
242 
243     def __repr__(self):
244         return 'Sale(name=%r, value=%r, type=%s)' % (
245             str(self.name), self.value, self.get_type_display())
246 
247     def __str__(self):
248         return self.name
249 
250     def get_discount(self):
251         if self.type == self.FIXED:
252             discount_price = Price(net=self.value,
253                                    currency=settings.DEFAULT_CURRENCY)
254             return FixedDiscount(amount=discount_price, name=self.name)
255         elif self.type == self.PERCENTAGE:
256             return percentage_discount(value=self.value, name=self.name)
257         raise NotImplementedError('Unknown discount type')
258 
259     def _product_has_category_discount(self, product, discounted_categories):
260         for category in product.categories.all():
261             for discounted_category in discounted_categories:
262                 if category.is_descendant_of(discounted_category,
263                                              include_self=True):
264                     return True
265         return False
266 
267     def modifier_for_variant(self, variant):
268         check_price = variant.get_price_per_item()
269         discounted_products = [p.pk for p in self.products.all()]
270         discounted_categories = list(self.categories.all())
271         if discounted_products and variant.pk not in discounted_products:
272             raise NotApplicable('Discount not applicable for this product')
273         if (discounted_categories and not
274             self._product_has_category_discount(
275                 variant.product, discounted_categories)):
276             raise NotApplicable('Discount too high for this product')
277         return self.get_discount()
278 
279 
280 def get_variant_discounts(variant, discounts, **kwargs):
281     for discount in discounts:
282         try:
283             yield discount.modifier_for_variant(variant, **kwargs)
284         except NotApplicable:
285             pass
286
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/saleor/discount/models.py b/saleor/discount/models.py
--- a/saleor/discount/models.py
+++ b/saleor/discount/models.py
@@ -265,10 +265,9 @@
         return False
 
     def modifier_for_variant(self, variant):
-        check_price = variant.get_price_per_item()
         discounted_products = [p.pk for p in self.products.all()]
         discounted_categories = list(self.categories.all())
-        if discounted_products and variant.pk not in discounted_products:
+        if discounted_products and variant.product.pk not in discounted_products:
             raise NotApplicable('Discount not applicable for this product')
         if (discounted_categories and not
             self._product_has_category_discount(

{"golden_diff": "diff --git a/saleor/discount/models.py b/saleor/discount/models.py\n--- a/saleor/discount/models.py\n+++ b/saleor/discount/models.py\n@@ -265,10 +265,9 @@\n         return False\n \n     def modifier_for_variant(self, variant):\n-        check_price = variant.get_price_per_item()\n         discounted_products = [p.pk for p in self.products.all()]\n         discounted_categories = list(self.categories.all())\n-        if discounted_products and variant.pk not in discounted_products:\n+        if discounted_products and variant.product.pk not in discounted_products:\n             raise NotApplicable('Discount not applicable for this product')\n         if (discounted_categories and not\n             self._product_has_category_discount(\n", "issue": "Sales with products applied to wrong products\n- Create a sale in discounts and add a product to it.\n- The discount is applied to an other product.\n\nThe system is using the product pk (not the product variant pk) for calculating the discount.\nSo the discount is applied to the product with variant pk equal to product pk.\n\n", "before_files": [{"content": "from __future__ import unicode_literals\nfrom datetime import date\nfrom decimal import Decimal\n\nfrom django.conf import settings\nfrom django.db import models\nfrom django.db.models import F\nfrom django.utils.translation import pgettext, pgettext_lazy\nfrom django.utils.encoding import python_2_unicode_compatible, smart_text\nfrom django_countries import countries\nfrom django_prices.models import PriceField\nfrom django_prices.templatetags.prices_i18n import net\nfrom prices import FixedDiscount, percentage_discount, Price\n\nfrom ..cart.utils import (\n    get_product_variants_and_prices, get_category_variants_and_prices)\n\n\nclass NotApplicable(ValueError):\n    pass\n\n\nclass VoucherQueryset(models.QuerySet):\n\n    def active(self):\n        today = date.today()\n        queryset = self.filter(\n            models.Q(usage_limit__isnull=True) |\n            models.Q(used__lt=models.F('usage_limit')))\n        queryset = queryset.filter(\n            models.Q(end_date__isnull=True) | models.Q(end_date__gte=today))\n        queryset = queryset.filter(start_date__lte=today)\n        return queryset\n\n    def increase_usage(self, voucher):\n        voucher.used = F('used') + 1\n        voucher.save(update_fields=['used'])\n\n    def decrease_usage(self, voucher):\n        voucher.used = F('used') - 1\n        voucher.save(update_fields=['used'])\n\n\n@python_2_unicode_compatible\nclass Voucher(models.Model):\n\n    APPLY_TO_ONE_PRODUCT = 'one'\n    APPLY_TO_ALL_PRODUCTS = 'all'\n\n    APPLY_TO_PRODUCT_CHOICES = (\n        (APPLY_TO_ONE_PRODUCT,\n         pgettext_lazy('voucher', 'Apply to a single item')),\n        (APPLY_TO_ALL_PRODUCTS,\n         pgettext_lazy('voucher', 'Apply to all matching products')))\n\n    DISCOUNT_VALUE_FIXED = 'fixed'\n    DISCOUNT_VALUE_PERCENTAGE = 'percentage'\n\n    DISCOUNT_VALUE_TYPE_CHOICES = (\n        (DISCOUNT_VALUE_FIXED,\n         pgettext_lazy('voucher', settings.DEFAULT_CURRENCY)),\n        (DISCOUNT_VALUE_PERCENTAGE, pgettext_lazy('voucher', '%')))\n\n    PRODUCT_TYPE = 'product'\n    CATEGORY_TYPE = 'category'\n    SHIPPING_TYPE = 'shipping'\n    VALUE_TYPE = 'value'\n\n    TYPE_CHOICES = (\n        (VALUE_TYPE, pgettext_lazy('voucher', 'All purchases')),\n        (PRODUCT_TYPE, pgettext_lazy('voucher', 'One product')),\n        (CATEGORY_TYPE, pgettext_lazy('voucherl', 'A category of products')),\n        (SHIPPING_TYPE, pgettext_lazy('voucher', 'Shipping')))\n\n    type = models.CharField(\n        pgettext_lazy('voucher', 'discount for'), max_length=20,\n        choices=TYPE_CHOICES, default=VALUE_TYPE)\n    name = models.CharField(\n        pgettext_lazy('voucher', 'name'), max_length=255, null=True,\n        blank=True)\n    code = models.CharField(\n        pgettext_lazy('voucher', 'code'), max_length=12, unique=True,\n        db_index=True)\n    usage_limit = models.PositiveIntegerField(\n        pgettext_lazy('voucher', 'usage limit'), null=True, blank=True)\n    used = models.PositiveIntegerField(default=0, editable=False)\n    start_date = models.DateField(\n        pgettext_lazy('voucher', 'start date'), default=date.today)\n    end_date = models.DateField(\n        pgettext_lazy('voucher', 'end date'), null=True, blank=True)\n\n    discount_value_type = models.CharField(\n        pgettext_lazy('voucher', 'discount type'), max_length=10,\n        choices=DISCOUNT_VALUE_TYPE_CHOICES, default=DISCOUNT_VALUE_FIXED)\n    discount_value = models.DecimalField(\n        pgettext_lazy('voucher', 'discount value'), max_digits=12,\n        decimal_places=2)\n\n    # not mandatory fields, usage depends on type\n    product = models.ForeignKey('product.Product', blank=True, null=True)\n    category = models.ForeignKey('product.Category', blank=True, null=True)\n    apply_to = models.CharField(max_length=20, blank=True, null=True)\n    limit = PriceField(max_digits=12, decimal_places=2, null=True,\n                       blank=True, currency=settings.DEFAULT_CURRENCY)\n\n    objects = VoucherQueryset.as_manager()\n\n    @property\n    def is_free(self):\n        return (self.discount_value == Decimal(100) and\n                self.discount_value_type == Voucher.DISCOUNT_VALUE_PERCENTAGE)\n\n    def __str__(self):\n        if self.name:\n            return self.name\n        discount = '%s%s' % (\n            self.discount_value, self.get_discount_value_type_display())\n        if self.type == Voucher.SHIPPING_TYPE:\n            if self.is_free:\n                return pgettext('voucher', 'Free shipping')\n            else:\n                return pgettext('voucher', '%(discount)s off shipping') % {\n                    'discount': discount}\n        if self.type == Voucher.PRODUCT_TYPE:\n            return pgettext('voucher', '%(discount)s off %(product)s') % {\n                'discount': discount, 'product': self.product}\n        if self.type == Voucher.CATEGORY_TYPE:\n            return pgettext('voucher', '%(discount)s off %(category)s') % {\n                'discount': discount, 'category': self.category}\n        return pgettext('voucher', '%(discount)s off') % {'discount': discount}\n\n    def get_apply_to_display(self):\n        if self.type == Voucher.SHIPPING_TYPE and self.apply_to:\n            return countries.name(self.apply_to)\n        if self.type == Voucher.SHIPPING_TYPE:\n            return pgettext('voucher', 'Any country')\n        if self.apply_to and self.type in {\n                Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE}:\n            choices = dict(self.APPLY_TO_PRODUCT_CHOICES)\n            return choices[self.apply_to]\n\n    def get_fixed_discount_for(self, amount):\n        if self.discount_value_type == self.DISCOUNT_VALUE_FIXED:\n            discount_price = Price(net=self.discount_value,\n                                   currency=settings.DEFAULT_CURRENCY)\n            discount = FixedDiscount(\n                amount=discount_price, name=smart_text(self))\n        elif self.discount_value_type == self.DISCOUNT_VALUE_PERCENTAGE:\n            discount = percentage_discount(\n                value=self.discount_value, name=smart_text(self))\n            fixed_discount_value = amount - discount.apply(amount)\n            discount = FixedDiscount(\n                amount=fixed_discount_value, name=smart_text(self))\n        else:\n            raise NotImplementedError('Unknown discount value type')\n        if discount.amount > amount:\n            return FixedDiscount(amount, name=smart_text(self))\n        else:\n            return discount\n\n    def get_discount_for_checkout(self, checkout):\n        if self.type == Voucher.VALUE_TYPE:\n            cart_total = checkout.cart.get_total()\n            limit = self.limit if self.limit is not None else cart_total\n            if cart_total < limit:\n                msg = pgettext(\n                    'voucher',\n                    'This offer is only valid for orders over %(amount)s.')\n                raise NotApplicable(msg % {'amount': net(limit)})\n            return self.get_fixed_discount_for(cart_total)\n\n        elif self.type == Voucher.SHIPPING_TYPE:\n            if not checkout.is_shipping_required:\n                msg = pgettext(\n                    'voucher', 'Your order does not require shipping.')\n                raise NotApplicable(msg)\n            shipping_method = checkout.shipping_method\n            if not shipping_method:\n                msg = pgettext(\n                    'voucher', 'Please select a shipping method first.')\n                raise NotApplicable(msg)\n            if (self.apply_to and\n                    shipping_method.country_code != self.apply_to):\n                msg = pgettext(\n                    'voucher', 'This offer is only valid in %(country)s.')\n                raise NotApplicable(msg % {\n                    'country': self.get_apply_to_display()})\n            if self.limit is not None and shipping_method.price > self.limit:\n                msg = pgettext(\n                    'voucher',\n                    'This offer is only valid for shipping over %(amount)s.')\n                raise NotApplicable(msg % {'amount': net(self.limit)})\n            return self.get_fixed_discount_for(shipping_method.price)\n\n        elif self.type in (Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE):\n            if self.type == Voucher.PRODUCT_TYPE:\n                prices = list(\n                    (item[1] for item in get_product_variants_and_prices(\n                        checkout.cart, self.product)))\n            else:\n                prices = list(\n                    (item[1] for item in get_category_variants_and_prices(\n                        checkout.cart, self.category)))\n            if len(prices) == 0:\n                msg = pgettext(\n                    'voucher', 'This offer is only valid for selected items.')\n                raise NotApplicable(msg)\n            if self.apply_to == Voucher.APPLY_TO_ALL_PRODUCTS:\n                discounts = (\n                    self.get_fixed_discount_for(price) for price in prices)\n                discount_total = sum(\n                    (discount.amount for discount in discounts),\n                    Price(0, currency=settings.DEFAULT_CURRENCY))\n                return FixedDiscount(discount_total, smart_text(self))\n            else:\n                product_total = sum(\n                    prices, Price(0, currency=settings.DEFAULT_CURRENCY))\n                return self.get_fixed_discount_for(product_total)\n\n        else:\n            raise NotImplementedError('Unknown discount type')\n\n\n@python_2_unicode_compatible\nclass Sale(models.Model):\n    FIXED = 'fixed'\n    PERCENTAGE = 'percentage'\n\n    DISCOUNT_TYPE_CHOICES = (\n        (FIXED, pgettext_lazy('discount_type', settings.DEFAULT_CURRENCY)),\n        (PERCENTAGE, pgettext_lazy('discount_type', '%')))\n\n    name = models.CharField(max_length=255)\n    type = models.CharField(max_length=10, choices=DISCOUNT_TYPE_CHOICES,\n                            default=FIXED)\n    value = models.DecimalField(max_digits=12, decimal_places=2, default=0)\n    products = models.ManyToManyField('product.Product', blank=True)\n    categories = models.ManyToManyField('product.Category', blank=True)\n\n    class Meta:\n        app_label = 'discount'\n\n    def __repr__(self):\n        return 'Sale(name=%r, value=%r, type=%s)' % (\n            str(self.name), self.value, self.get_type_display())\n\n    def __str__(self):\n        return self.name\n\n    def get_discount(self):\n        if self.type == self.FIXED:\n            discount_price = Price(net=self.value,\n                                   currency=settings.DEFAULT_CURRENCY)\n            return FixedDiscount(amount=discount_price, name=self.name)\n        elif self.type == self.PERCENTAGE:\n            return percentage_discount(value=self.value, name=self.name)\n        raise NotImplementedError('Unknown discount type')\n\n    def _product_has_category_discount(self, product, discounted_categories):\n        for category in product.categories.all():\n            for discounted_category in discounted_categories:\n                if category.is_descendant_of(discounted_category,\n                                             include_self=True):\n                    return True\n        return False\n\n    def modifier_for_variant(self, variant):\n        check_price = variant.get_price_per_item()\n        discounted_products = [p.pk for p in self.products.all()]\n        discounted_categories = list(self.categories.all())\n        if discounted_products and variant.pk not in discounted_products:\n            raise NotApplicable('Discount not applicable for this product')\n        if (discounted_categories and not\n            self._product_has_category_discount(\n                variant.product, discounted_categories)):\n            raise NotApplicable('Discount too high for this product')\n        return self.get_discount()\n\n\ndef get_variant_discounts(variant, discounts, **kwargs):\n    for discount in discounts:\n        try:\n            yield discount.modifier_for_variant(variant, **kwargs)\n        except NotApplicable:\n            pass\n", "path": "saleor/discount/models.py"}], "after_files": [{"content": "from __future__ import unicode_literals\nfrom datetime import date\nfrom decimal import Decimal\n\nfrom django.conf import settings\nfrom django.db import models\nfrom django.db.models import F\nfrom django.utils.translation import pgettext, pgettext_lazy\nfrom django.utils.encoding import python_2_unicode_compatible, smart_text\nfrom django_countries import countries\nfrom django_prices.models import PriceField\nfrom django_prices.templatetags.prices_i18n import net\nfrom prices import FixedDiscount, percentage_discount, Price\n\nfrom ..cart.utils import (\n    get_product_variants_and_prices, get_category_variants_and_prices)\n\n\nclass NotApplicable(ValueError):\n    pass\n\n\nclass VoucherQueryset(models.QuerySet):\n\n    def active(self):\n        today = date.today()\n        queryset = self.filter(\n            models.Q(usage_limit__isnull=True) |\n            models.Q(used__lt=models.F('usage_limit')))\n        queryset = queryset.filter(\n            models.Q(end_date__isnull=True) | models.Q(end_date__gte=today))\n        queryset = queryset.filter(start_date__lte=today)\n        return queryset\n\n    def increase_usage(self, voucher):\n        voucher.used = F('used') + 1\n        voucher.save(update_fields=['used'])\n\n    def decrease_usage(self, voucher):\n        voucher.used = F('used') - 1\n        voucher.save(update_fields=['used'])\n\n\n@python_2_unicode_compatible\nclass Voucher(models.Model):\n\n    APPLY_TO_ONE_PRODUCT = 'one'\n    APPLY_TO_ALL_PRODUCTS = 'all'\n\n    APPLY_TO_PRODUCT_CHOICES = (\n        (APPLY_TO_ONE_PRODUCT,\n         pgettext_lazy('voucher', 'Apply to a single item')),\n        (APPLY_TO_ALL_PRODUCTS,\n         pgettext_lazy('voucher', 'Apply to all matching products')))\n\n    DISCOUNT_VALUE_FIXED = 'fixed'\n    DISCOUNT_VALUE_PERCENTAGE = 'percentage'\n\n    DISCOUNT_VALUE_TYPE_CHOICES = (\n        (DISCOUNT_VALUE_FIXED,\n         pgettext_lazy('voucher', settings.DEFAULT_CURRENCY)),\n        (DISCOUNT_VALUE_PERCENTAGE, pgettext_lazy('voucher', '%')))\n\n    PRODUCT_TYPE = 'product'\n    CATEGORY_TYPE = 'category'\n    SHIPPING_TYPE = 'shipping'\n    VALUE_TYPE = 'value'\n\n    TYPE_CHOICES = (\n        (VALUE_TYPE, pgettext_lazy('voucher', 'All purchases')),\n        (PRODUCT_TYPE, pgettext_lazy('voucher', 'One product')),\n        (CATEGORY_TYPE, pgettext_lazy('voucherl', 'A category of products')),\n        (SHIPPING_TYPE, pgettext_lazy('voucher', 'Shipping')))\n\n    type = models.CharField(\n        pgettext_lazy('voucher', 'discount for'), max_length=20,\n        choices=TYPE_CHOICES, default=VALUE_TYPE)\n    name = models.CharField(\n        pgettext_lazy('voucher', 'name'), max_length=255, null=True,\n        blank=True)\n    code = models.CharField(\n        pgettext_lazy('voucher', 'code'), max_length=12, unique=True,\n        db_index=True)\n    usage_limit = models.PositiveIntegerField(\n        pgettext_lazy('voucher', 'usage limit'), null=True, blank=True)\n    used = models.PositiveIntegerField(default=0, editable=False)\n    start_date = models.DateField(\n        pgettext_lazy('voucher', 'start date'), default=date.today)\n    end_date = models.DateField(\n        pgettext_lazy('voucher', 'end date'), null=True, blank=True)\n\n    discount_value_type = models.CharField(\n        pgettext_lazy('voucher', 'discount type'), max_length=10,\n        choices=DISCOUNT_VALUE_TYPE_CHOICES, default=DISCOUNT_VALUE_FIXED)\n    discount_value = models.DecimalField(\n        pgettext_lazy('voucher', 'discount value'), max_digits=12,\n        decimal_places=2)\n\n    # not mandatory fields, usage depends on type\n    product = models.ForeignKey('product.Product', blank=True, null=True)\n    category = models.ForeignKey('product.Category', blank=True, null=True)\n    apply_to = models.CharField(max_length=20, blank=True, null=True)\n    limit = PriceField(max_digits=12, decimal_places=2, null=True,\n                       blank=True, currency=settings.DEFAULT_CURRENCY)\n\n    objects = VoucherQueryset.as_manager()\n\n    @property\n    def is_free(self):\n        return (self.discount_value == Decimal(100) and\n                self.discount_value_type == Voucher.DISCOUNT_VALUE_PERCENTAGE)\n\n    def __str__(self):\n        if self.name:\n            return self.name\n        discount = '%s%s' % (\n            self.discount_value, self.get_discount_value_type_display())\n        if self.type == Voucher.SHIPPING_TYPE:\n            if self.is_free:\n                return pgettext('voucher', 'Free shipping')\n            else:\n                return pgettext('voucher', '%(discount)s off shipping') % {\n                    'discount': discount}\n        if self.type == Voucher.PRODUCT_TYPE:\n            return pgettext('voucher', '%(discount)s off %(product)s') % {\n                'discount': discount, 'product': self.product}\n        if self.type == Voucher.CATEGORY_TYPE:\n            return pgettext('voucher', '%(discount)s off %(category)s') % {\n                'discount': discount, 'category': self.category}\n        return pgettext('voucher', '%(discount)s off') % {'discount': discount}\n\n    def get_apply_to_display(self):\n        if self.type == Voucher.SHIPPING_TYPE and self.apply_to:\n            return countries.name(self.apply_to)\n        if self.type == Voucher.SHIPPING_TYPE:\n            return pgettext('voucher', 'Any country')\n        if self.apply_to and self.type in {\n                Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE}:\n            choices = dict(self.APPLY_TO_PRODUCT_CHOICES)\n            return choices[self.apply_to]\n\n    def get_fixed_discount_for(self, amount):\n        if self.discount_value_type == self.DISCOUNT_VALUE_FIXED:\n            discount_price = Price(net=self.discount_value,\n                                   currency=settings.DEFAULT_CURRENCY)\n            discount = FixedDiscount(\n                amount=discount_price, name=smart_text(self))\n        elif self.discount_value_type == self.DISCOUNT_VALUE_PERCENTAGE:\n            discount = percentage_discount(\n                value=self.discount_value, name=smart_text(self))\n            fixed_discount_value = amount - discount.apply(amount)\n            discount = FixedDiscount(\n                amount=fixed_discount_value, name=smart_text(self))\n        else:\n            raise NotImplementedError('Unknown discount value type')\n        if discount.amount > amount:\n            return FixedDiscount(amount, name=smart_text(self))\n        else:\n            return discount\n\n    def get_discount_for_checkout(self, checkout):\n        if self.type == Voucher.VALUE_TYPE:\n            cart_total = checkout.cart.get_total()\n            limit = self.limit if self.limit is not None else cart_total\n            if cart_total < limit:\n                msg = pgettext(\n                    'voucher',\n                    'This offer is only valid for orders over %(amount)s.')\n                raise NotApplicable(msg % {'amount': net(limit)})\n            return self.get_fixed_discount_for(cart_total)\n\n        elif self.type == Voucher.SHIPPING_TYPE:\n            if not checkout.is_shipping_required:\n                msg = pgettext(\n                    'voucher', 'Your order does not require shipping.')\n                raise NotApplicable(msg)\n            shipping_method = checkout.shipping_method\n            if not shipping_method:\n                msg = pgettext(\n                    'voucher', 'Please select a shipping method first.')\n                raise NotApplicable(msg)\n            if (self.apply_to and\n                    shipping_method.country_code != self.apply_to):\n                msg = pgettext(\n                    'voucher', 'This offer is only valid in %(country)s.')\n                raise NotApplicable(msg % {\n                    'country': self.get_apply_to_display()})\n            if self.limit is not None and shipping_method.price > self.limit:\n                msg = pgettext(\n                    'voucher',\n                    'This offer is only valid for shipping over %(amount)s.')\n                raise NotApplicable(msg % {'amount': net(self.limit)})\n            return self.get_fixed_discount_for(shipping_method.price)\n\n        elif self.type in (Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE):\n            if self.type == Voucher.PRODUCT_TYPE:\n                prices = list(\n                    (item[1] for item in get_product_variants_and_prices(\n                        checkout.cart, self.product)))\n            else:\n                prices = list(\n                    (item[1] for item in get_category_variants_and_prices(\n                        checkout.cart, self.category)))\n            if len(prices) == 0:\n                msg = pgettext(\n                    'voucher', 'This offer is only valid for selected items.')\n                raise NotApplicable(msg)\n            if self.apply_to == Voucher.APPLY_TO_ALL_PRODUCTS:\n                discounts = (\n                    self.get_fixed_discount_for(price) for price in prices)\n                discount_total = sum(\n                    (discount.amount for discount in discounts),\n                    Price(0, currency=settings.DEFAULT_CURRENCY))\n                return FixedDiscount(discount_total, smart_text(self))\n            else:\n                product_total = sum(\n                    prices, Price(0, currency=settings.DEFAULT_CURRENCY))\n                return self.get_fixed_discount_for(product_total)\n\n        else:\n            raise NotImplementedError('Unknown discount type')\n\n\n@python_2_unicode_compatible\nclass Sale(models.Model):\n    FIXED = 'fixed'\n    PERCENTAGE = 'percentage'\n\n    DISCOUNT_TYPE_CHOICES = (\n        (FIXED, pgettext_lazy('discount_type', settings.DEFAULT_CURRENCY)),\n        (PERCENTAGE, pgettext_lazy('discount_type', '%')))\n\n    name = models.CharField(max_length=255)\n    type = models.CharField(max_length=10, choices=DISCOUNT_TYPE_CHOICES,\n                            default=FIXED)\n    value = models.DecimalField(max_digits=12, decimal_places=2, default=0)\n    products = models.ManyToManyField('product.Product', blank=True)\n    categories = models.ManyToManyField('product.Category', blank=True)\n\n    class Meta:\n        app_label = 'discount'\n\n    def __repr__(self):\n        return 'Sale(name=%r, value=%r, type=%s)' % (\n            str(self.name), self.value, self.get_type_display())\n\n    def __str__(self):\n        return self.name\n\n    def get_discount(self):\n        if self.type == self.FIXED:\n            discount_price = Price(net=self.value,\n                                   currency=settings.DEFAULT_CURRENCY)\n            return FixedDiscount(amount=discount_price, name=self.name)\n        elif self.type == self.PERCENTAGE:\n            return percentage_discount(value=self.value, name=self.name)\n        raise NotImplementedError('Unknown discount type')\n\n    def _product_has_category_discount(self, product, discounted_categories):\n        for category in product.categories.all():\n            for discounted_category in discounted_categories:\n                if category.is_descendant_of(discounted_category,\n                                             include_self=True):\n                    return True\n        return False\n\n    def modifier_for_variant(self, variant):\n        discounted_products = [p.pk for p in self.products.all()]\n        discounted_categories = list(self.categories.all())\n        if discounted_products and variant.product.pk not in discounted_products:\n            raise NotApplicable('Discount not applicable for this product')\n        if (discounted_categories and not\n            self._product_has_category_discount(\n                variant.product, discounted_categories)):\n            raise NotApplicable('Discount too high for this product')\n        return self.get_discount()\n\n\ndef get_variant_discounts(variant, discounts, **kwargs):\n    for discount in discounts:\n        try:\n            yield discount.modifier_for_variant(variant, **kwargs)\n        except NotApplicable:\n            pass\n", "path": "saleor/discount/models.py"}]}