Skip to content

Commit

Permalink
Hierarchical product URL support.
Browse files Browse the repository at this point in the history
Adds the new setting SHOP_USE_HIERARCHICAL_URLS (default disabled).  When
enabled, a product URL is determined as follows:

  - If the product belongs to exactly one category, the product's URL will be:
      <category-slug>/<product-slug>/<product-id>

  - Otherwise, the product's URL will be as before:
      product/<product-slug>
  • Loading branch information
mik3y committed Jun 7, 2013
1 parent 432b6b1 commit 655154c
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 3 deletions.
9 changes: 9 additions & 0 deletions cartridge/shop/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,12 @@
editable=False,
default=True,
)

register_setting(
name="SHOP_USE_HIERARCHICAL_URLS",
label=_("Use hierarchical product URLs"),
description="If set and a product only belongs to a single category, "
"generate the product's URL in terms of that category.",
editable=False,
default=False,
)
25 changes: 25 additions & 0 deletions cartridge/shop/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,25 @@ def save(self, *args, **kwargs):

@models.permalink
def get_absolute_url(self):
if settings.SHOP_USE_HIERARCHICAL_URLS:
category = self.get_category()
if category:
return ("shop_category_product", (), {
"category_slug": category.get_raw_slug(),
"slug": self.slug,
"product_id": self.id})
return ("shop_product", (), {"slug": self.slug})

def get_category(self):
"""
Returns the single category this product is associated with, or None
if the number of categories is not exactly 1.
"""
categories = self.categories.all()
if len(categories) == 1:
return categories[0]
return None

def copy_default_variation(self):
"""
Copies the price and image fields from the default variation
Expand Down Expand Up @@ -382,6 +399,14 @@ def filters(self):
return reduce(operator, filters)
return products

def get_raw_slug(self):
"""
Returns this object's slug stripped of its parent's slug.
"""
if not self.parent or not self.parent.slug:
return self.slug
return self.slug.lstrip(self.parent.slug).lstrip('/')


class Order(models.Model):

Expand Down
2 changes: 2 additions & 0 deletions cartridge/shop/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
url("^checkout/$", "checkout_steps", name="shop_checkout"),
url("^checkout/complete/$", "complete", name="shop_complete"),
url("^invoice/(?P<order_id>\d+)/$", "invoice", name="shop_invoice"),
url("^(?P<category_slug>.+)/(?P<slug>.+)/(?P<product_id>\d+)$",
"category_product", name="shop_category_product"),
)
27 changes: 24 additions & 3 deletions cartridge/shop/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
order_handler = handler(settings.SHOP_HANDLER_ORDER)


def product(request, slug, template="shop/product.html"):
def product(request, slug, template="shop/product.html", product=None):
"""
Display a product - convert the product variations to JSON as well as
handling adding the product to either the cart or the wishlist.
"""
published_products = Product.objects.published(for_user=request.user)
product = get_object_or_404(published_products, slug=slug)
if not product:
published_products = Product.objects.published(for_user=request.user)
product = get_object_or_404(published_products, slug=slug)

fields = [f.name for f in ProductVariation.option_fields()]
variations = product.variations.all()
variations_json = simplejson.dumps([dict([(f, getattr(v, f))
Expand Down Expand Up @@ -81,6 +83,25 @@ def product(request, slug, template="shop/product.html"):
}
return render(request, template, context)

def category_product(request, category_slug, slug, product_id,
template="shop/product.html"):
"""
Display a product in terms of its category's slug. Wrapper around
product(). Only enabled when SHOP_USE_HIERARCHICAL_URLS is True.
"""
published_products = Product.objects.published(for_user=request.user)
product_obj = get_object_or_404(published_products, id=product_id)
category = product_obj.get_category()

# Tolerate stale URLs by redirecting to the new URL.
if not settings.SHOP_USE_HIERARCHICAL_URLS or not category:
# Setting is disabled or category has been removed.
return redirect(product_obj.get_absolute_url(), permanent=True)
elif slug != product_obj.slug or category_slug != category.get_raw_slug():
# Category or product slug mismatch.
return redirect(product_obj.get_absolute_url(), permanent=True)

return product(request, slug, template=template, product=product_obj)

@never_cache
def wishlist(request, template="shop/wishlist.html"):
Expand Down

0 comments on commit 655154c

Please sign in to comment.