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

Issue:
SE Iterate tags in templates
[trello origin](https://trello.com/c/zulRj7lF/294-se-iterate-tags-in-templates)
[seo templates doc](https://docs.google.com/document/d/18DFBsuh6NT8hjyihOJ2bxw9zEe8z_070MBQrbAq0kvE/edit#)

**Проблема**
Сеошники после некоторых применений мультисв-в обнаружили заголовок *Блоки питания для ноутбуков устанавливает пользователь и от сети 220 В* [по этому линку](https://www.shopelectro.ru/catalog/categories/bloki-pitaniia-288/tags/ustanavlivaet-polzovatel-and-ot-seti-220-v/)

Получилось нехорошо. Предлагают сделать заголовок таким:
*Блоки питания для ноутбуков от сети 220 В, выбор выходного напряжения - устанавливает пользователь*


**Решение**
Чтобы переделать заголовок, нам нужно добавить в шаблон возможность указывать имя одного конкретного тега.

А решить это можно так: добавляем полноценный tags в seo-шаблоны.
Сейчас у нас только обрезанный tags.titles


Discussion:

Resolving patch:
diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py
--- a/shopelectro/views/catalog.py
+++ b/shopelectro/views/catalog.py
@@ -141,6 +141,7 @@ def get_context_data(self, **kwargs):
         tags = self.kwargs.get('tags')
         tags_metadata = {
             'titles': '',
+            'raw': [],
         }
 
         if tags:
@@ -160,6 +161,7 @@ def get_context_data(self, **kwargs):
             )
 
             tags_metadata['titles'] = tags_titles
+            tags_metadata['raw'] = tags
 
         def template_context(page, tags):
             return {


Test patch:
Repository: fidals/shopelectro

Issue:
SE yml fix delivery 3k -> 5k
[origin trello task](https://trello.com/c/LyLVDakS/298-se-%D0%BF%D1%80%D0%B0%D0%B2%D1%8C-yml)

Внезапно обноружили, что через фид передается( в теге <sales_notes>) неверная инфа о доставке.
Исправь на 5к.


Discussion:

Resolving patch:
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -268,7 +268,7 @@
     'cps_phone': '+78124163200',
     'cps_email': 'info@shopelectro.ru',
     'local_delivery_cost': 300,
-    'local_delivery_cost_threshold': 3000,
+    'local_delivery_cost_threshold': 5000,
 }
 
 # used in data-migrations and tests


Test patch:
Repository: fidals/shopelectro

Issue:
Сделай автокомплит для категорий в карточке товара
Первый пункт из задачи https://github.com/fidals/refarm-site/issues/79


Discussion:

Resolving patch:
diff --git a/shopelectro/admin.py b/shopelectro/admin.py
--- a/shopelectro/admin.py
+++ b/shopelectro/admin.py
@@ -6,6 +6,8 @@
 from django.utils.html import format_html
 from django.utils.translation import ugettext_lazy as _
 
+from django_select2.forms import ModelSelect2Widget
+
 from pages.models import CustomPage, FlatPage, PageTemplate
 from generic_admin import inlines, models, sites
 
@@ -111,6 +113,20 @@ class ProductInline(inlines.ProductInline):
         )
     }),)
 
+    def formfield_for_dbfield(self, db_field, **kwargs):
+        if db_field.name == 'category':
+            kwargs['widget'] = ModelSelect2Widget(
+                model=se_models.Category,
+                search_fields=[
+                    'name__icontains',
+                    'pk__startswith',
+                ],
+            )
+        return super(ProductInline, self).formfield_for_dbfield(
+            db_field,
+            **kwargs,
+        )
+
 
 class CategoryPageAdmin(models.CategoryPageAdmin):
 
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -62,6 +62,7 @@
     'mptt',
     'widget_tweaks',
     'sorl.thumbnail',
+    'django_select2',
     'images',
     'pages',
     'catalog',
