|
|
@@ -1,25 +1,8 @@
|
|
1
|
1
|
from django.db import models
|
|
2
|
|
-from django.db.models import Exists, OuterRef
|
|
3
|
|
-
|
|
4
|
|
-
|
|
5
|
|
-def product_class_as_queryset(product):
|
|
6
|
|
- "Returns a queryset with the product_classes of a product (only one)"
|
|
7
|
|
- ProductClass = product._meta.get_field("product_class").related_model
|
|
8
|
|
- return ProductClass.objects.filter(
|
|
9
|
|
- pk__in=product.__class__.objects.filter(pk=product.pk)
|
|
10
|
|
- .annotate(
|
|
11
|
|
- _product_class_id=models.Case(
|
|
12
|
|
- models.When(
|
|
13
|
|
- structure=product.CHILD, then=models.F("parent__product_class")
|
|
14
|
|
- ),
|
|
15
|
|
- models.When(
|
|
16
|
|
- structure__in=[product.PARENT, product.STANDALONE],
|
|
17
|
|
- then=models.F("product_class"),
|
|
18
|
|
- ),
|
|
19
|
|
- )
|
|
20
|
|
- )
|
|
21
|
|
- .values("_product_class_id")
|
|
22
|
|
- )
|
|
|
2
|
+
|
|
|
3
|
+from oscar.core.loading import get_class
|
|
|
4
|
+
|
|
|
5
|
+ExpandUpwardsCategoryQueryset = get_class("catalogue.expressions", "ExpandUpwardsCategoryQueryset")
|
|
23
|
6
|
|
|
24
|
7
|
|
|
25
|
8
|
class RangeQuerySet(models.query.QuerySet):
|
|
|
@@ -27,31 +10,55 @@ class RangeQuerySet(models.query.QuerySet):
|
|
27
|
10
|
This queryset add ``contains_product`` which allows selecting the
|
|
28
|
11
|
ranges that contain the product in question.
|
|
29
|
12
|
"""
|
|
30
|
|
- def contains_product(self, product):
|
|
31
|
|
- "Return ranges that contain ``product`` in a single query"
|
|
|
13
|
+
|
|
|
14
|
+ def _excluded_products_clause(self, product):
|
|
32
|
15
|
if product.structure == product.CHILD:
|
|
33
|
|
- return self._ranges_that_contain_product(
|
|
34
|
|
- product.parent
|
|
35
|
|
- ) | self._ranges_that_contain_product(product)
|
|
36
|
|
- return self._ranges_that_contain_product(product)
|
|
37
|
|
-
|
|
38
|
|
- def _ranges_that_contain_product(self, product):
|
|
39
|
|
- Category = product.categories.model
|
|
40
|
|
- included_in_subtree = product.categories.filter(
|
|
41
|
|
- path__startswith=OuterRef("path")
|
|
42
|
|
- )
|
|
43
|
|
- category_tree = Category.objects.annotate(
|
|
44
|
|
- is_included_in_subtree=Exists(included_in_subtree.values("id"))
|
|
45
|
|
- ).filter(is_included_in_subtree=True)
|
|
|
16
|
+ # child products are excluded from a range if either they are
|
|
|
17
|
+ # excluded, or their parent.
|
|
|
18
|
+ return ~(
|
|
|
19
|
+ models.Q(excluded_products=product)
|
|
|
20
|
+ | models.Q(excluded_products__id=product.parent_id)
|
|
|
21
|
+ )
|
|
|
22
|
+ return ~models.Q(excluded_products=product)
|
|
46
|
23
|
|
|
|
24
|
+ def _included_products_clause(self, product):
|
|
|
25
|
+ if product.structure == product.CHILD:
|
|
|
26
|
+ # child products are included in a range if either they are
|
|
|
27
|
+ # included, or their parent is included
|
|
|
28
|
+ return models.Q(included_products=product) | models.Q(
|
|
|
29
|
+ included_products__id=product.parent_id
|
|
|
30
|
+ )
|
|
|
31
|
+ else:
|
|
|
32
|
+ return models.Q(included_products=product)
|
|
|
33
|
+
|
|
|
34
|
+ def _productclasses_clause(self, product):
|
|
|
35
|
+ if product.structure == product.CHILD:
|
|
|
36
|
+ # child products are included in a range if their parent is
|
|
|
37
|
+ # included in the range by means of their productclass.
|
|
|
38
|
+ return models.Q(classes__products__parent_id=product.parent_id)
|
|
|
39
|
+ return models.Q(classes__id=product.product_class_id)
|
|
|
40
|
+
|
|
|
41
|
+ def _get_category_ids(self, product):
|
|
|
42
|
+ if product.structure == product.CHILD:
|
|
|
43
|
+ # Since a child can not be in a catagory, it must be determined
|
|
|
44
|
+ # which category the parent is in
|
|
|
45
|
+ ProductCategory = product.productcategory_set.model
|
|
|
46
|
+ return ProductCategory.objects.filter(product_id=product.parent_id).values("category_id")
|
|
|
47
|
+
|
|
|
48
|
+ return product.categories.values("id")
|
|
|
49
|
+
|
|
|
50
|
+ def contains_product(self, product):
|
|
|
51
|
+ # the wide query is used to determine which ranges have includes_all_products
|
|
|
52
|
+ # turned on, we only need to look at explicit exclusions, the other
|
|
|
53
|
+ # mechanism for adding a product to a range don't need to be checked
|
|
47
|
54
|
wide = self.filter(
|
|
48
|
|
- ~models.Q(excluded_products=product), includes_all_products=True
|
|
|
55
|
+ self._excluded_products_clause(product), includes_all_products=True
|
|
49
|
56
|
)
|
|
50
|
57
|
narrow = self.filter(
|
|
51
|
|
- ~models.Q(excluded_products=product),
|
|
52
|
|
- models.Q(included_products=product)
|
|
53
|
|
- | models.Q(included_categories__in=category_tree)
|
|
54
|
|
- | models.Q(classes__in=product_class_as_queryset(product)),
|
|
|
58
|
+ self._excluded_products_clause(product),
|
|
|
59
|
+ self._included_products_clause(product)
|
|
|
60
|
+ | models.Q(included_categories__in=ExpandUpwardsCategoryQueryset(self._get_category_ids(product)))
|
|
|
61
|
+ | self._productclasses_clause(product),
|
|
55
|
62
|
includes_all_products=False,
|
|
56
|
63
|
)
|
|
57
|
64
|
return wide | narrow
|