Search code examples
pythondjangocategoriesslug

django subcategory slug filter


Getting my head around Django and followed the tango with Django book, but this last issue gets me after adding subcategories, which are not included in that tutorial.

I have the following:

models.py
class Category(models.Model):
"""Category"""
name = models.CharField(max_length=50)
slug = models.SlugField()


def save(self, *args, **kwargs):

                    #self.slug = slugify(self.name)
            self.slug = slugify(self.name)
            super(Category, self).save(*args, **kwargs)
def __unicode__(self):
    return self.name


class SubCategory(models.Model):
"""Sub Category"""
category = models.ForeignKey(Category)
name = models.CharField(max_length=50)
slug = models.SlugField()

def save(self, *args, **kwargs):

            self.slug = slugify(self.name)
            super(SubCategory, self).save(*args, **kwargs)

def __unicode__(self):
    return self.name

and

urls.py
(r'^links/$', 'rango.views.links'),
(r'^links/(?P<category_name_slug>[\w\-]+)/$', 'rango.views.category'),  
(r'^links/(?P<category_name_slug>[\w\-]+)/(?P<subcategory_name_slug>[\w\-]+)/$', 'rango.views.subcategory'),  

and

views.py
@require_GET
def links(request):
"""Linkdirectory Page"""
category_list = Category.objects.order_by('name')
context_dict = {'categories': category_list}
return render(request, 'links.html', context_dict)

@require_GET
def category(request, category_name_slug):
"""Category Page"""
category = Category.objects.get(slug=category_name_slug)
subcategory_list = SubCategory.objects.filter(category=category)
context_dict = {'subcategories': subcategory_list}
return render(request, 'category.html', context_dict)

@require_GET
def subcategory(request, subcategory_name_slug, category_name_slug):
"""SubCategory Page"""
context_dict = {}
try:
    subcategory = SubCategory.objects.get(slug=subcategory_name_slug)
    context_dict['subcategory_name'] = subcategory.name
    websites = Website.objects.filter(sub_categories=subcategory)
    context_dict['websites'] = websites
    context_dict['subcategory'] = subcategory
except SubCategory.DoesNotExist:
return render(request, 'subcategory.html', context_dict)

This all works nicely up to the point I add subcategories with the same name, e.g. the subcategory "other" for multiple categories.

I understand why, when I reach "def subcategory" my slug will return multiple subcategories so I need to limit these to the related category in some way, like a

"SELECT 
subcategory = SubCategory.objects.get(slug=subcategory_name_slug)
WHERE 
subcategory = SubCategory.objects.filter(category=subcategory)
CLAUSE" 

or something ;)

Not sure what's the best route to take on this and how to filter these


Solution

  • Given that you may have two different SubCategory objects with the same name for two different Category objects, as you suggested you can add the Category as an additional filter.

    Filter by both SubCategory.slug and Category.slug

    To achieve this I see you have a view that takes slugs for both SubCategory and Category, that you defined like this subcategory(request, subcategory_name_slug, category_name_slug). Those are sufficient to filter:

    subcategory = SubCategory.objects.get(
        slug=subcategory_name_slug,
        category__slug=category_name_slug
    )
               ^
               |__ # This "double" underscore category__slug is a way to filter
                   # a related object (SubCategory.category)
                   # So effectively it's like filtering for SubCategory objects where
                   # SubCategory.category.slug is category_name_slug
    

    You see above I've used SubCateogry.objects.get(...) to get a single object instead of `SubCategory.objects.filter(...) which can return many objects.

    Enforcing unicity of SubCategory.name per Category

    To do this safely with get(), there needs to be a guarantee that for any given Category, there will no more than one Subcategory with the same name

    You can enforce this condition with unique_together

    class SubCategory(models.Model):
        class Meta:
            unique_together = (
                ('category', 'name'),          # since slug is based on name,
                                               # we are sure slug will be unique too
            )