internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: saleor/saleor

Issue:
Add a base template for dashboard modals
Modals are quite a common part of the dashboard UI and since they have a similar (if not the same) structure, we should extract a base template for them. This will help us a lot with maintenance and consistency of the UI. We already have the `base_modal_confirm_delete.html` which can be used as a reference (e.g. in terms of blocks).


Discussion:

Resolving patch:
diff --git a/saleor/dashboard/discount/urls.py b/saleor/dashboard/discount/urls.py
--- a/saleor/dashboard/discount/urls.py
+++ b/saleor/dashboard/discount/urls.py
@@ -8,10 +8,13 @@
     url(r'sale/$', views.sale_list, name='sale-list'),
     url(r'sale/(?P<pk>[0-9]+)/$', views.sale_edit, name='sale-update'),
     url(r'sale/add/$', views.sale_edit, name='sale-add'),
-    url(r'sale/(?P<pk>[0-9]+)/delete/$', views.sale_delete, name='sale-delete'),
+    url(r'sale/(?P<pk>[0-9]+)/delete/$', views.sale_delete,
+        name='sale-delete'),
 
     url(r'voucher/$', views.voucher_list, name='voucher-list'),
-    url(r'voucher/(?P<pk>[0-9]+)/$', views.voucher_edit, name='voucher-update'),
+    url(r'voucher/(?P<pk>[0-9]+)/$', views.voucher_edit,
+        name='voucher-update'),
     url(r'voucher/add/$', views.voucher_edit, name='voucher-add'),
-    url(r'voucher/(?P<pk>[0-9]+)/delete/$', views.voucher_delete, name='voucher-delete'),
+    url(r'voucher/(?P<pk>[0-9]+)/delete/$', views.voucher_delete,
+        name='voucher-delete'),
 ]


Test patch:
Repository: saleor/saleor

Issue:
Add Categories to the Sitemap
Sitemap consists of Products only, we should expand it to include categories as well.


Discussion:

Resolving patch:
diff --git a/saleor/core/sitemaps.py b/saleor/core/sitemaps.py
--- a/saleor/core/sitemaps.py
+++ b/saleor/core/sitemaps.py
@@ -2,7 +2,7 @@
 
 from django.contrib.sitemaps import Sitemap
 
-from ..product.models import Product
+from ..product.models import Category, Product
 
 
 class ProductSitemap(Sitemap):
@@ -11,4 +11,13 @@ def items(self):
         return Product.objects.only('id', 'name').order_by('-id')
 
 
-sitemaps = {'products': ProductSitemap}
+class CategorySitemap(Sitemap):
+
+    def items(self):
+        categories = Category.objects.all().order_by('id')
+        return categories.only('id', 'name', 'slug')
+
+sitemaps = {
+    'categories': CategorySitemap,
+    'products': ProductSitemap
+}


Test patch:
diff --git a/tests/test_sitemap.py b/tests/test_sitemap.py
--- a/tests/test_sitemap.py
+++ b/tests/test_sitemap.py
@@ -5,8 +5,16 @@
 
 
 def test_sitemap_uses_site_settings_domain(client, product_in_stock):
-    url = 'http://%(domain)s%(product_url)s' % {
-        'domain': Site.objects.get_current().domain,
+    domain = Site.objects.get_current().domain
+    product_url = 'http://%(domain)s%(product_url)s' % {
+        'domain': domain,
         'product_url': product_in_stock.get_absolute_url()}
+    category = product_in_stock.categories.first()
+    category_url = 'http://%(domain)s%(category_url)s' % {
+        'domain': domain,
+        'category_url': category.get_absolute_url()}
+    expected_links = [product_url, category_url]
+
     response = client.get(reverse('django.contrib.sitemaps.views.sitemap'))
-    assert response.context['urlset'][0]['location'] == url
+    sitemap_links = [url['location'] for url in response.context['urlset']]
+    assert sorted(sitemap_links) == sorted(expected_links)
Repository: saleor/saleor

Issue:
Schema.org search URL is incorrect
## Incorrect search url
Steps to reproduce:
1. Run server
2. Check Schema.org created by context_processor (last `script` in`body` )
3. Take a look at `target` inside `potentialAction`

Actual result:
    Url contains duplicated slash:
    http://demo.getsaleor.com//search/?q={search_term}

## Incorrect search input name
name typed in `query-input` inside of `potentialAction` should match search input field name


Discussion:

Resolving patch:
diff --git a/saleor/core/context_processors.py b/saleor/core/context_processors.py
--- a/saleor/core/context_processors.py
+++ b/saleor/core/context_processors.py
@@ -9,6 +9,11 @@
 from ..core.utils import build_absolute_uri
 from ..product.models import Category
 
+try:
+    from urllib.parse import urljoin
+except ImportError:
+    from urlparse import urljoin
+
 
 def get_setting_as_dict(name, short_name=None):
     short_name = short_name or name
@@ -44,8 +49,9 @@ def webpage_schema(request):
         'name': site.name,
         'description': site.settings.description}
     if settings.ENABLE_SEARCH:
