brazyko
brazyko

Reputation: 63

Django. Categories and subcategories

I want to make a navigation through categories and subcategories in Django.

Now I have this:

127.0.0.1:8000/products/last subcategory/product slug

and I want to make

127.0.0.1:8000/products/category/subcategory/subsubcategory/.../product slug

smth like this:

my models.py:

class Product(models.Model):
    title       = models.CharField(max_length=120)
    slug        = models.SlugField(unique=True)
    category    = models.ForeignKey('Category', on_delete=models.CASCADE) 
    description = models.TextField(blank=True,null=True)

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super(Product,self).save(*args, **kwargs)

    def get_absolute_url(self):
        return self.slug

class Category(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    parent = models.ForeignKey('self',blank=True, null=True         ,related_name='child', on_delete=models.CASCADE)
    class Meta:
        unique_together = ('slug', 'parent',)    
        verbose_name_plural = "categories"   

    def __str__(self):                           
        full_path = [self.name]            
        k = self.parent
        while k is not None:
            full_path.append(k.name)
            k = k.parent

        return ' -> '.join(full_path[::-1])

my urls.py

path('', ProductList, name='product-list1'),
path('<category_slug>/', products_by_category, name='product-categories'),

views.py

def ProductList(request):
    products = Product.objects.all()
    products = products.order_by('-publish')
    categories = Category.objects.filter(parent__isnull=True)
    context = {
        'products':products,
        'categories':categories,
    }
    template = 'products/product_list.html'
    return render(request, template ,context)


def products_by_category(request,category_slug):
    products = Product.objects.all()
    categories = Category.objects.filter(parent__isnull=True)
    slug = category_slug
    if slug:
        category_s = get_object_or_404(Category,slug = slug)    
        products = products.filter(category = category_s)
    context = {
        'products':products,
        'categories':categories,
        'category':category_s,
        'page_obj':page_obj,
    }
    template = 'products/products_by_category_list.html'
    return render(request,template,context)

And how can I display all products that are in one category. By the way, is it possible to show all items that belong to one parent category if sorting by that parent cat.? For example all food

Upvotes: 6

Views: 16767

Answers (1)

Adam Richard
Adam Richard

Reputation: 564

It sounds like what you're trying to accomplish is to create a tree structure for your categories and subcategories. There are a couple of tools out there to help make this much more manageable in Django. I've spent a fair amount of time trying to implement something along those lines myself and ultimately found these tools to save a lot of time and frustration.

The two that rise to the top for this purpose seem to by Django-mptt and Django-treebeard. Both are quite useful, though with different strengths and weaknesses. For me, I tended to prefer working with Django-mptt largely because I found the documentation more thorough.

An example of what you could do with mptt:

models.py

...

from mptt.models import MPTTModel, TreeForeignKey

class Product(models.Model):
    title       = models.CharField(max_length=120)
    slug        = models.SlugField(unique=True)
    description = models.TextField(blank=True,null=True)
    category    = models.ForeignKey(
        'Category',
        related_name="products",
        on_delete=models.CASCADE
    ) 

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super(Product,self).save(*args, **kwargs)

    def get_absolute_url(self):
        return self.slug

class Category(MPTTModel):
    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    parent = TreeForeignKey(
        'self',
        blank=True,
        null=True,
        related_name='child',
        on_delete=models.CASCADE
    )

    class Meta:
        unique_together = ('slug', 'parent',)    
        verbose_name_plural = "categories"   

    def __str__(self):                           
        full_path = [self.name]            
        k = self.parent
        while k is not None:
            full_path.append(k.name)
            k = k.parent

        return ' -> '.join(full_path[::-1])

views.py

...

def index(request):
    if request.method == 'GET':
        category_id = int(request.GET.get('category_id', default=1))
        current_category = Category.objects.get(pk=category_id)

        children = current_category.get_children()
        ancestors = current_category.get_ancestors()
        products = current_category.products.all()

        context = {
            'categories': children,
            'current_category': current_category,
            'ancestors': ancestors,
            'products': products,
        }

    return render(request, 'your_site/index.html', context)

Hopefully, you can get a sense from looking at the index view how handy this additional context could be when you write your template to display this information. This is a very bare-bones example, but should provide some ideas.

Upvotes: 14

Related Questions