diff --git a/shopelectro/urls.py b/shopelectro/urls.py
--- a/shopelectro/urls.py
+++ b/shopelectro/urls.py
@@ -36,6 +36,7 @@ def cache_page(arg):  # Ignore PyFlakesBear
     url(r'^get-tree-items/$', views.Tree.as_view()),
     url(r'^redirect-to-product/$', views.RedirectToProduct.as_view()),
     url(r'^table-editor-api/$', views.TableEditorAPI.as_view()),
+    url(r'^select2/', include('django_select2.urls')),
 ]
 
 catalog_urls = [


Test patch:
Repository: fidals/shopelectro

Issue:
Верни список заказов в админку
Помнится у нас был список заказов в админке, но его несколько раз ломали и выпиливали. Возвращай.


Discussion:

Resolving patch:
diff --git a/shopelectro/admin.py b/shopelectro/admin.py
--- a/shopelectro/admin.py
+++ b/shopelectro/admin.py
@@ -8,8 +8,9 @@
 
 from django_select2.forms import ModelSelect2Widget
 
+from ecommerce.models import Position
 from pages.models import CustomPage, FlatPage, PageTemplate
-from generic_admin import inlines, models, sites
+from generic_admin import inlines, mixins, models, sites
 
 from shopelectro import models as se_models
 from shopelectro.views.admin import TableEditor
@@ -128,6 +129,11 @@ def formfield_for_dbfield(self, db_field, **kwargs):
         )
 
 
+class PositionInline(admin.StackedInline):
+
+    model = Position
+
+
 class CategoryPageAdmin(models.CategoryPageAdmin):
 
     add = False
@@ -228,12 +234,21 @@ def custom_group(self, obj):
     custom_group.short_description = _('Group')
 
 
+class OrderAdmin(mixins.PermissionsControl):
+
+    add = False
+    inlines = [PositionInline]
+    list_display = ['name', 'email', 'phone', 'address', 'city', 'payment_type', 'paid']
+    search_fields = ['name', 'email', 'phone']
+    list_display_links = ['name']
+
 se_admin = SEAdminSite(name='se_admin')
 se_admin.register(CustomPage, models.CustomPageAdmin)
 se_admin.register(FlatPage, models.FlatPageAdmin)
 se_admin.register(PageTemplate, models.CustomPageTemplateAdmin)
 
 se_admin.register(se_models.CategoryPage, CategoryPageAdmin)
+se_admin.register(se_models.Order, OrderAdmin)
 se_admin.register(se_models.ProductPage, ProductPageAdmin)
 se_admin.register(se_models.ProductFeedback, ProductFeedbackPageAdmin)
 se_admin.register(se_models.TagGroup, TagGroupAdmin)


Test patch:
Repository: fidals/shopelectro

Issue:
Добавь артикул в карту товара
В мобильной версии сайта, рядом с заголовком товара, не отображается артикул. Как в веб версии. Добавьте такой же элемент. 


Discussion:
`hard` because markup skills are required