+        search_url = urljoin(url, reverse('search:search'))
         data['potentialAction'] = {
             '@type': 'SearchAction',
-            'target': '%s%s?q={search_term}' % (url, reverse('search:search')),
-            'query-input': 'required name=search_term'}
+            'target': '%s?q={q}' % search_url,
+            'query-input': 'required name=q'}
     return {'webpage_schema': json.dumps(data)}


Test patch:
Repository: saleor/saleor

Issue:
Refactor displaying success messages in the dashboard
The code responsible for displaying success messages in the dashboard lives in [_messages.html](https://github.com/mirumee/saleor/blob/master/templates/dashboard/includes/_messages.html) template and mixes Django's templating language with JS which isn't very elegant. Instead, there should be a function written entirely in JS that would take care of rendering those messages with data passed from backend through `data-*` attributes.


Discussion:

Resolving patch:
diff --git a/saleor/dashboard/templatetags/utils.py b/saleor/dashboard/templatetags/utils.py
--- a/saleor/dashboard/templatetags/utils.py
+++ b/saleor/dashboard/templatetags/utils.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+from json import dumps
 from urllib.parse import urlencode
 
 from django import forms
@@ -102,3 +104,13 @@ def add_filters(context, filter_set, sort_by_filter_name='sort_by'):
     return {
         'chips': chips, 'filter': filter_set, 'count': filter_set.qs.count(),
         'sort_by': request_get.get(sort_by_filter_name, None)}
+
+
+@register.simple_tag(takes_context=True)
+def serialize_messages(context):
+    """Serialize django.contrib.messages to JSON"""
+    messages = context.get('messages', [])
+    data = {}
+    for i, message in enumerate(messages):
+        data[i] = str(message)
+    return dumps(data)


Test patch:
Repository: saleor/saleor

Issue:
Missing pagination in "Recent orders" view
Results in the "Recent Orders" tab in "Your account" view are not paginated.


Discussion:

Resolving patch:
diff --git a/saleor/userprofile/views.py b/saleor/userprofile/views.py
--- a/saleor/userprofile/views.py
+++ b/saleor/userprofile/views.py
@@ -1,3 +1,4 @@
+from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required
 from django.http import HttpResponseRedirect
@@ -6,6 +7,7 @@
 from django.urls import reverse
 from django.utils.translation import pgettext
 
+from ..core.utils import get_paginator_items
 from .forms import (
     ChangePasswordForm, get_address_form, logout_on_password_change)
 
@@ -13,8 +15,11 @@
 @login_required
 def details(request):
     password_form = get_or_process_password_form(request)
+    orders = request.user.orders.prefetch_related('groups__lines')
+    orders_paginated = get_paginator_items(
+        orders, settings.PAGINATE_BY, request.GET.get('page'))
     ctx = {'addresses': request.user.addresses.all(),
-           'orders': request.user.orders.prefetch_related('groups__lines'),
+           'orders': orders_paginated,
            'change_password_form': password_form}
 
     return TemplateResponse(request, 'userprofile/details.html', ctx)


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -294,6 +294,18 @@ def product_list(product_class, default_category):
 
     return [product_1, product_2, product_3]
 
+from prices import Price
+@pytest.fixture
+def order_list(admin_user, billing_address):
+    data = {
+        'billing_address': billing_address, 'user': admin_user,
+        'user_email': admin_user.email, 'total': Price(123, currency='USD')}
+    order = Order.objects.create(**data)
+    order1 = Order.objects.create(**data)
+    order2 = Order.objects.create(**data)
+
+    return [order, order1, order2]
+
 
 @pytest.fixture
 def stock_location():
diff --git a/tests/test_userprofile.py b/tests/test_userprofile.py
--- a/tests/test_userprofile.py
+++ b/tests/test_userprofile.py
@@ -2,8 +2,10 @@
 
 import i18naddress
 import pytest
+from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.http import QueryDict
+from django.urls import reverse
 
 from saleor.userprofile import forms, i18n, models
 from saleor.userprofile.validators import validate_possible_number
@@ -117,3 +119,16 @@ def test_validate_possible_number(input, exception):
             validate_possible_number(input)
     else:
         validate_possible_number(input)
+
+
+def test_order_with_lines_pagination(admin_client, order_list):
+    settings.PAGINATE_BY = 1
+    data = {'page': '1'}
+    url = reverse('profile:details')
+    response = admin_client.get(url, data)
+    assert response.status_code == 200
+
+    data = {'page': '2'}
+    url = reverse('profile:details')
+    response = admin_client.get(url, data)
+    assert response.status_code == 200
Repository: saleor/saleor

Issue:
Custom 404 page
Right now in demo store we display generic 404 page
https://demo.getsaleor.com/i-do-not-exist/

Could implement something more interesting, with a nice design.

What should be included:
- [ ] Drawing
- [ ] Link to the homepage
- [ ] Search bar (?)
- [ ] ...


Discussion:
helou What about a PIrate like this... 
![saleor_404_01](https://user-images.githubusercontent.com/7837760/33432421-1e0b5ed6-d5d8-11e7-82a2-29296860b14b.png)


![2](https://user-images.githubusercontent.com/7837760/33432879-e9e8eedc-d5d9-11e7-9ce1-7dd17f75edd3.png)

![4](https://user-images.githubusercontent.com/7837760/33432880-ea0b7a6a-d5d9-11e7-86c0-9330fb54daa2.png)


moze 3 obrazki na randomowej rotacji ? 




This picture with desert from @Pacu2 presentation stole my :heart: .
What about the copy for those graphics in random:

Nothing here, comrade!  (@Pacu2 used this one)
It has to be somewhere else!
Nothing in sight comrade! 
"Comrade" is more "Russian revolution" than "pirates and sailors".
/SKARB/
I can’t help but feel I’ve been ripped off. 
I’m sure you're feeling something similar, bucko.

/LUNETA/
Is it over? ...Hello? ...Did we win?



Design style. 
![404-saleor](https://user-images.githubusercontent.com/7837760/33881962-cf94628a-df36-11e7-8759-60175b0bafdb.jpg)

👍  This is fine for the demo page. @subgrafik We could also have some generic 404 page without any branding, that we'll provide by default for development.

Resolving patch:
diff --git a/saleor/core/urls.py b/saleor/core/urls.py
--- a/saleor/core/urls.py
+++ b/saleor/core/urls.py
@@ -8,6 +8,7 @@
 urlpatterns = [
     url(r'^$', views.home, name='home'),
     url(r'^style-guide/', views.styleguide, name='styleguide'),
+    url(r'^404', views.handle_404, name='404'),
     url(r'^impersonate/(?P<uid>\d+)/', views.impersonate,
         name='impersonate-start'),
 ]
diff --git a/saleor/core/views.py b/saleor/core/views.py
--- a/saleor/core/views.py
+++ b/saleor/core/views.py
@@ -5,6 +5,7 @@
 from django.conf import settings
 from django.utils.translation import pgettext_lazy
 from impersonate.views import impersonate as orig_impersonate
+from random import randint
 
 from ..dashboard.views import staff_member_required
 from ..product.utils import products_with_availability, products_for_homepage
@@ -33,3 +34,8 @@ def impersonate(request, uid):
             'You are now logged as {}'.format(User.objects.get(pk=uid)))
         messages.success(request, msg)
     return response
+
+
+def handle_404(request):
+    ctx = {'variant': randint(0, 2)}
+    return TemplateResponse(request, '404.html', ctx, status=404)
diff --git a/saleor/urls.py b/saleor/urls.py
--- a/saleor/urls.py
+++ b/saleor/urls.py
@@ -20,6 +20,9 @@
 from .search.urls import urlpatterns as search_urls
 from .userprofile.urls import urlpatterns as userprofile_urls
 
+
+handler404 = 'saleor.core.views.handle_404'
+
 urlpatterns = [
     url(r'^', include(core_urls)),
     url(r'^account/', include(registration_urls)),


Test patch:
Repository: saleor/saleor

Issue:
Hide 'Clear filters' when list view has page in GET
### What I'm trying to achieve

Clear filters button should appear only when some filters are applied.

### Steps to reproduce the problem

1. Go to dashboard or store front page with filters and pagination and move to next page  (/dashboard/products/?page=2)
2. Clear filters button is present in filters card

### What I expected to happen

Clear filters button should be only present if some filters are applied.

### What happened instead/how it failed

Clear filter button is present.

![screenshot from 2017-12-06 10-11-14](https://user-images.githubusercontent.com/11785371/33653595-d34f5b00-da6d-11e7-90d6-dd31767434f7.png)




Discussion:

Resolving patch:
diff --git a/saleor/core/filters.py b/saleor/core/filters.py
--- a/saleor/core/filters.py
+++ b/saleor/core/filters.py
@@ -5,16 +5,11 @@ class SortedFilterSet(FilterSet):
     '''
     Base class for filtersets used in dashboard views. Adds flag
     is_bound_unsorted to indicate if FilterSet has data from filters other
-    than sort_by.
+    than sort_by or page.
     '''
     def __init__(self, data, *args, **kwargs):
-        data_copy = data.copy() if data else None
-        self.is_bound_unsorted = self.set_is_bound_unsorted(data_copy)
-        super().__init__(data, *args, **kwargs)
+        self.is_bound_unsorted = self.set_is_bound_unsorted(data)
+        super(SortedFilterSet, self).__init__(data, *args, **kwargs)
 
-    def set_is_bound_unsorted(self, data_copy):
-        if data_copy and data_copy.get('sort_by', None):
-            del data_copy['sort_by']
-        if data_copy:
-            return True
-        return False
+    def set_is_bound_unsorted(self, data):
+        return any([key not in {'sort_by', 'page'} for key in data.keys()])


Test patch:
diff --git a/tests/dashboard/test_product.py b/tests/dashboard/test_product.py
--- a/tests/dashboard/test_product.py
+++ b/tests/dashboard/test_product.py
@@ -591,11 +591,13 @@ def test_product_list_pagination(admin_client, product_list):
     url = reverse('dashboard:product-list')
     response = admin_client.get(url, data)
     assert response.status_code == 200
+    assert not response.context['filter'].is_bound_unsorted
 
     data = {'page': '2'}
     url = reverse('dashboard:product-list')
     response = admin_client.get(url, data)
     assert response.status_code == 200
+    assert not response.context['filter'].is_bound_unsorted
 
 
 def test_product_list_pagination_with_filters(admin_client, product_list):
Repository: saleor/saleor

Issue:
Missing pagination on the dashboard's homepage
On the homepage in the dashboard, there are three lists and none of them is paginated. Since they are three different querysets, I don't see a reasonable way to paginate them at once. Therefore I'd propose to limit the displayed results to a fixed limit e.g. 10 recent orders, 10 recent preauthorized payments etc.


Discussion:
I agree, it would be more useful to see "11 products are out of stock, most recent three are X, Y, Z, click here to see all" than a comprehensive list of all possible actionable items.
Currently, there is no way to have "Show more" links, as some of the sections (e.g. "Preauthorized payments") have no corresponding list views. For now, I would propose to limit the number of items to 10 in each section and show a message "Showing last 10 results" when hovering on the section title.
Maybe we should consider dropping some of the sections then? Are we sure there is a use case behind all of them? Maybe there are better metrics we could offer instead?
Yes, I agree that we should discuss that. But before we do this I would really like to fix the problem with three long lists that are not paginated and looks terribly bad in the demo store for instance.

Resolving patch:
diff --git a/saleor/dashboard/views.py b/saleor/dashboard/views.py
--- a/saleor/dashboard/views.py
+++ b/saleor/dashboard/views.py
@@ -34,18 +34,18 @@ def superuser_required(view_func=None, redirect_field_name=REDIRECT_FIELD_NAME,
 
 @staff_member_required
 def index(request):
-    orders_to_ship = Order.objects.select_related('user').prefetch_related(
-        'groups', 'groups__lines', 'payments')
+    INDEX_PAGINATE_BY = 10
+    orders_to_ship = Order.objects.open().select_related(
+        'user').prefetch_related('groups', 'groups__lines', 'payments')
     orders_to_ship = [
-        order for order in orders_to_ship
-        if order.status == OrderStatus.OPEN and order.is_fully_paid()]
+        order for order in orders_to_ship if order.is_fully_paid()]
     payments = Payment.objects.filter(
         status=PaymentStatus.PREAUTH).order_by('-created')
     payments = payments.select_related('order', 'order__user')
     low_stock = get_low_stock_products()
-    ctx = {'preauthorized_payments': payments,
-           'orders_to_ship': orders_to_ship,
-           'low_stock': low_stock}
+    ctx = {'preauthorized_payments': payments[:INDEX_PAGINATE_BY],
+           'orders_to_ship': orders_to_ship[:INDEX_PAGINATE_BY],
+           'low_stock': low_stock[:INDEX_PAGINATE_BY]}
     return TemplateResponse(request, 'dashboard/index.html', ctx)
 
 


Test patch:
Repository: saleor/saleor

Issue:
Adjust IMPERSONATE settings
We should prepare for newer release and adjust settings accordingly
```
UserWarning: The IMPERSONATE_* settings are now deprecated and will be removed in an upcoming release. Please use the IMPERSONATE dictionary setting.
```


Discussion:

Resolving patch:
diff --git a/saleor/settings.py b/saleor/settings.py
--- a/saleor/settings.py
+++ b/saleor/settings.py
@@ -419,8 +419,9 @@ def get_host():
 CELERY_RESULT_BACKEND = 'django-db'
 
 # Impersonate module settings
-IMPERSONATE_URI_EXCLUSIONS = [r'^dashboard/']
-IMPERSONATE_CUSTOM_USER_QUERYSET = \
-    'saleor.userprofile.impersonate.get_impersonatable_users'
-IMPERSONATE_USE_HTTP_REFERER = True
-IMPERSONATE_CUSTOM_ALLOW = 'saleor.userprofile.impersonate.can_impersonate'
+IMPERSONATE = {
+    'URI_EXCLUSIONS': [r'^dashboard/'],
+    'CUSTOM_USER_QUERYSET': 'saleor.userprofile.impersonate.get_impersonatable_users',  # noqa
+    'USE_HTTP_REFERER': True,
+    'CUSTOM_ALLOW': 'saleor.userprofile.impersonate.can_impersonate'
+}


Test patch:
Repository: saleor/saleor

Issue:
Use django-bootstrap-4
We're still using the `django-bootstrap-3` library for rendering Bootstrap components in templates. While it still works, we should rather switch to [django-bootstrap-4](https://github.com/zostera/django-bootstrap4) instead.


Discussion:

Resolving patch:
diff --git a/saleor/core/utils/form_renderer.py b/saleor/core/utils/form_renderer.py
--- a/saleor/core/utils/form_renderer.py
+++ b/saleor/core/utils/form_renderer.py
@@ -1,4 +1,4 @@
-from bootstrap3.renderers import FormRenderer as BaseFormRenderer
+from bootstrap4.renderers import FormRenderer as BaseFormRenderer
 
 
 class FormRenderer(BaseFormRenderer):
diff --git a/saleor/settings.py b/saleor/settings.py
--- a/saleor/settings.py
+++ b/saleor/settings.py
@@ -176,7 +176,7 @@ def get_list(text):
     # External apps
     'versatileimagefield',
     'django_babel',
-    'bootstrap3',
+    'bootstrap4',
     'django_prices',
     'django_prices_openexchangerates',
     'graphene_django',
@@ -289,7 +289,7 @@ def get_host():
 DASHBOARD_PAGINATE_BY = 30
 DASHBOARD_SEARCH_LIMIT = 5
 
-BOOTSTRAP3 = {
+bootstrap4 = {
     'set_placeholder': False,
     'set_required': False,
     'success_css_class': '',


Test patch:
Repository: saleor/saleor

Issue:
Product variants out of stock.
Variants of products out of stock should be grayed out or crossed out.
Currently, the customer is informed about the lack of a variant available, as he will decide to buy it:
![nagranie ekranu 2018-01-03 o 15 13 13](https://user-images.githubusercontent.com/1561204/34523722-f6e3c470-f098-11e7-8fb9-f6afdf93c83c.gif)



Discussion:

Resolving patch:
diff --git a/saleor/product/utils.py b/saleor/product/utils.py
--- a/saleor/product/utils.py
+++ b/saleor/product/utils.py
@@ -166,14 +166,15 @@ def get_variant_picker_data(product, discounts=None, local_currency=None):
                        'itemCondition': 'http://schema.org/NewCondition',
                        'priceCurrency': price.currency,
                        'price': price.net}
-
-        if variant.is_in_stock():
+        in_stock = variant.is_in_stock()
+        if in_stock:
             schema_data['availability'] = 'http://schema.org/InStock'
         else:
             schema_data['availability'] = 'http://schema.org/OutOfStock'
 
         variant_data = {
             'id': variant.id,
+            'availability': in_stock,
             'price': price_as_dict(price),
             'priceUndiscounted': price_as_dict(price_undiscounted),
             'attributes': variant.attributes,


Test patch:
Repository: saleor/saleor

Issue:
Bump backend dependencies
The outdated dependencies should be upgraded: https://requires.io/github/mirumee/saleor/requirements/?branch=master


Discussion:

Resolving patch:
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,6 +1,6 @@
 def get_sort_by_choices(filter_set):
     return [(choice[0], choice[1].lower()) for choice in
-            filter_set.filters['sort_by'].field.choices[1::2]]
+            list(filter_set.filters['sort_by'].field.choices)[1::2]]
 
 
 def get_now_sorted_by(filter_set, default_sort='name'):


Test patch:
diff --git a/tests/dashboard/test_product.py b/tests/dashboard/test_product.py
--- a/tests/dashboard/test_product.py
+++ b/tests/dashboard/test_product.py
@@ -25,7 +25,7 @@
 
 def create_image():
     img_data = BytesIO()
-    image = Image.new('RGBA', size=(1, 1), color=(255, 0, 0, 0))
+    image = Image.new('RGB', size=(1, 1), color=(255, 0, 0, 0))
     image.save(img_data, format='JPEG')
     image_name = 'product2'
     image = SimpleUploadedFile(
Repository: saleor/saleor

Issue:
Missing "filter by product type" feature
In the product list in the dashboard, there should be the ability to filter the list by product types.


Discussion:

Resolving patch:
diff --git a/saleor/dashboard/product/filters.py b/saleor/dashboard/product/filters.py
--- a/saleor/dashboard/product/filters.py
+++ b/saleor/dashboard/product/filters.py
@@ -39,6 +39,10 @@ class ProductFilter(SortedFilterSet):
         label=pgettext_lazy('Product list filter label', 'Categories'),
         name='categories',
         queryset=Category.objects.all())
+    product_class = ModelMultipleChoiceFilter(
+        label=pgettext_lazy('Product list filter label', 'Product type'),
+        name='product_class',
+        queryset=ProductClass.objects.all())
     price = RangeFilter(
         label=pgettext_lazy('Product list filter label', 'Price'),
         name='price',


Test patch:
diff --git a/tests/dashboard/test_chips.py b/tests/dashboard/test_chips.py
--- a/tests/dashboard/test_chips.py
+++ b/tests/dashboard/test_chips.py
@@ -7,7 +7,6 @@
     CHIPS_PATTERN, handle_default, handle_single_choice,
     handle_multiple_choice, handle_nullboolean, handle_single_model_choice,
     handle_multiple_model_choice, handle_range)
-from saleor.dashboard.product.filters import ProductFilter
 from saleor.dashboard.widgets import PriceRangeWidget
 from saleor.product.models import Category, Product