How to put all previous categories slug into url?

Hi, i hope somebody can give me an advice or help.

I am creating a web shop with django.

What do we need?

To have all category slugs in the url for any child category, not just a child but any sub sub category.

Example:

I created one category:
http://127.0.0.1:8000/shop/category/main/

Then i want to create a new category “sub1” and to have this “main category” as a parent, so it looks like this:
http://127.0.0.1:8000/shop/category/main/sub1/

We have this setup:

These are the models

from django.db import models
from django.conf import settings
from django.urls import reverse
from django.utils.safestring import mark_safe
import uuid
from django.urls import reverse

def file_upload_path(instance, filename):
    return f'user-{instance.id}/shop/images/{filename}'


# Product Category
class ProductCategory(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=110, unique=True)
    description = models.TextField(null=True, blank=True) 
    image = models.ImageField(upload_to=file_upload_path, default='default-images/demo.jpg')
    #id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # relations
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='shop_categories')

    # parent category
    parent_category = models.ForeignKey('self', blank=True, null=True, related_name='children_categories', on_delete=models.CASCADE)

    def __str__(self):
        return self.title
    
    '''def get_absolute_url(self):
        return reverse('shop:view_product_category', args=[str(self.slug)])'''
    
    def get_absolute_url(self):
        if self.parent_category:
            return reverse('front:category_detail_child', kwargs={'parent_slug': self.parent_category.slug, 'child_slug': self.slug})
        else:
            return reverse('front:category_detail_parent', kwargs={'parent_slug': self.slug})

    def clean(self):
        self.title = self.title.capitalize()
        self.slug = self.slug.lower()

    def category_image(self):
        return mark_safe(f"<img src='{self.image.url}' width='50' height='50'/>")
    
    class Meta:
        verbose_name_plural = 'Categories'


# Product
class Product(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    STATUS_CHOICES = (
        ('draft','Draft'),
        ('published','Published'),
    )
    title = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=110, unique=True)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    image = models.ImageField(upload_to=file_upload_path, default='default-images/demo.jpg')
    tizer = models.TextField(max_length=200, null=True, blank=True)
    content = models.TextField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_featured = models.BooleanField(default=False)
    is_new = models.BooleanField(default=False)
    status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='published')
    
    #sku = ShortUUIDField(length=12, max_length=20, prefix='sku', alphabet='abcdefgh1234567')

    # relations
    category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, related_name='products')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='products')

    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return reverse('front:view_product', args=[str(self.slug)])
    
    def product_image(self):
        return mark_safe(f'<img src="{self.image.url}" width="50" height="50"/>')
    
    def clean(self):
        self.title = self.title.capitalize()
        self.slug = self.slug.lower()

    class Meta:
        ordering = ('-created_at',)

    
# Order Model    
class Order(models.Model):
    class OrderStatus(models.TextChoices):
        CONFIRMED = 'CONFIRMED', 'Confirmed'
        CANCELED = 'CANCELED', 'Canceled'
        IN_PROCESS = 'IN_PROCESS', 'In process'

    # ID 
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # ID for tracking
    order_id = models.CharField(max_length=8, unique=True, editable=False)
    #order_id = models.UUIDField(default=uuid.uuid4, max_length=10, editable=False, unique=True)
    full_name = models.CharField(max_length=100)
    email = models.EmailField(max_length=100)
    shipping_address = models.CharField(max_length=200)
    amount_paid = models.DecimalField(max_digits=7, decimal_places=2)
    date_ordered = models.DateTimeField(auto_now_add=True)

    # relation to user
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True)

    # order status - trigger emails
    status = models.CharField(max_length=30, choices=OrderStatus.choices, default=OrderStatus.IN_PROCESS)
    
    # relation to delivery option
    delivery_option = models.ForeignKey('DeliveryOption', on_delete=models.CASCADE)

    # delivery message
    delivery_message = models.ForeignKey('OrderMessage', on_delete=models.CASCADE, null=True, blank=True)
    
    def __str__(self):
        return f'Order {self.id}'
    
    class Meta:
        ordering = ['-date_ordered']

    def save(self, *args, **kwargs):
        if not self.order_id:
            self.order_id = str(uuid.uuid4())[:8]
        super().save(*args, **kwargs)    
    
class OrderItem(models.Model):
    quantity = models.PositiveBigIntegerField(default=1) 
    price = models.DecimalField(max_digits=7, decimal_places=2)

    # relations
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True)
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    def get_item_total(self):
        total = self.quantity * self.price
        return total

    def __str__(self):
        return f'Order item: #{self.id}'
    

# Shipping address model - for logged in users who made a purchase or filled in this form, shipping info will be automatically filled in on the checkout page
class ShippingAddress(models.Model):
    full_name = models.CharField(max_length=100)
    email_address = models.EmailField(max_length=100)
    #phone_number = models.CharField(max_length=20)
    address1 = models.CharField(max_length=200)
    city = models.CharField(max_length=100)
    zipcode = models.CharField(max_length=10) 
    state = models.CharField(max_length=100, null=True, blank=True)

    # relation to user
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True)

    # relation to delivery option
    #delivery_option = models.ForeignKey('DeliveryOption', on_delete=models.CASCADE, null=True, blank=True)

    def __str__(self):
        return f'Shipping Address: {str(self.id)}' 
    
    class Meta:
        verbose_name_plural = 'Shipping Address'
    

# Delivery options
class DeliveryOption(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=7, decimal_places=2)
    note = models.TextField(max_length=300)
    # relation to Order

    def __str__(self):
        return self.name

# Order Message
class OrderMessage(models.Model):
    content = models.TextField(max_length=300, null=True, blank=True)

    def __str__(self):
        return self.content

I have this view for rendering one product category and it checks for parent child:

def category_detail(request, parent_slug, child_slug=None):
    if child_slug:
        # Ako postoji child_slug, prikaži detinjsku kategoriju unutar roditeljske kategorije
        parent_category = get_object_or_404(ProductCategory, slug=parent_slug)
        category = get_object_or_404(ProductCategory, slug=child_slug, parent_category=parent_category)
    else:
        # Ako nema child_slug, prikaži roditeljsku kategoriju
        category = get_object_or_404(ProductCategory, slug=parent_slug, parent_category=None)
    
    products = Product.objects.filter(category=category)

    context = {
        'products':products
    }
    return render(request, 'shop/frontend/categories/view.html', context)

Urls are as follows:

# If child shop category
path('shop/category/<slug:parent_slug>/<slug:child_slug>/', shop_views.category_detail, name='category_detail_child'), 

# If parent shop category
path('shop/category/<slug:parent_slug>/', shop_views.category_detail, name='category_detail_parent'),

That is all working fine.

But the problem is when i want to add deeper category relation. So for a child category i want to assign as a parent to some category.

So i add a new category named “Sub2” and we need this url:
http://127.0.0.1:8000/shop/category/main/sub1/sub2

But currently it shows (with this setup that is):
http://127.0.0.1:8000/shop/category/sub1/sub2/

So it doesn’t show all parent categories before that.

I hope you can understand the problem and maybe help.

I tried with chatgpt for days now, but it wasn’t able to do it.

If i need to change anything i will, i am open to all advices and help, this was just my effort with my current Django knowlegde :slight_smile:

Thank you