I tried to get values with using three tables.
cart_c = Cart.objects.select_related('item').filter(user=2)
context = {'cart_c': cart_c}
I got QuerySet, 'cart_c', but I could not refer ItemPhoto.photo from 'cart_c'.
So, I made three tables joined with prefetch_related().
items = Item.objects.prefetch_related('item_photo', 'cart_item').filter(cart__user=2)
context = {'items': items}
But, I do not have any good idea to access ItemPhoto.photo and Cart.quantity except this.
items[0].cart_item.values()[0].get('quantity')
# Or [i.quantity for item in items for i in item.cart_item.all()]
I think this way is not proper and sophisticated to use in Django Template System.
It's complicated I think.
I found a relative question in StackOverFlow, and one of the answers is same like code I wrote.
items[0].cart_item.all()[0]
Could anyone teach me how I should change and write code to send QuerySet or dictionary to Django Template to access Item.name, Item.price, ItemPhoto.photo and Cart.quantity with using three tables, Item, ItemPhoto and Cart?
Thanks.
models.py(omitted)
class Item(models.Model): # Product
name = models.CharField("item_name", max_length=255, blank=False, default="")
price = models.DecimalField("price",
max_digits=12,
decimal_places=2,
default=0.00,
blank=False,
)
class ItemPhoto(models.Model): # Photo
item = models.ForeignKey(Item, on_delete=models.CASCADE,
related_name="item_photo", verbose_name="item_id",)
photo = models.ImageField("item_photo", blank=True,
upload_to=get_itemimage_path)
class Cart(models.Model): # Order
user = models.ForeignKey(settings.AUTH_USER_MODEL,
verbose_name="user",
on_delete=models.CASCADE, null=False,
related_name="cart_user",)
item = models.ForeignKey(Item, verbose_name="item_id",
on_delete=models.CASCADE,
related_name="cart_item", null=False,)
quantity = models.PositiveSmallIntegerField("quantity", default=0)
Python: 3.9 / Django 4.1
What you have as Cart
, should actually be CartItem
. A model that represents the quantity of each item that a user can put in their actual Cart
. In my answer, I'm referring it as CartItem
.
You can access Item's attributes, its photo, and cart quantity in the following manner:
For Postgres, we've used ArrayAgg
to combine the list of photos per item and annotate it on the CartItem
object:
from django.contrib.postgres.aggregates import ArrayAgg
cart_item_qs = CartItem.objects.filter(user=2).select_related('item').annotate(
item_photos=ArrayAgg('item__item_photo__photo')
)
# You can then pass this in the context
context = {"cart_items": cart_item_qs}
Finally, in the template, you can iterate on your cart_items
like:
{% for cart_item in cart_items %}
{{ cart_item.item.name }} - {{ cart_item.item.price }}
{% for photo in cart_item.item_photos %}
<img src="{{ photo.url }}" />
{% endfor %}
{% endfor %}
UPDATE
For MySQL, we can use Func
expression to apply GROUP_CONCAT
SQL function to concatenate all photo
from CartItem
cart_item_qs = CartItem.objects.filter(user=2).select_related('item').annotate(
item_photos=models.Func(models.Value('GROUP_CONCAT'), models.Value('item_photo.photo'), function='GROUP_CONCAT')
)
# You can then pass this in the context
context = {"cart_items": cart_item_qs}
To parse this in the template, you'll do something like this:
{% for cart_item in cart_items %}
{{ cart_item.item.name }} - {{ cart_item.item.price }}
{% for photo in cart_item.photos.split(",") %}
<img src="{{ photo }}" />
{% endfor %}
{% endfor %}
If the GROUP_CONCAT
is not supported, then you can also write this query using Subquery
in Django.
PS: You can use django-versatilimagefield for storing your images. It provides a lot of additional functionality for storing and retrieving images in different dimensions.