Bläddra i källkod

Add is_public field to Product model (#2935)

master
Roel Bruggink 6 år sedan
förälder
incheckning
a5096da38a

+ 7
- 0
.gitignore Visa fil

@@ -6,6 +6,13 @@ __pycache__
6 6
 /build/
7 7
 /node_modules/
8 8
 
9
+# venv
10
+/bin/
11
+/lib/
12
+/share
13
+/pyvenv.cfg
14
+/pip-selfcheck.json
15
+
9 16
 # Vagrant
10 17
 .vagrant
11 18
 

+ 5
- 0
src/oscar/apps/catalogue/abstract_models.py Visa fil

@@ -263,6 +263,11 @@ class AbstractProduct(models.Model):
263 263
         _("Product structure"), max_length=10, choices=STRUCTURE_CHOICES,
264 264
         default=STANDALONE)
265 265
 
266
+    is_public = models.BooleanField(
267
+        _('Is public'),
268
+        default=True,
269
+        help_text=_("Show this product in search results and catalogue listings."))
270
+
266 271
     upc = NullCharField(
267 272
         _("UPC"), max_length=64, blank=True, null=True, unique=True,
268 273
         help_text=_("Universal Product Code (UPC) is an identifier for "

+ 9
- 1
src/oscar/apps/catalogue/managers.py Visa fil

@@ -99,7 +99,15 @@ class ProductQuerySet(models.query.QuerySet):
99 99
 
100 100
     def browsable(self):
101 101
         """
102
-        Excludes non-canonical products.
102
+        Excludes non-canonical products and non-public products
103
+        """
104
+        return self.filter(parent=None, is_public=True)
105
+
106
+    def browsable_dashboard(self):
107
+        """
108
+        Products that should be browsable in the dashboard.
109
+
110
+        Excludes non-canonical products, but includes non-public products.
103 111
         """
104 112
         return self.filter(parent=None)
105 113
 

+ 18
- 0
src/oscar/apps/catalogue/migrations/0015_product_is_public.py Visa fil

@@ -0,0 +1,18 @@
1
+# Generated by Django 2.1.5 on 2019-02-11 11:55
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('catalogue', '0014_auto_20181115_1953'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='product',
15
+            name='is_public',
16
+            field=models.BooleanField(default=True, help_text='Show this product in search results and catalogue listings.', verbose_name='Is public'),
17
+        ),
18
+    ]

+ 8
- 1
src/oscar/apps/catalogue/views.py Visa fil

@@ -1,6 +1,6 @@
1 1
 from django.contrib import messages
2 2
 from django.core.paginator import InvalidPage
3
-from django.http import HttpResponsePermanentRedirect
3
+from django.http import Http404, HttpResponsePermanentRedirect
4 4
 from django.shortcuts import get_object_or_404, redirect
5 5
 from django.utils.http import urlquote
6 6
 from django.utils.translation import gettext_lazy as _
@@ -41,10 +41,17 @@ class ProductDetailView(DetailView):
41 41
         if redirect is not None:
42 42
             return redirect
43 43
 
44
+        # Do allow staff members so they can test layout etc.
45
+        if not self.is_viewable(product, request):
46
+            raise Http404()
47
+
44 48
         response = super().get(request, **kwargs)
45 49
         self.send_signal(request, response, product)
46 50
         return response
47 51
 
52
+    def is_viewable(self, product, request):
53
+        return product.is_public or request.user.is_staff
54
+
48 55
     def get_object(self, queryset=None):
49 56
         # Check if self.object is already set to prevent unnecessary DB calls
50 57
         if hasattr(self, 'object'):

+ 1
- 1
src/oscar/apps/dashboard/catalogue/forms.py Visa fil

@@ -187,7 +187,7 @@ class ProductForm(forms.ModelForm):
187 187
     class Meta:
188 188
         model = Product
189 189
         fields = [
190
-            'title', 'upc', 'description', 'is_discountable', 'structure']
190
+            'title', 'upc', 'description', 'is_public', 'is_discountable', 'structure']
191 191
         widgets = {
192 192
             'structure': forms.HiddenInput()
193 193
         }

+ 1
- 1
src/oscar/apps/dashboard/catalogue/views.py Visa fil

@@ -124,7 +124,7 @@ class ProductListView(SingleTableView):
124 124
         """
125 125
         Build the queryset for this list
126 126
         """
127
-        queryset = Product.objects.browsable().base_queryset()
127
+        queryset = Product.objects.browsable_dashboard().base_queryset()
128 128
         queryset = self.filter_queryset(queryset)
