diff --git a/Dshop/apps/products_catalogue/filters.py b/Dshop/apps/products_catalogue/filters.py
index 7707544..68c15a5 100644
--- a/Dshop/apps/products_catalogue/filters.py
+++ b/Dshop/apps/products_catalogue/filters.py
@@ -1,11 +1,55 @@
+from django.forms import TextInput
from django_filters import rest_framework as filters
-from .models import Product
+from .models import Product, Category
class ProductFilter(filters.FilterSet):
- name = filters.CharFilter(field_name='name', lookup_expr='icontains')
- is_active = filters.BooleanFilter(field_name='is_active')
+ name = filters.CharFilter(field_name='name', lookup_expr='icontains',
+ widget=TextInput(attrs={'placeholder': 'Szukaj po nazwie'}))
+ price__lt = filters.NumberFilter(field_name='price', lookup_expr='lt',
+ widget=TextInput(attrs={'placeholder': 'max'}))
+ price__gt = filters.NumberFilter(field_name='price', lookup_expr='gt',
+ widget=TextInput(attrs={'placeholder': 'min'}))
+ category_name = filters.ChoiceFilter(field_name='category__name',
+ choices=Category.objects.all().values_list('name', 'name').distinct(),
+ label='Category', empty_label='Wybierz Kategorie')
+ availability = filters.ChoiceFilter(
+ choices=Product.AVAILABILITY_CHOICES,
+ label="Dostępność",
+ empty_label="Wszystkie",
+ method='filter_availability')
+ order_by = filters.OrderingFilter(
+ fields=(
+ ('price', 'price'),
+ ('name', 'name'),
+ ('created_at', 'created_at')
+ ),
+ field_labels={
+ 'price': 'Cena rosnąco',
+ '-price': 'Cena malejąco',
+ '-name': 'Produkt A-Z',
+ 'name': 'Produkt Z-A',
+ 'created_at': 'Najnowsze',
+ '-created_at': 'Najstarsze',
+ },
+ label='Sortuj',
+ empty_label='Domyślnie'
+ )
+
+ def filter_availability(self, queryset, name, value):
+ value = int(value)
+ if value in (1, 3, 7, 14):
+ return queryset.filter(availability__lte=value)
+ return queryset.filter(**{name: value})
+
+ def __init__(self, *args, **kwargs):
+ super(ProductFilter, self).__init__(*args, **kwargs)
+ self.filters['name'].label = 'Nazwa produktu'
+ self.filters['price__lt'].label = 'Cena do'
+ self.filters['price__gt'].label = 'Cena od'
+ self.filters['order_by'].label = 'Sortowanie'
+ self.filters['category_name'].label = 'Kategoria'
class Meta:
model = Product
- fields = ['name', 'is_active']
+ fields = ['name', 'price', 'category_name', 'availability']
diff --git a/Dshop/apps/products_catalogue/models.py b/Dshop/apps/products_catalogue/models.py
index ecc14d3..0782b0f 100644
--- a/Dshop/apps/products_catalogue/models.py
+++ b/Dshop/apps/products_catalogue/models.py
@@ -63,6 +63,15 @@ def get_absolute_url(self):
class Product(CatalogueItemModel):
+ AVAILABILITY_CHOICES = [
+ (1, 'Dostępny, sklep wyśle produkt w ciągu 24 godzin'),
+ (3, 'Sklep wyśle produkt do 3 dni'),
+ (7, 'Sklep wyśle produkt w ciągu tygodnia'),
+ (14, 'Sklep wyśle produkt do 14 dni'),
+ (90, 'Towar na zamówienie'),
+ (99, 'Brak informacji o dostępności - status „sprawdź w sklepie”'),
+ (110, 'Przedsprzedaż'),
+ ]
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
short_description = tinymce_models.HTMLField()
@@ -70,15 +79,7 @@ class Product(CatalogueItemModel):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
availability = models.PositiveSmallIntegerField(
- choices=[
- (1, 'Dostępny, sklep wyśle produkt w ciągu 24 godzin'),
- (3, 'Sklep wyśle produkt do 3 dni'),
- (7, 'Sklep wyśle produkt w ciągu tygodnia'),
- (14, 'Sklep wyśle produkt do 14 dni'),
- (90, 'Towar na zamówienie'),
- (99, 'Brak informacji o dostępności - status „sprawdź w sklepie”'),
- (110, 'Przedsprzedaż'),
- ],
+ choices=AVAILABILITY_CHOICES,
default=99,
)
parent_product = models.ForeignKey(
@@ -135,7 +136,7 @@ def get_price(self, item: CartItem) -> DecimalField:
def is_available(self):
return self.availability
-
+
class ProductImage(models.Model):
product = models.ForeignKey(
diff --git a/Dshop/apps/products_catalogue/templates/products_catalogue/products_list.html b/Dshop/apps/products_catalogue/templates/products_catalogue/products_list.html
index be4a197..5038227 100644
--- a/Dshop/apps/products_catalogue/templates/products_catalogue/products_list.html
+++ b/Dshop/apps/products_catalogue/templates/products_catalogue/products_list.html
@@ -14,9 +14,47 @@
DShop collection
+
{% for object in object_list %}
-
diff --git a/Dshop/apps/products_catalogue/tests/conftest.py b/Dshop/apps/products_catalogue/tests/conftest.py
index c8e2609..f715fcb 100644
--- a/Dshop/apps/products_catalogue/tests/conftest.py
+++ b/Dshop/apps/products_catalogue/tests/conftest.py
@@ -1,6 +1,9 @@
+import time
+
from django.contrib.auth.models import User
import pytest
from rest_framework.test import APIClient
+from apps.products_catalogue.models import Category, Product
@pytest.fixture
@@ -8,4 +11,20 @@ def api_client():
client = APIClient()
user = User.objects.create_user(username='testuser', password='testpassword')
client.force_authenticate(user)
- return client
\ No newline at end of file
+ return client
+
+
+@pytest.fixture
+def products():
+ category = Category.objects.create(name='Test Category', is_active=True)
+ for price in range(1, 11):
+ Product.objects.create(
+ name=f"main product ${price}",
+ category=category,
+ price=price,
+ short_description="short desc inactive",
+ full_description="full_description inactive",
+ is_active=True,
+ availability=3
+ )
+ time.sleep(0.2)
diff --git a/Dshop/apps/products_catalogue/tests/test_products_filters.py b/Dshop/apps/products_catalogue/tests/test_products_filters.py
new file mode 100644
index 0000000..6133c58
--- /dev/null
+++ b/Dshop/apps/products_catalogue/tests/test_products_filters.py
@@ -0,0 +1,122 @@
+from decimal import Decimal
+
+import pytest
+
+from django.urls import reverse
+from ..models import Product
+
+
+@pytest.mark.django_db
+def test_filter_by_price(client, products):
+ # Products is a fixture with 9 products and diff prices.
+ # checking ordering
+ # Given is in fixture
+ # When
+ url = reverse("products-list")
+ response = client.get(f"{url}?order_by=-price")
+ products = response.context["object_list"]
+ # Then
+ for idx, price in enumerate(range(10, 1, -1)):
+ assert products[idx].price == price
+
+
+@pytest.mark.django_db
+def test_product(client, products):
+ assert Product.objects.filter(is_active=True).count() == 10
+
+
+@pytest.mark.django_db
+def test_filter_by_price_asc(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?order_by=price")
+ products = response.context['object_list']
+ for idx, price in enumerate(range(1, 11)):
+ assert products[idx].price == price
+
+
+@pytest.mark.django_db
+def test_filter_by_price_desc(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?order_by=-price")
+ products = response.context['object_list']
+ for idx, price in enumerate(range(10, 0, -1)):
+ assert products[idx].price == Decimal(price)
+
+
+@pytest.mark.django_db
+def test_filter_order_by_name(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?order_by=name")
+ products = response.context['object_list']
+ for idx, name in enumerate([1, 10, 2, 3, 4, 5, 6, 7, 8, 9]):
+ assert products[idx].name == f"main product ${name}"
+
+
+@pytest.mark.django_db
+def test_filter_order_by_name_desc(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?order_by=-name")
+ products = response.context['object_list']
+ for idx, name in enumerate([9, 8, 7, 6, 5, 4, 3, 2, 10, 1]):
+ assert products[idx].name == f'main product ${name}'
+
+
+@pytest.mark.django_db
+def test_filter_by_created(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?order_by=created_at")
+ products = response.context['object_list']
+ for idx, value in enumerate(range(1, 10)):
+ assert products[idx].created_at <= products[idx + 1].created_at
+
+
+@pytest.mark.django_db
+def test_filter_by_created_desc(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?order_by=-created_at")
+ products = response.context['object_list']
+ for idx, value in enumerate(range(10, 1, -1)):
+ assert products[idx].created_at >= products[idx + 1].created_at
+
+
+@pytest.mark.django_db
+def test_filter_category(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?category_name=Test_Category")
+ products = response.context['object_list']
+ for idx, value in enumerate(range(1, 11)):
+ assert products[idx].category.name == 'Test Category'
+
+
+@pytest.mark.django_db
+def test_filter_by_min_price(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?prince__gt=5")
+ products = response.context['object_list']
+ for value in range(5, 10):
+ assert products[value].price == Decimal(value + 1)
+
+
+@pytest.mark.django_db
+def test_filter_by_max_price(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?prince__lt=5")
+ products = response.context['object_list']
+ for idx, value in enumerate(range(5)):
+ assert products[idx].price == Decimal(value + 1)
+
+
+@pytest.mark.django_db
+def test_filter_by_name(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?name=main+product+$10")
+ products = response.context['object_list']
+ assert products[0].name == 'main product $10'
+
+@pytest.mark.django_db
+def test_filter_availability(client, products):
+ url = reverse("products-list")
+ response = client.get(f"{url}?availability=3")
+ products = response.context['object_list']
+ for idx, value in enumerate(range(1, 11)):
+ assert products[idx].availability == 3
\ No newline at end of file
diff --git a/Dshop/apps/products_catalogue/views.py b/Dshop/apps/products_catalogue/views.py
index 9054ca5..24c7707 100644
--- a/Dshop/apps/products_catalogue/views.py
+++ b/Dshop/apps/products_catalogue/views.py
@@ -7,16 +7,25 @@
from lxml import etree
from dj_shop_cart.cart import Cart
-
+from .filters import ProductFilter
from .models import Product, Category
class ProductListView(ListView):
model = Product
template_name = 'products_catalogue/products_list.html'
+ queryset = Product.objects.filter(is_active=True)
+ paginate_by = 24
def get_queryset(self):
- return Product.objects.filter(is_active=True)
+ queryset = super().get_queryset()
+ self.filterset = ProductFilter(self.request.GET, queryset=queryset)
+ return self.filterset.qs
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['form'] = self.filterset.form
+ return context
class ProductDetailView(DetailView):