Reputation: 77
Right now I have defined my URLs such that they can include different combinations of category (always on), attribute_slugs (1 and/or 2) and brand_slug, all using the urlpatterns defined below.
In my views.py file I make sure that each slug exists in the respective model, or else a 404 is thrown. I also make sure that the attributes have a specific ordering, so that I avoid identical pages where the attributes are just switched around in the URLs. Then the context is sent to a template.
In my template for the categories, I implement an ecommerce filter based on the context. For example, the filter can show links to the attributes contained by the products on that particular page. So if you are on the page: /shoes/, then the color filter has the options: /shoes/green, /shoes/blue/, /shoes/black/ etc. This filter is made with the default { % url % } template tag with parameters based on the context.
It works fine, but there is one issue. It is based on a lot of logical statements in the template, which is cumbersome. For example, there is an if-statement to check if there is 0 attribute, 1 attribute and 2 attributes on the current page. I also check the same for brands, meaning there are a lot of if-statements in the template (remember they multiply). I have to do this, because I need to write different versions of the { % url % } template tag based on the context of the page. Specifically, the parameters of the template tag changes based on the page.
For example, if a given page does not include any attributes, then the clickable URL to any attribute must be: {% url 'products:product_categories' slug=category.slug attribute_slug=ATTRIBUTEVALUE %}. In other words: Only the first attribute_slug is set.
On the other hand, if the page has one attribute already, then the ordering of the new and the old attribute is checked to see which one comes first, and then the new attribute is added in the URL, like this: {% url 'products:product_categories' slug=category.slug attribute_slug=ATTRIBUTEVALUE attribute_slug2=ATTRIBUTEVALUE2 %}.
Now, this seems relatively simple when I explain it like this. But there are a lot of different cases that makes this more cumbersome, for example also considering the brand_slug, and considering that I also have functionality used to remove a particular attribute or brand from the URL.
So finally, my question is; Is there a better approach urlpatterns and dynamic parameters in template URL tags?
One thought I have myself is to pass an abitrary number of arguments in the URL template tag (like the **args or **kwargs in classical pythonic). This would look similar to this: {% url 'products:product_categories' *KWARGS %}. I just really havn't found a way for this to work.
Is there anything smarter you can think of?
Thanks in advance, and best regards.
# urls.py
urlpatterns = [
# Category + Attributes
re_path(r"^vk/(?P<slug>[\w-]+)/$", ProductCategoryListView.as_view(), name="product_categories"),
re_path(r"^vk/(?P<slug>[\w-]+)/(?P<attribute_slug>[\w-]+){1}/$", ProductCategoryListView.as_view(), name="product_categories"),
re_path(r"^vk/(?P<slug>[\w-]+)/(?P<attribute_slug>[\w-]+){1}/(?P<attribute_slug2>[\w-]+){1}/$", ProductCategoryListView.as_view(), name="product_categories"),
# Category + Brand + Attributes
re_path(r"^vk/(?P<slug>[\w-]+)/(?P<brand_slug>[\w-]+)/$", ProductCategoryListView.as_view(), name="product_categories"),
re_path(r"^vk/(?P<slug>[\w-]+)/(?P<brand_slug>[\w-]+)/(?P<attribute_slug>[\w-]+){1}/$", ProductCategoryListView.as_view(), name="product_categories"),
re_path(r"^vk/(?P<slug>[\w-]+)/(?P<brand_slug>[\w-]+)/(?P<attribute_slug>[\w-]+){1}/(?P<attribute_slug2>[\w-]+){1}/$", ProductCategoryListView.as_view(), name="product_categories"),
]
Upvotes: 0
Views: 106
Reputation: 8212
Writing up the above discussion as suggested.
For whatever reason, passing the parameters as GET parameters with a querystring is ruled out. They have to be in the URL.
My suggestion is then to parse only the constant parameter(s) in the URLconfig. For the others, roll them up into a single string parameter by imposing a syntactic restriction on what values they can and cannot have. Something like http://.../attr:attrval;brand:somebrand;category:one;...
parsed as, say, otherparams.
To minimize code duplication thereafter, it would be possible to write a common code Mixin for all class-based views which are going to use this technique. Subclass the setup
method:
class OtherparamsMixin( object):
def setup(self, request, *args, **kwargs):
super().setup( request, *args, **kwargs)
self.otherparams = {}
if 'otherparams' in self.kwargs:
params = self.kwargs['otherparams'].split(';')
for p in params:
k, v = p.split(':',1)
self.otherparams[k] = v
and then any view defined with this Mixin will have self.otyherparams
automagically available for use
class SomeView( OtherparamsMixin, CBVclass):
and for commonly-used CBV classes one can define base classes including the mixin:
class SiteFormView( OtherparamsMixin, FormView):
pass
class SiteListView( OtherparamsMixin, ListView):
pass
etc. and then use these instead of the standard CBVs.
For redirecting, it may be useful also to inject a method for converting a dict representation of a set of otherparams
back into a text string:
@staticmethod
def otherparam_str( otherparams):
p = []
for k,v in otherparams.items():
if ";" in v:
raise ValueError( f'Semicolon not permitted in an otherparams value, but found "{k}" with value "{v}" )
p.append( f"{k}:{v}" )
return p.join(";")
Upvotes: 1