129 129
         queryset = self.apply_search(queryset)
130 130
         return queryset

+ 28
- 0
tests/_site/apps/catalogue/migrations/0014_auto_20181115_1953.py Visa fil

@@ -0,0 +1,28 @@
1
+# Generated by Django 2.0.7 on 2018-11-15 19:53
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('catalogue', '0013_auto_20170821_1548'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='product',
15
+            name='date_created',
16
+            field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Date created'),
17
+        ),
18
+        migrations.AlterField(
19
+            model_name='productimage',
20
+            name='display_order',
21
+            field=models.PositiveIntegerField(db_index=True, default=0, help_text='An image with a display order of zero will be the primary image for a product', verbose_name='Display order'),
22
+        ),
23
+        migrations.AlterField(
24
+            model_name='productrecommendation',
25
+            name='ranking',
26
+            field=models.PositiveSmallIntegerField(db_index=True, default=0, help_text='Determines order of the products. A product with a higher value will appear before one with a lower ranking.', verbose_name='Ranking'),
27
+        ),
28
+    ]

+ 18
- 0
tests/_site/apps/catalogue/migrations/0015_product_is_public.py Visa fil

@@ -0,0 +1,18 @@
1
+# Generated by Django 2.1.5 on 2019-02-11 11:55
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('catalogue', '0014_auto_20181115_1953'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='product',
15
+            name='is_public',
16
+            field=models.BooleanField(default=True, help_text='Show this product in search results and catalogue listings.', verbose_name='Is public'),
17
+        ),
18
+    ]

+ 30
- 0
tests/functional/catalogue/test_catalogue.py Visa fil

@@ -46,6 +46,24 @@ class TestProductDetailView(WebTestCase):
46 46
         response = self.app.get(child_url)
47 47
         self.assertEqual(http_client.MOVED_PERMANENTLY, response.status_code)
48 48
 
49
+    def test_is_public_on(self):
50
+        product = create_product(upc="kleine-bats", is_public=True)
51
+
52
+        kwargs = {'product_slug': product.slug, 'pk': product.id}
53
+        url = reverse('catalogue:detail', kwargs=kwargs)
54
+        response = self.app.get(url)
55
+
56
+        self.assertTrue(response.status_code, 200)
57
+
58
+    def test_is_public_off(self):
59
+        product = create_product(upc="kleine-bats", is_public=False)
60
+
61
+        kwargs = {'product_slug': product.slug, 'pk': product.id}
62
+        url = reverse('catalogue:detail', kwargs=kwargs)
63
+        response = self.app.get(url, expect_errors=True)
64
+
65
+        self.assertTrue(response.status_code, 404)
66
+
49 67
 
50 68
 class TestProductListView(WebTestCase):
51 69
 
@@ -73,6 +91,18 @@ class TestProductListView(WebTestCase):
73 91
 
74 92
         self.assertContains(page, "Page 1 of 2")
75 93
 
94
+    def test_is_public_on(self):
95
+        product = create_product(upc="grote-bats", is_public=True)
96
+        page = self.app.get(reverse('catalogue:index'))
97
+        products_on_page = list(page.context['products'].all())
98
+        self.assertEqual(products_on_page, [product])
99
+
100
+    def test_is_public_off(self):
101
+        create_product(upc="kleine-bats", is_public=False)
102
+        page = self.app.get(reverse('catalogue:index'))
103
+        products_on_page = list(page.context['products'].all())
104
+        self.assertEqual(products_on_page, [])
105
+
76 106
 
77 107
 class TestProductCategoryView(WebTestCase):
78 108
 

+ 9
- 0
tests/functional/dashboard/test_catalogue.py Visa fil

@@ -84,6 +84,15 @@ class TestCatalogueViews(WebTestCase):
84 84
         self.assertIn(product2, products_on_page)
85 85
         self.assertNotIn(product3, products_on_page)
86 86
 
87
+    def test_is_public(self):
88
+        # Can I still find non-public products in dashboard?
89
+        product = create_product(is_public=False, upc="kleine-bats")
90
+        page = self.get("%s?upc=%s" % (
91
+            reverse('dashboard:catalogue-product-list'), product.upc
92
+        ))
93
+        products_on_page = [row.record for row in page.context['products'].page.object_list]
94
+        self.assertEqual(products_on_page, [product])
95
+
87 96
 
88 97
 class TestAStaffUser(WebTestCase):
89 98
     is_staff = True

Laddar…
Avbryt
Spara