Resolving patch:
diff --git a/shopelectro/admin.py b/shopelectro/admin.py
--- a/shopelectro/admin.py
+++ b/shopelectro/admin.py
@@ -103,7 +103,7 @@ class ProductInline(inlines.ProductInline):
     fieldsets = ((None, {
         'classes': ('primary-chars', ),
         'fields': (
-            ('name', 'id'),
+            ('name', 'id', 'vendor_code'),
             ('category', 'correct_category_id'),
             ('price', 'in_stock'),
             'is_popular',


Test patch:
Repository: fidals/shopelectro

Issue:
Меняй формулу ценообразования 
Когда-то мы искусственно повысили ценник на 1% что бы не казаться аффилиатами у se78
Понизь ценник на 2% (0.99 * real_price). Это даст нам больше шансов в трафике и сео


Discussion:

Resolving patch:
diff --git a/shopelectro/management/commands/_update_catalog/update_products.py b/shopelectro/management/commands/_update_catalog/update_products.py
--- a/shopelectro/management/commands/_update_catalog/update_products.py
+++ b/shopelectro/management/commands/_update_catalog/update_products.py
@@ -59,9 +59,14 @@ def fetch_products(root: Element, config: XmlFile) -> Iterator:
 
 def fetch_prices(root: Element, config) -> Iterator:
     def get_prices(prices_el):
-        def get_(price_el: Element):
+        def get_(price_el: Element) -> float:
             return float(price_el.find(config.xpaths['price']).text)
-        return sorted(map(get_, prices_el.findall(config.xpaths['prices'])))
+        def multiply(price: float) -> float:
+            return price * settings.PRICE_MULTIPLIER
+        return sorted(
+            multiply(get_(price))
+            for price in prices_el.findall(config.xpaths['prices'])
+        )
     product_price_els = root.findall(config.xpaths['product_prices'])
     for prices_el in product_price_els:
         product_uuid = prices_el.find(config.xpaths['product_uuid']).text
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -294,6 +294,8 @@
 
 TOP_PRODUCTS = [291, 438, 1137, 2166, 2725, 2838, 3288, 3884, 3959, 2764]
 
+PRICE_MULTIPLIER = 0.99
+
 SHOP = {
     'id': '69886',
     'scid': '64788',


Test patch:
Repository: fidals/shopelectro

Issue:
Price round problem: product page and cart
Before this moment we had requirement to do `-1%` for price fetched from 1C.

But we round this price on product page. Look at the screen: two products cost less then `price x 2` because of this UI rounding.

To solve it PO created new requirement: don't do `-1%` for price, but do `-1 руб` for every retail (not wholesale) price.
Do it in fetching data from 1C



Discussion:

Resolving patch:
diff --git a/shopelectro/management/commands/_update_catalog/update_products.py b/shopelectro/management/commands/_update_catalog/update_products.py
--- a/shopelectro/management/commands/_update_catalog/update_products.py
+++ b/shopelectro/management/commands/_update_catalog/update_products.py
@@ -61,12 +61,11 @@ def fetch_prices(root: Element, config) -> Iterator:
     def get_prices(prices_el):
         def get_(price_el: Element) -> float:
             return float(price_el.find(config.xpaths['price']).text)
-        def multiply(price: float) -> float:
-            return price * settings.PRICE_MULTIPLIER
-        return sorted(
-            multiply(get_(price))
+        *prices, retail_price = sorted(
+            get_(price)
             for price in prices_el.findall(config.xpaths['prices'])
         )
+        return prices + [retail_price - settings.PRICE_REDUCER]
     product_price_els = root.findall(config.xpaths['product_prices'])
     for prices_el in product_price_els:
         product_uuid = prices_el.find(config.xpaths['product_uuid']).text
@@ -74,7 +73,6 @@ def multiply(price: float) -> float:
             config.extra_options['price_types'],
             get_prices(prices_el)
         ))
-
         yield product_uuid, prices
 
 
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -293,7 +293,9 @@
 
 TOP_PRODUCTS = [291, 438, 1137, 2166, 2725, 2838, 3288, 3884, 3959, 2764]
 
-PRICE_MULTIPLIER = 0.99
+# Reduce retail product prices by PRICE_REDUCER.
+# It is required to make prices on shopelectro.ru and se78.ru unique.
+PRICE_REDUCER = 1
 
 SHOP = {
     'id': '69886',


Test patch:
Repository: fidals/shopelectro

Issue:
ProductPage and ya.market prices not equals
This product in `yandex.yml` (from prod) has price `200 RUB` on it's product page

```
        <offer id="4928" available="true">
          <url>https://www.shopelectro.ru/catalog/products/4928/?utm_source=YM&amp;utm_medium=cpc&amp;utm_content=akkumuliatory-270&amp;utm_term=4928</url>
          <price>199.98</price>
          <purchase_price>106.8111</purchase_price>
          <currencyId>RUR</currencyId>
```


Discussion:
i'll round prices when yaml loads, but not during UI rendering

Resolving patch:
diff --git a/shopelectro/management/commands/_update_catalog/update_products.py b/shopelectro/management/commands/_update_catalog/update_products.py
--- a/shopelectro/management/commands/_update_catalog/update_products.py
+++ b/shopelectro/management/commands/_update_catalog/update_products.py
@@ -61,11 +61,13 @@ def fetch_prices(root: Element, config) -> Iterator:
     def get_prices(prices_el):
         def get_(price_el: Element) -> float:
             return float(price_el.find(config.xpaths['price']).text)
+        def prepare_retail_(price: float):
+            return round(price - settings.PRICE_REDUCER)
         *prices, retail_price = sorted(
             get_(price)
             for price in prices_el.findall(config.xpaths['prices'])
         )
-        return prices + [retail_price - settings.PRICE_REDUCER]
+        return prices + [prepare_retail_(retail_price)]
     product_price_els = root.findall(config.xpaths['product_prices'])
     for prices_el in product_price_els:
         product_uuid = prices_el.find(config.xpaths['product_uuid']).text


Test patch:
Repository: fidals/shopelectro

Issue:
docker-compose.yml:93: Resurrect coala eslint.
The puzzle `322-dbccf9a1` from #322 has to be resolved:

https://github.com/fidals/shopelectro/blob/c827fa74e382579bf43fa368be3549cd108f6050/docker/docker-compose.yml#L93-L93

The puzzle was created by Artemiy on 08-Jun-18. 

Estimate: 0 minutes,     

If you have any technical questions, don't ask me, submit new tickets instead. The task will be "done" when the problem is fixed and the text of the puzzle is _removed_ from the source code. Here is more about [PDD](http://www.yegor256.com/2009/03/04/pdd.html) and [about me](http://www.yegor256.com/2017/04/05/pdd-in-action.html).


Discussion:

Resolving patch:
diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py
--- a/shopelectro/views/catalog.py
+++ b/shopelectro/views/catalog.py
@@ -205,7 +205,9 @@ def load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None):
     products_on_page = limit or get_products_count(request)
     offset = int(offset)
     if offset < 0:
-        return HttpResponseBadRequest('The offset is wrong. An offset should be greater than or equal to 0.')
+        return HttpResponseBadRequest(
+            'The offset is wrong. An offset should be greater than or equal to 0.'
+        )
     if products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS:
         return HttpResponseBadRequest(
             'The limit number is wrong. List of available numbers:'


Test patch:
diff --git a/shopelectro/tests/tests_views.py b/shopelectro/tests/tests_views.py
--- a/shopelectro/tests/tests_views.py
+++ b/shopelectro/tests/tests_views.py
@@ -54,9 +54,7 @@ def json_to_dict(response: HttpResponse) -> dict():
 
 
 class CatalogPage(TestCase):
-    """
-    @todo #302 Divide the CatalogPage test class into parts related to the features.
-    """
+    """@todo #302 Divide the CatalogPage test class into parts related to the features."""
 
     fixtures = ['dump.json']
 
@@ -199,16 +197,16 @@ def test_pagination_numbering(self):
         response = self.get_category_page(query_string={'page': page_number})
         self.assertEqual(get_page_number(response), page_number)
 
-    def test_pagination_products_count(self):
+    def test_pagination_products_count(self):  # Ignore PyDocStyleBear
         """
         @todo #302:30m Implement test case for pagination logic.
          Products number changes in depend on page number.
          If step=24 and page number=2, then products quantity is 48.
-         If step=24 and page number=2 and total products quantity is 40, then products quantity is 40.
+         If step=24 and page number=2 and total products quantity is 40, then products quantity is 40.  # Ignore PycodestyleBear (E501)
         """
 
     def test_pagination_step(self):
-        """CategoryPage should contain `pagination_step` products count in list."""
+        """Category page contains `pagination_step` count of products in list."""
         pagination_step = 25
         response = self.get_category_page(query_string={'step': pagination_step})
         self.assertEqual(len(response.context['product_image_pairs']), pagination_step)
Repository: fidals/shopelectro

Issue:
Do `gulp build` on image side
Currently, we have problems with gulp build on client side. See #344 for details.
Moreover, building static on container site is more good practice


Discussion:

Resolving patch:
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -152,7 +152,7 @@
 STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
 
 STATICFILES_DIRS = [
-    os.path.join(BASE_DIR, 'front/build'),
+    os.environ['FRONT_BUILD_DIR'],
     ASSETS_DIR,
 ]
 


Test patch:
Repository: fidals/shopelectro

Issue:
Resurrect `npm install` and `gulp build`. stb2
Now we have this error:

```
docker-compose run --rm nodejs gulp build
[12:59:08] Failed to load external module @babel/register
[12:59:08] Failed to load external module babel-register
[12:59:08] Failed to load external module babel-core/register
[12:59:08] Failed to load external module babel/register
[12:59:08] Local gulp not found in /usr/app/src
[12:59:08] Try running: npm install gulp
Makefile:36: ошибка выполнения рецепта для цели «build-static»
make: *** [build-static] Ошибка 1
```


Discussion:
the problem is in this docker-compose.yml instruction:

```
    volumes_from:
      - app
```

After it, local folder contains folder `$SRC_DIR/node_modules/` from local disk, but not from image.
We should launch gulp build on container's side.
I'll create task about it 
depends on #345 
#345 done most of work

Resolving patch:
diff --git a/shopelectro/migrations/0023_excludedmodeltpage.py b/shopelectro/migrations/0023_excludedmodeltpage.py
new file mode 100644
--- /dev/null
+++ b/shopelectro/migrations/0023_excludedmodeltpage.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2018-06-15 18:08
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('pages', '0015_add_validation_to_template'),
+        ('shopelectro', '0022_page_templates_content'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ExcludedModelTPage',
+            fields=[
+            ],
+            options={
+                'abstract': False,
+                'proxy': True,
+            },
+            bases=('pages.page',),
+        ),
+    ]


Test patch:
diff --git a/shopelectro/tests/helpers.py b/shopelectro/tests/helpers.py
--- a/shopelectro/tests/helpers.py
+++ b/shopelectro/tests/helpers.py
@@ -1,5 +1,6 @@
 import base64
 import re
+import time
 from contextlib import contextmanager
 
 from django.conf import settings
@@ -64,6 +65,8 @@ def setUpClass(cls):
         # This bug is won't fixed by selenium guys https://goo.gl/6Ttguf
         # Ohh, so selenium is so selenium ...
         # UPD 19.05.18: Seems it works, so we enable it to reduce number of errors
+        # UPD 21.06.18: It does not work all times. When serv is loaded, this mech breaks.
+        time.sleep(1.0)
         cls.browser.maximize_window()
 
     @classmethod
Repository: fidals/shopelectro

Issue:
helpers.py:58-59: Move selenium timeout to env var. stb2...
The puzzle `371-6673a075` from #371 has to be resolved:

https://github.com/fidals/shopelectro/blob/7d319f81942692c16105a8f8703e483a7067f1ef/shopelectro/tests/helpers.py#L58-L59

The puzzle was created by duker33 on 19-Jun-18. 

Estimate: 15 minutes,     

If you have any technical questions, don't ask me, submit new tickets instead. The task will be "done" when the problem is fixed and the text of the puzzle is _removed_ from the source code. Here is more about [PDD](http://www.yegor256.com/2009/03/04/pdd.html) and [about me](http://www.yegor256.com/2017/04/05/pdd-in-action.html).


Discussion:
@artemiyds2 @duker33 please pay attention to this new issue.

Resolving patch:
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -215,6 +215,8 @@
 }
 
 SELENIUM_URL = os.environ.get('SELENIUM_URL', 'http://selenium:4444/wd/hub')
+SELENIUM_WAIT_SECONDS = int(os.environ.get('SELENIUM_WAIT_SECONDS', 60))
+SELENIUM_TIMEOUT_SECONDS = int(os.environ.get('SELENIUM_TIMEOUT_SECONDS', 30))
 
 SITE_CREATED = datetime(2013, 1, 1)
 


Test patch:
diff --git a/shopelectro/tests/helpers.py b/shopelectro/tests/helpers.py
--- a/shopelectro/tests/helpers.py
+++ b/shopelectro/tests/helpers.py
@@ -58,9 +58,9 @@ def setUpClass(cls):
         )
         # @todo #371:15m Move selenium timeout to env var. stb2
         #  To be able to change it from drone without touching code.
-        cls.wait = WebDriverWait(cls.browser, 60)
+        cls.wait = WebDriverWait(cls.browser, settings.SELENIUM_WAIT_SECONDS)
         cls.browser.implicitly_wait(30)
-        cls.browser.set_page_load_timeout(30)
+        cls.browser.set_page_load_timeout(settings.SELENIUM_TIMEOUT_SECONDS)
         # Fresh created browser failures on maximizing window.
         # This bug is won't fixed by selenium guys https://goo.gl/6Ttguf
         # Ohh, so selenium is so selenium ...
Repository: fidals/shopelectro

Issue:
Use new redirects app. stb2
Depends on https://github.com/fidals/refarm-site/pull/141

And resurrect test `shopelectro.tests.tests_views.Redirects`


Discussion:

Resolving patch:
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -62,6 +62,7 @@
     'sorl.thumbnail',
     'django_select2',
     'images',
+    'refarm_redirects',
     'pages',
     'catalog',
     'search',
@@ -78,10 +79,10 @@
     'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
-    'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
     'django.middleware.locale.LocaleMiddleware',
     'django_user_agents.middleware.UserAgentMiddleware',
     'debug_toolbar.middleware.DebugToolbarMiddleware',
+    'refarm_redirects.middleware.RedirectAllMiddleware',
 ]
 
 ROOT_URLCONF = 'shopelectro.urls'


Test patch:
diff --git a/shopelectro/tests/tests_views.py b/shopelectro/tests/tests_views.py
--- a/shopelectro/tests/tests_views.py
+++ b/shopelectro/tests/tests_views.py
@@ -5,7 +5,6 @@
 They all should be using Django's TestClient.
 """
 import json
-import unittest
 from functools import partial
 from itertools import chain
 from operator import attrgetter
@@ -573,8 +572,6 @@ class Redirects(TestCase):
 
     fixtures = ['dump.json']
 
-    # will be resurrected at rf#140
-    @unittest.expectedFailure
     def test_redirect_from_existing_page(self):
         """`refarm-site.redirects` app should redirect from existing url too."""
         # take some existing `url_from`
Repository: fidals/shopelectro

Issue:
Убери пункт меню
Убери mp3 колонки из меню. Их больше не будет.
Я зашел в админку и выключил категорию. Надеюсь правильно ) 
http://prntscr.com/k553lt


Discussion:

Resolving patch:
diff --git a/shopelectro/templatetags/se_extras.py b/shopelectro/templatetags/se_extras.py
--- a/shopelectro/templatetags/se_extras.py
+++ b/shopelectro/templatetags/se_extras.py
@@ -20,10 +20,13 @@
 @register.simple_tag
 def roots():
     return sorted(
-        Category.objects
-        .select_related('page')
-        .get_cached_trees(),  # https://goo.gl/rFKiku
-        key=lambda x: x.page.position
+        filter(
+            lambda x: x.page.is_active,
+            Category.objects  # https://goo.gl/rFKiku
+            .select_related('page')
+            .get_cached_trees()
+        ),
+        key=lambda x: x.page.position,
     )
 
 


Test patch:
diff --git a/shopelectro/tests/tests_templatetags.py b/shopelectro/tests/tests_templatetags.py
new file mode 100644
--- /dev/null
+++ b/shopelectro/tests/tests_templatetags.py
@@ -0,0 +1,25 @@
+from django.test import TestCase
+
+from shopelectro.models import Category
+from shopelectro.templatetags.se_extras import roots
+
+
+class TemplateTags(TestCase):
+
+    fixtures = ['dump.json']
+
+    def test_root_categories(self):
+        """All root categories are presented in result of calling the `roots` tag."""
+        self.assertFalse(
+            set(Category.objects.get_cached_trees()) ^ set(roots()),
+        )
+
+    def test_exclusions_of_root_categories(self):
+        """Excluded categories are not presented in result of calling the `roots` tag."""
+        excluded_category, *_ = categories = Category.objects.get_cached_trees()
+        excluded_category.page.is_active = False
+        excluded_category.page.save()
+
+        self.assertTrue(
+            excluded_category in set(categories) ^ set(roots())
+        )