We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Refactor passing possible sort_by fields to the category page
Currently, we have a function `get_sort_by_choices`, that constructs sort_by choices from product filter object. I think this could be done much simpler, by using `saleor.product.filters.SORT_BY_FIELDS`, which already have the field names and translated labels that should be displayed in the UI.

--- 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/product/filters.py`
Content:
```
1 from collections import OrderedDict
2 
3 from django.forms import CheckboxSelectMultiple, ValidationError
4 from django.utils.translation import pgettext_lazy
5 from django_filters import MultipleChoiceFilter, OrderingFilter, RangeFilter
6 from django_prices.models import PriceField
7 
8 from ..core.filters import SortedFilterSet
9 from .models import Product, ProductAttribute
10 
11 SORT_BY_FIELDS = {
12     'name': pgettext_lazy('Product list sorting option', 'name'),
13     'price': pgettext_lazy('Product list sorting option', 'price')}
14 
15 
16 class ProductFilter(SortedFilterSet):
17     sort_by = OrderingFilter(
18         label=pgettext_lazy('Product list sorting form', 'Sort by'),
19         fields=SORT_BY_FIELDS.keys(),
20         field_labels=SORT_BY_FIELDS)
21 
22     class Meta:
23         model = Product
24         fields = ['price']
25         filter_overrides = {PriceField: {'filter_class': RangeFilter}}
26 
27     def __init__(self, *args, **kwargs):
28         self.category = kwargs.pop('category')
29         super().__init__(*args, **kwargs)
30         self.product_attributes, self.variant_attributes = (
31             self._get_attributes())
32         self.filters.update(self._get_product_attributes_filters())
33         self.filters.update(self._get_product_variants_attributes_filters())
34         self.filters = OrderedDict(sorted(self.filters.items()))
35         self.form.fields['sort_by'].validators.append(self.validate_sort_by)
36 
37     def _get_attributes(self):
38         product_attributes = (
39             ProductAttribute.objects.all()
40             .prefetch_related('values')
41             .filter(products_class__products__categories=self.category)
42             .distinct())
43         variant_attributes = (
44             ProductAttribute.objects.all()
45             .prefetch_related('values')
46             .filter(product_variants_class__products__categories=self.category)
47             .distinct())
48         return product_attributes, variant_attributes
49 
50     def _get_product_attributes_filters(self):
51         filters = {}
52         for attribute in self.product_attributes:
53             filters[attribute.slug] = MultipleChoiceFilter(
54                 name='attributes__%s' % attribute.pk,
55                 label=attribute.name,
56                 widget=CheckboxSelectMultiple,
57                 choices=self._get_attribute_choices(attribute))
58         return filters
59 
60     def _get_product_variants_attributes_filters(self):
61         filters = {}
62         for attribute in self.variant_attributes:
63             filters[attribute.slug] = MultipleChoiceFilter(
64                 name='variants__attributes__%s' % attribute.pk,
65                 label=attribute.name,
66                 widget=CheckboxSelectMultiple,
67                 choices=self._get_attribute_choices(attribute))
68         return filters
69 
70     def _get_attribute_choices(self, attribute):
71         return [(choice.pk, choice.name) for choice in attribute.values.all()]
72 
73     def validate_sort_by(self, value):
74         if value.strip('-') not in SORT_BY_FIELDS:
75             raise ValidationError(
76                 pgettext_lazy(
77                     'Validation error for sort_by filter',
78                     '%(value)s is not a valid sorting option'),
79                 params={'value': value})
80
```

Path: `saleor/core/utils/filters.py`
Content:
```
1 def get_sort_by_choices(filter_set):
2     return [(choice[0], choice[1].lower()) for choice in
3             list(filter_set.filters['sort_by'].field.choices)[1::2]]
4 
5 
6 def get_now_sorted_by(filter_set, default_sort='name'):
7     sort_by = filter_set.form.cleaned_data.get('sort_by')
8     if sort_by:
9         sort_by = sort_by[0].strip('-')
10     else:
11         sort_by = default_sort
12     return sort_by
13
```

Path: `saleor/product/views.py`
Content:
```
1 import datetime
2 import json
3 
4 from django.conf import settings
5 from django.http import HttpResponsePermanentRedirect, JsonResponse
6 from django.shortcuts import get_object_or_404, redirect
7 from django.template.response import TemplateResponse
8 from django.urls import reverse
9 
10 from ..cart.utils import set_cart_cookie
11 from ..core.utils import get_paginator_items, serialize_decimal
12 from ..core.utils.filters import get_now_sorted_by, get_sort_by_choices
13 from .filters import ProductFilter, SORT_BY_FIELDS
14 from .models import Category
15 from .utils import (
16     get_availability, get_product_attributes_data, get_product_images,
17     get_variant_picker_data, handle_cart_form, product_json_ld,
18     products_for_cart, products_with_availability, products_with_details)
19 
20 
21 def product_details(request, slug, product_id, form=None):
22     """Product details page
23 
24     The following variables are available to the template:
25 
26     product:
27         The Product instance itself.
28 
29     is_visible:
30         Whether the product is visible to regular users (for cases when an
31         admin is previewing a product before publishing).
32 
33     form:
34         The add-to-cart form.
35 
36     price_range:
37         The PriceRange for the product including all discounts.
38 
39     undiscounted_price_range:
40         The PriceRange excluding all discounts.
41 
42     discount:
43         Either a Price instance equal to the discount value or None if no
44         discount was available.
45 
46     local_price_range:
47         The same PriceRange from price_range represented in user's local
48         currency. The value will be None if exchange rate is not available or
49         the local currency is the same as site's default currency.
50     """
51     products = products_with_details(user=request.user)
52     product = get_object_or_404(products, id=product_id)
53     if product.get_slug() != slug:
54         return HttpResponsePermanentRedirect(product.get_absolute_url())
55     today = datetime.date.today()
56     is_visible = (
57         product.available_on is None or product.available_on <= today)
58     if form is None:
59         form = handle_cart_form(request, product, create_cart=False)[0]
60     availability = get_availability(product, discounts=request.discounts,
61                                     local_currency=request.currency)
62     product_images = get_product_images(product)
63     variant_picker_data = get_variant_picker_data(
64         product, request.discounts, request.currency)
65     product_attributes = get_product_attributes_data(product)
66     # show_variant_picker determines if variant picker is used or select input
67     show_variant_picker = all([v.attributes for v in product.variants.all()])
68     json_ld_data = product_json_ld(product, product_attributes)
69     return TemplateResponse(
70         request, 'product/details.html',
71         {'is_visible': is_visible,
72          'form': form,
73          'availability': availability,
74          'product': product,
75          'product_attributes': product_attributes,
76          'product_images': product_images,
77          'show_variant_picker': show_variant_picker,
78          'variant_picker_data': json.dumps(
79              variant_picker_data, default=serialize_decimal),
80          'json_ld_product_data': json.dumps(
81              json_ld_data, default=serialize_decimal)})
82 
83 
84 def product_add_to_cart(request, slug, product_id):
85     # types: (int, str, dict) -> None
86 
87     if not request.method == 'POST':
88         return redirect(reverse(
89             'product:details',
90             kwargs={'product_id': product_id, 'slug': slug}))
91 
92     products = products_for_cart(user=request.user)
93     product = get_object_or_404(products, pk=product_id)
94     form, cart = handle_cart_form(request, product, create_cart=True)
95     if form.is_valid():
96         form.save()
97         if request.is_ajax():
98             response = JsonResponse({'next': reverse('cart:index')}, status=200)
99         else:
100             response = redirect('cart:index')
101     else:
102         if request.is_ajax():
103             response = JsonResponse({'error': form.errors}, status=400)
104         else:
105             response = product_details(request, slug, product_id, form)
106     if not request.user.is_authenticated:
107         set_cart_cookie(cart, response)
108     return response
109 
110 
111 def category_index(request, path, category_id):
112     category = get_object_or_404(Category, id=category_id)
113     actual_path = category.get_full_path()
114     if actual_path != path:
115         return redirect('product:category', permanent=True, path=actual_path,
116                         category_id=category_id)
117     products = (products_with_details(user=request.user)
118                 .filter(categories__id=category.id)
119                 .order_by('name'))
120     product_filter = ProductFilter(
121         request.GET, queryset=products, category=category)
122     products_paginated = get_paginator_items(
123         product_filter.qs, settings.PAGINATE_BY, request.GET.get('page'))
124     products_and_availability = list(products_with_availability(
125         products_paginated, request.discounts, request.currency))
126     now_sorted_by = get_now_sorted_by(product_filter)
127     arg_sort_by = request.GET.get('sort_by')
128     is_descending = arg_sort_by.startswith('-') if arg_sort_by else False
129     ctx = {'category': category, 'filter_set': product_filter,
130            'products': products_and_availability,
131            'products_paginated': products_paginated,
132            'sort_by_choices': get_sort_by_choices(product_filter),
133            'now_sorted_by': now_sorted_by,
134            'is_descending': is_descending}
135     return TemplateResponse(request, 'category/index.html', ctx)
136
```
--- 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/core/utils/filters.py b/saleor/core/utils/filters.py
--- a/saleor/core/utils/filters.py
+++ b/saleor/core/utils/filters.py
@@ -1,8 +1,3 @@
-def get_sort_by_choices(filter_set):
-    return [(choice[0], choice[1].lower()) for choice in
-            list(filter_set.filters['sort_by'].field.choices)[1::2]]
-
-
 def get_now_sorted_by(filter_set, default_sort='name'):
     sort_by = filter_set.form.cleaned_data.get('sort_by')
     if sort_by:
diff --git a/saleor/product/filters.py b/saleor/product/filters.py
--- a/saleor/product/filters.py
+++ b/saleor/product/filters.py
@@ -8,9 +8,10 @@
 from ..core.filters import SortedFilterSet
 from .models import Product, ProductAttribute
 
-SORT_BY_FIELDS = {
-    'name': pgettext_lazy('Product list sorting option', 'name'),
-    'price': pgettext_lazy('Product list sorting option', 'price')}
+
+SORT_BY_FIELDS = OrderedDict([
+    ('name', pgettext_lazy('Product list sorting option', 'name')),
+    ('price', pgettext_lazy('Product list sorting option', 'price'))])
 
 
 class ProductFilter(SortedFilterSet):
diff --git a/saleor/product/views.py b/saleor/product/views.py
--- a/saleor/product/views.py
+++ b/saleor/product/views.py
@@ -9,7 +9,7 @@
 
 from ..cart.utils import set_cart_cookie
 from ..core.utils import get_paginator_items, serialize_decimal
-from ..core.utils.filters import get_now_sorted_by, get_sort_by_choices
+from ..core.utils.filters import get_now_sorted_by
 from .filters import ProductFilter, SORT_BY_FIELDS
 from .models import Category
 from .utils import (
@@ -129,7 +129,7 @@
     ctx = {'category': category, 'filter_set': product_filter,
            'products': products_and_availability,
            'products_paginated': products_paginated,
-           'sort_by_choices': get_sort_by_choices(product_filter),
+           'sort_by_choices': SORT_BY_FIELDS,
            'now_sorted_by': now_sorted_by,
            'is_descending': is_descending}
     return TemplateResponse(request, 'category/index.html', ctx)

{"golden_diff": "diff --git a/saleor/core/utils/filters.py b/saleor/core/utils/filters.py\n--- a/saleor/core/utils/filters.py\n+++ b/saleor/core/utils/filters.py\n@@ -1,8 +1,3 @@\n-def get_sort_by_choices(filter_set):\n-    return [(choice[0], choice[1].lower()) for choice in\n-            list(filter_set.filters['sort_by'].field.choices)[1::2]]\n-\n-\n def get_now_sorted_by(filter_set, default_sort='name'):\n     sort_by = filter_set.form.cleaned_data.get('sort_by')\n     if sort_by:\ndiff --git a/saleor/product/filters.py b/saleor/product/filters.py\n--- a/saleor/product/filters.py\n+++ b/saleor/product/filters.py\n@@ -8,9 +8,10 @@\n from ..core.filters import SortedFilterSet\n from .models import Product, ProductAttribute\n \n-SORT_BY_FIELDS = {\n-    'name': pgettext_lazy('Product list sorting option', 'name'),\n-    'price': pgettext_lazy('Product list sorting option', 'price')}\n+\n+SORT_BY_FIELDS = OrderedDict([\n+    ('name', pgettext_lazy('Product list sorting option', 'name')),\n+    ('price', pgettext_lazy('Product list sorting option', 'price'))])\n \n \n class ProductFilter(SortedFilterSet):\ndiff --git a/saleor/product/views.py b/saleor/product/views.py\n--- a/saleor/product/views.py\n+++ b/saleor/product/views.py\n@@ -9,7 +9,7 @@\n \n from ..cart.utils import set_cart_cookie\n from ..core.utils import get_paginator_items, serialize_decimal\n-from ..core.utils.filters import get_now_sorted_by, get_sort_by_choices\n+from ..core.utils.filters import get_now_sorted_by\n from .filters import ProductFilter, SORT_BY_FIELDS\n from .models import Category\n from .utils import (\n@@ -129,7 +129,7 @@\n     ctx = {'category': category, 'filter_set': product_filter,\n            'products': products_and_availability,\n            'products_paginated': products_paginated,\n-           'sort_by_choices': get_sort_by_choices(product_filter),\n+           'sort_by_choices': SORT_BY_FIELDS,\n            'now_sorted_by': now_sorted_by,\n            'is_descending': is_descending}\n     return TemplateResponse(request, 'category/index.html', ctx)\n", "issue": "Refactor passing possible sort_by fields to the category page\nCurrently, we have a function `get_sort_by_choices`, that constructs sort_by choices from product filter object. I think this could be done much simpler, by using `saleor.product.filters.SORT_BY_FIELDS`, which already have the field names and translated labels that should be displayed in the UI.\n", "before_files": [{"content": "from collections import OrderedDict\n\nfrom django.forms import CheckboxSelectMultiple, ValidationError\nfrom django.utils.translation import pgettext_lazy\nfrom django_filters import MultipleChoiceFilter, OrderingFilter, RangeFilter\nfrom django_prices.models import PriceField\n\nfrom ..core.filters import SortedFilterSet\nfrom .models import Product, ProductAttribute\n\nSORT_BY_FIELDS = {\n    'name': pgettext_lazy('Product list sorting option', 'name'),\n    'price': pgettext_lazy('Product list sorting option', 'price')}\n\n\nclass ProductFilter(SortedFilterSet):\n    sort_by = OrderingFilter(\n        label=pgettext_lazy('Product list sorting form', 'Sort by'),\n        fields=SORT_BY_FIELDS.keys(),\n        field_labels=SORT_BY_FIELDS)\n\n    class Meta:\n        model = Product\n        fields = ['price']\n        filter_overrides = {PriceField: {'filter_class': RangeFilter}}\n\n    def __init__(self, *args, **kwargs):\n        self.category = kwargs.pop('category')\n        super().__init__(*args, **kwargs)\n        self.product_attributes, self.variant_attributes = (\n            self._get_attributes())\n        self.filters.update(self._get_product_attributes_filters())\n        self.filters.update(self._get_product_variants_attributes_filters())\n        self.filters = OrderedDict(sorted(self.filters.items()))\n        self.form.fields['sort_by'].validators.append(self.validate_sort_by)\n\n    def _get_attributes(self):\n        product_attributes = (\n            ProductAttribute.objects.all()\n            .prefetch_related('values')\n            .filter(products_class__products__categories=self.category)\n            .distinct())\n        variant_attributes = (\n            ProductAttribute.objects.all()\n            .prefetch_related('values')\n            .filter(product_variants_class__products__categories=self.category)\n            .distinct())\n        return product_attributes, variant_attributes\n\n    def _get_product_attributes_filters(self):\n        filters = {}\n        for attribute in self.product_attributes:\n            filters[attribute.slug] = MultipleChoiceFilter(\n                name='attributes__%s' % attribute.pk,\n                label=attribute.name,\n                widget=CheckboxSelectMultiple,\n                choices=self._get_attribute_choices(attribute))\n        return filters\n\n    def _get_product_variants_attributes_filters(self):\n        filters = {}\n        for attribute in self.variant_attributes:\n            filters[attribute.slug] = MultipleChoiceFilter(\n                name='variants__attributes__%s' % attribute.pk,\n                label=attribute.name,\n                widget=CheckboxSelectMultiple,\n                choices=self._get_attribute_choices(attribute))\n        return filters\n\n    def _get_attribute_choices(self, attribute):\n        return [(choice.pk, choice.name) for choice in attribute.values.all()]\n\n    def validate_sort_by(self, value):\n        if value.strip('-') not in SORT_BY_FIELDS:\n            raise ValidationError(\n                pgettext_lazy(\n                    'Validation error for sort_by filter',\n                    '%(value)s is not a valid sorting option'),\n                params={'value': value})\n", "path": "saleor/product/filters.py"}, {"content": "def get_sort_by_choices(filter_set):\n    return [(choice[0], choice[1].lower()) for choice in\n            list(filter_set.filters['sort_by'].field.choices)[1::2]]\n\n\ndef get_now_sorted_by(filter_set, default_sort='name'):\n    sort_by = filter_set.form.cleaned_data.get('sort_by')\n    if sort_by:\n        sort_by = sort_by[0].strip('-')\n    else:\n        sort_by = default_sort\n    return sort_by\n", "path": "saleor/core/utils/filters.py"}, {"content": "import datetime\nimport json\n\nfrom django.conf import settings\nfrom django.http import HttpResponsePermanentRedirect, JsonResponse\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse\n\nfrom ..cart.utils import set_cart_cookie\nfrom ..core.utils import get_paginator_items, serialize_decimal\nfrom ..core.utils.filters import get_now_sorted_by, get_sort_by_choices\nfrom .filters import ProductFilter, SORT_BY_FIELDS\nfrom .models import Category\nfrom .utils import (\n    get_availability, get_product_attributes_data, get_product_images,\n    get_variant_picker_data, handle_cart_form, product_json_ld,\n    products_for_cart, products_with_availability, products_with_details)\n\n\ndef product_details(request, slug, product_id, form=None):\n    \"\"\"Product details page\n\n    The following variables are available to the template:\n\n    product:\n        The Product instance itself.\n\n    is_visible:\n        Whether the product is visible to regular users (for cases when an\n        admin is previewing a product before publishing).\n\n    form:\n        The add-to-cart form.\n\n    price_range:\n        The PriceRange for the product including all discounts.\n\n    undiscounted_price_range:\n        The PriceRange excluding all discounts.\n\n    discount:\n        Either a Price instance equal to the discount value or None if no\n        discount was available.\n\n    local_price_range:\n        The same PriceRange from price_range represented in user's local\n        currency. The value will be None if exchange rate is not available or\n        the local currency is the same as site's default currency.\n    \"\"\"\n    products = products_with_details(user=request.user)\n    product = get_object_or_404(products, id=product_id)\n    if product.get_slug() != slug:\n        return HttpResponsePermanentRedirect(product.get_absolute_url())\n    today = datetime.date.today()\n    is_visible = (\n        product.available_on is None or product.available_on <= today)\n    if form is None:\n        form = handle_cart_form(request, product, create_cart=False)[0]\n    availability = get_availability(product, discounts=request.discounts,\n                                    local_currency=request.currency)\n    product_images = get_product_images(product)\n    variant_picker_data = get_variant_picker_data(\n        product, request.discounts, request.currency)\n    product_attributes = get_product_attributes_data(product)\n    # show_variant_picker determines if variant picker is used or select input\n    show_variant_picker = all([v.attributes for v in product.variants.all()])\n    json_ld_data = product_json_ld(product, product_attributes)\n    return TemplateResponse(\n        request, 'product/details.html',\n        {'is_visible': is_visible,\n         'form': form,\n         'availability': availability,\n         'product': product,\n         'product_attributes': product_attributes,\n         'product_images': product_images,\n         'show_variant_picker': show_variant_picker,\n         'variant_picker_data': json.dumps(\n             variant_picker_data, default=serialize_decimal),\n         'json_ld_product_data': json.dumps(\n             json_ld_data, default=serialize_decimal)})\n\n\ndef product_add_to_cart(request, slug, product_id):\n    # types: (int, str, dict) -> None\n\n    if not request.method == 'POST':\n        return redirect(reverse(\n            'product:details',\n            kwargs={'product_id': product_id, 'slug': slug}))\n\n    products = products_for_cart(user=request.user)\n    product = get_object_or_404(products, pk=product_id)\n    form, cart = handle_cart_form(request, product, create_cart=True)\n    if form.is_valid():\n        form.save()\n        if request.is_ajax():\n            response = JsonResponse({'next': reverse('cart:index')}, status=200)\n        else:\n            response = redirect('cart:index')\n    else:\n        if request.is_ajax():\n            response = JsonResponse({'error': form.errors}, status=400)\n        else:\n            response = product_details(request, slug, product_id, form)\n    if not request.user.is_authenticated:\n        set_cart_cookie(cart, response)\n    return response\n\n\ndef category_index(request, path, category_id):\n    category = get_object_or_404(Category, id=category_id)\n    actual_path = category.get_full_path()\n    if actual_path != path:\n        return redirect('product:category', permanent=True, path=actual_path,\n                        category_id=category_id)\n    products = (products_with_details(user=request.user)\n                .filter(categories__id=category.id)\n                .order_by('name'))\n    product_filter = ProductFilter(\n        request.GET, queryset=products, category=category)\n    products_paginated = get_paginator_items(\n        product_filter.qs, settings.PAGINATE_BY, request.GET.get('page'))\n    products_and_availability = list(products_with_availability(\n        products_paginated, request.discounts, request.currency))\n    now_sorted_by = get_now_sorted_by(product_filter)\n    arg_sort_by = request.GET.get('sort_by')\n    is_descending = arg_sort_by.startswith('-') if arg_sort_by else False\n    ctx = {'category': category, 'filter_set': product_filter,\n           'products': products_and_availability,\n           'products_paginated': products_paginated,\n           'sort_by_choices': get_sort_by_choices(product_filter),\n           'now_sorted_by': now_sorted_by,\n           'is_descending': is_descending}\n    return TemplateResponse(request, 'category/index.html', ctx)\n", "path": "saleor/product/views.py"}], "after_files": [{"content": "from collections import OrderedDict\n\nfrom django.forms import CheckboxSelectMultiple, ValidationError\nfrom django.utils.translation import pgettext_lazy\nfrom django_filters import MultipleChoiceFilter, OrderingFilter, RangeFilter\nfrom django_prices.models import PriceField\n\nfrom ..core.filters import SortedFilterSet\nfrom .models import Product, ProductAttribute\n\n\nSORT_BY_FIELDS = OrderedDict([\n    ('name', pgettext_lazy('Product list sorting option', 'name')),\n    ('price', pgettext_lazy('Product list sorting option', 'price'))])\n\n\nclass ProductFilter(SortedFilterSet):\n    sort_by = OrderingFilter(\n        label=pgettext_lazy('Product list sorting form', 'Sort by'),\n        fields=SORT_BY_FIELDS.keys(),\n        field_labels=SORT_BY_FIELDS)\n\n    class Meta:\n        model = Product\n        fields = ['price']\n        filter_overrides = {PriceField: {'filter_class': RangeFilter}}\n\n    def __init__(self, *args, **kwargs):\n        self.category = kwargs.pop('category')\n        super().__init__(*args, **kwargs)\n        self.product_attributes, self.variant_attributes = (\n            self._get_attributes())\n        self.filters.update(self._get_product_attributes_filters())\n        self.filters.update(self._get_product_variants_attributes_filters())\n        self.filters = OrderedDict(sorted(self.filters.items()))\n        self.form.fields['sort_by'].validators.append(self.validate_sort_by)\n\n    def _get_attributes(self):\n        product_attributes = (\n            ProductAttribute.objects.all()\n            .prefetch_related('values')\n            .filter(products_class__products__categories=self.category)\n            .distinct())\n        variant_attributes = (\n            ProductAttribute.objects.all()\n            .prefetch_related('values')\n            .filter(product_variants_class__products__categories=self.category)\n            .distinct())\n        return product_attributes, variant_attributes\n\n    def _get_product_attributes_filters(self):\n        filters = {}\n        for attribute in self.product_attributes:\n            filters[attribute.slug] = MultipleChoiceFilter(\n                name='attributes__%s' % attribute.pk,\n                label=attribute.name,\n                widget=CheckboxSelectMultiple,\n                choices=self._get_attribute_choices(attribute))\n        return filters\n\n    def _get_product_variants_attributes_filters(self):\n        filters = {}\n        for attribute in self.variant_attributes:\n            filters[attribute.slug] = MultipleChoiceFilter(\n                name='variants__attributes__%s' % attribute.pk,\n                label=attribute.name,\n                widget=CheckboxSelectMultiple,\n                choices=self._get_attribute_choices(attribute))\n        return filters\n\n    def _get_attribute_choices(self, attribute):\n        return [(choice.pk, choice.name) for choice in attribute.values.all()]\n\n    def validate_sort_by(self, value):\n        if value.strip('-') not in SORT_BY_FIELDS:\n            raise ValidationError(\n                pgettext_lazy(\n                    'Validation error for sort_by filter',\n                    '%(value)s is not a valid sorting option'),\n                params={'value': value})\n", "path": "saleor/product/filters.py"}, {"content": "def get_now_sorted_by(filter_set, default_sort='name'):\n    sort_by = filter_set.form.cleaned_data.get('sort_by')\n    if sort_by:\n        sort_by = sort_by[0].strip('-')\n    else:\n        sort_by = default_sort\n    return sort_by\n", "path": "saleor/core/utils/filters.py"}, {"content": "import datetime\nimport json\n\nfrom django.conf import settings\nfrom django.http import HttpResponsePermanentRedirect, JsonResponse\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse\n\nfrom ..cart.utils import set_cart_cookie\nfrom ..core.utils import get_paginator_items, serialize_decimal\nfrom ..core.utils.filters import get_now_sorted_by\nfrom .filters import ProductFilter, SORT_BY_FIELDS\nfrom .models import Category\nfrom .utils import (\n    get_availability, get_product_attributes_data, get_product_images,\n    get_variant_picker_data, handle_cart_form, product_json_ld,\n    products_for_cart, products_with_availability, products_with_details)\n\n\ndef product_details(request, slug, product_id, form=None):\n    \"\"\"Product details page\n\n    The following variables are available to the template:\n\n    product:\n        The Product instance itself.\n\n    is_visible:\n        Whether the product is visible to regular users (for cases when an\n        admin is previewing a product before publishing).\n\n    form:\n        The add-to-cart form.\n\n    price_range:\n        The PriceRange for the product including all discounts.\n\n    undiscounted_price_range:\n        The PriceRange excluding all discounts.\n\n    discount:\n        Either a Price instance equal to the discount value or None if no\n        discount was available.\n\n    local_price_range:\n        The same PriceRange from price_range represented in user's local\n        currency. The value will be None if exchange rate is not available or\n        the local currency is the same as site's default currency.\n    \"\"\"\n    products = products_with_details(user=request.user)\n    product = get_object_or_404(products, id=product_id)\n    if product.get_slug() != slug:\n        return HttpResponsePermanentRedirect(product.get_absolute_url())\n    today = datetime.date.today()\n    is_visible = (\n        product.available_on is None or product.available_on <= today)\n    if form is None:\n        form = handle_cart_form(request, product, create_cart=False)[0]\n    availability = get_availability(product, discounts=request.discounts,\n                                    local_currency=request.currency)\n    product_images = get_product_images(product)\n    variant_picker_data = get_variant_picker_data(\n        product, request.discounts, request.currency)\n    product_attributes = get_product_attributes_data(product)\n    # show_variant_picker determines if variant picker is used or select input\n    show_variant_picker = all([v.attributes for v in product.variants.all()])\n    json_ld_data = product_json_ld(product, product_attributes)\n    return TemplateResponse(\n        request, 'product/details.html',\n        {'is_visible': is_visible,\n         'form': form,\n         'availability': availability,\n         'product': product,\n         'product_attributes': product_attributes,\n         'product_images': product_images,\n         'show_variant_picker': show_variant_picker,\n         'variant_picker_data': json.dumps(\n             variant_picker_data, default=serialize_decimal),\n         'json_ld_product_data': json.dumps(\n             json_ld_data, default=serialize_decimal)})\n\n\ndef product_add_to_cart(request, slug, product_id):\n    # types: (int, str, dict) -> None\n\n    if not request.method == 'POST':\n        return redirect(reverse(\n            'product:details',\n            kwargs={'product_id': product_id, 'slug': slug}))\n\n    products = products_for_cart(user=request.user)\n    product = get_object_or_404(products, pk=product_id)\n    form, cart = handle_cart_form(request, product, create_cart=True)\n    if form.is_valid():\n        form.save()\n        if request.is_ajax():\n            response = JsonResponse({'next': reverse('cart:index')}, status=200)\n        else:\n            response = redirect('cart:index')\n    else:\n        if request.is_ajax():\n            response = JsonResponse({'error': form.errors}, status=400)\n        else:\n            response = product_details(request, slug, product_id, form)\n    if not request.user.is_authenticated:\n        set_cart_cookie(cart, response)\n    return response\n\n\ndef category_index(request, path, category_id):\n    category = get_object_or_404(Category, id=category_id)\n    actual_path = category.get_full_path()\n    if actual_path != path:\n        return redirect('product:category', permanent=True, path=actual_path,\n                        category_id=category_id)\n    products = (products_with_details(user=request.user)\n                .filter(categories__id=category.id)\n                .order_by('name'))\n    product_filter = ProductFilter(\n        request.GET, queryset=products, category=category)\n    products_paginated = get_paginator_items(\n        product_filter.qs, settings.PAGINATE_BY, request.GET.get('page'))\n    products_and_availability = list(products_with_availability(\n        products_paginated, request.discounts, request.currency))\n    now_sorted_by = get_now_sorted_by(product_filter)\n    arg_sort_by = request.GET.get('sort_by')\n    is_descending = arg_sort_by.startswith('-') if arg_sort_by else False\n    ctx = {'category': category, 'filter_set': product_filter,\n           'products': products_and_availability,\n           'products_paginated': products_paginated,\n           'sort_by_choices': SORT_BY_FIELDS,\n           'now_sorted_by': now_sorted_by,\n           'is_descending': is_descending}\n    return TemplateResponse(request, 'category/index.html', ctx)\n", "path": "saleor/product/views.py"}]}