0

I'm building a shopping cart. In my shopping cart an item can be composed of other items. I need to display a set of items with their corresponding associated parts in a single template. I know how to show a single item with its corresponding parts in a template, but I can't seem to figure out how to show more than one item, each with its own list of included parts.

I have fiddled with every permutation of tags in the template file:

# checkout.html

{% for item in cart_items %}
    <tr>
        <td class="left">
        {{ item.name }}
        <ul>
        {% for part in item.product.buildpart.part_set.all %}
            <li>{{ part.name }}
        {% endfor %}
        </ul>

        </td>
        <td>${{ item.price }}</td>
        <td>{{ item.quantity }}</td>
        <td class="right">${{ item.lineItemTotal }}</td>
    </tr>
{% endfor %}

Here is the vew that generates the template:

# views.py

def checkout(request):
    cart_items = get_cart_items(request)

    <snip>    

    return render(request, 'checkout.html', locals())

And here's the get_cart_items() function that returns all the items in the user's shopping cart:

# cart.py

def get_cart_items(request):
    """ return all items from the current user's cart """
    return CartItem.objects.filter(cart_id=get_cart_id(request))

Here's the CartItem model:

# models.py

class Item(models.Model):
    cart_id = models.CharField(max_length=50)
    quantity = models.IntegerField(default=1)
    product = models.ForeignKey(PartModel, unique=False)

    class Meta:
        abstract = True

    <snip>

class CartItem(Item):
    date_added = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['date_added']
        verbose_name = "Cart Item"

    <snip>

The 'product' field is a ForeignKey to the PartModel model:

# models.py

class PartModel(models.Model):
    family = models.ForeignKey(PartFamily)
    name = models.CharField("Model Name", max_length=50, unique=True)
    slug = models.SlugField(help_text="http://www.Knowele.com/<b>*slug*</b>",
                            unique=True)
    <snip>
    buildpart = models.ManyToManyField('self', through='BuildPart',
                                symmetrical=False, related_name='+')

    class Meta:
        ordering = ['name']
        verbose_name = "Product Model"

    <snip>

The PartModel model has a ManyToMany relationship with itself through the buildpart field and the BuildPart model to facilitate the notion of catalog items that can be composed of other catalog items:

# models.py

class Build(models.Model):
    build = models.ForeignKey(PartModel, related_name='+')
    part = models.ForeignKey(PartModel, related_name='+')
    quantity = models.PositiveSmallIntegerField(default=1)

    class Meta:
        abstract = True
        unique_together = ('build', 'part')

    def __unicode__(self):
        return self.build.name + ' with ' + str(self.quantity) + ' * ' + \
               self.part.family.make.name + ' ' + self.part.name

class BuildPart(Build):
    pass

    class Meta:
        verbose_name = "Build Part"

I can't seem to make the necessary ForeignKey traversals in the template (listed above) in order to get all the parts associated with the user's items in the CartItem model. Is it something I'm not doing right in the template or am I not packaging up the right QuerySets in my view?

The second part of this issue is that once I get those parts, I need them to show up in the order specified in the 'order' integer field of the PartType model:

# models.py

class PartType(models.Model):
    name = models.CharField("Part Type", max_length=30, unique=True)
    slug = models.SlugField(unique=True)
    order = models.PositiveSmallIntegerField()
    description = models.TextField(blank=True, null=True)

    class Meta:
        ordering = ['name']
        verbose_name = "Product Type"

    def __unicode__(self):
        return self.name

class PartFamily(models.Model):
    make = models.ForeignKey(PartMake)
    type = models.ForeignKey(PartType)
    name = models.CharField("Family Name", max_length=30,
                             unique=True)
    slug = models.SlugField(unique=True)
    url = models.URLField("URL", blank=True, null=True)
    description = models.TextField(blank=True, null=True)

    class Meta:
        ordering = ['name']
        verbose_name = "Product Family"
        verbose_name_plural = "Product Families"

    def __unicode__(self):
        return self.name

So as you can see, in the PartModel model, the 'family' field is a ForeignKey to the PartFamily model, and in the PartFamily model the 'type' field is a ForeignKey to the PartType model, within which is the all-important 'order' field that the parts need to be ordered by.

I hope this makes sense and you can see why this is so complicated for a noob like me.

pyrodney
  • 370
  • 3
  • 14
  • 2
    I answered part 1 on your previous question. The answer remains the same; this time you are doing `buildpart.part_set`, but buildpart is still the many-to-many accessor so there is no such thing as part_set, just iterate through `buildpart.all`. – Daniel Roseman Dec 18 '14 at 10:20
  • 1
    Also, it's really not clear why you have all these abstract models which you then inherit from. What's the point? `BuildPart` should just have the fields on directly, and delete the `Build` model. – Daniel Roseman Dec 18 '14 at 10:21
  • Thanks Daniel Roseman! You got it working for me! I'll give you the winning answer if you would repost the answer the normal way so I can click the checkmark thingy. **Also, can you (or someone else) please still tell me how to get them to list in the correct order (by the 'order' field in the PartType model?** To answer your question, I need the abstract models because multiple models inherit from them, I just snipped those models for brevity but they're there and I use them. You'll see those other models come up in my previous question to this one. Thanks again! – pyrodney Dec 18 '14 at 11:02
  • 1
    For the answer to your second question: http://stackoverflow.com/questions/6540032/sorting-related-items-in-a-django-template – waterproof Dec 18 '14 at 17:27

1 Answers1

1

Just iterate on item.product.buildpart.all:

{% for item in cart_items %}
    [...]
    {% for part in item.product.buildpart.all %}
        {{ part.name }}[...]
    {% endfor %}
{% endfor %}
waterproof
  • 4,943
  • 5
  • 30
  • 28