Avishka Dambawinna
Avishka Dambawinna

Reputation: 1215

How to fetch data from one to many relationship using Django queries

I'm new to Django and trying to get path details of one specific image from the ProductImage table using the place field in the table and display the image.Where place == 'Main Product Img' Product and ProductImage has one to many relationship.

My database has two tables named Product(id, name, price,..) and ProductImage(id, productid, image, place) which contains three images('Main Product IMG', 'Sub Img 1', 'Sub Img 2')

Here is my models.py

from django.db import models
from django.contrib.auth.models import User


class Product(models.Model):
    name = models.CharField(max_length=200)
    desc = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    discount = models.DecimalField(max_digits=4, decimal_places=0)

    def __str__(self):
        return self.name

class ProductImage(models.Model):
    PLACEHOLDER= (
        ('Main Product Image', 'Main Product Image'),
        ('Sub Img 1', 'Sub Img 1'),
        ('Sub Img 2', 'Sub Img 2'),
    )
    product = models.ForeignKey(Product, related_name='image_set', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='images/')
    place = models.CharField(max_length=20, choices=PLACEHOLDER)

    def __str__(self):
        return str(self.id)

views.py

def menu(request):
    products = Product.objects
    images = ProductImage.objects

    context = {'products': products, 'images': images}
    return render(request, 'store/menu.html', context)

Template

{% for product in products.all %}

  <a href="#">

    <img src="{{ images.filter(product__id=product.id, place='Main Product Image')[0].image.url }}" class="img-fluid mt-3">

    {% if product.discount != 0 %}
      <span class="bonus">{{ product.discount }}% off</span>
    {% endif %}
  </a>

settings.py

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),

]

# Media files

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR

urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('menu', views.menu, name='menu'),
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

I was able to write a query in the shell to do that

>>> images = ProductImage.objects
>>> images.filter(product__id='1', place='Main Product Image')[0].image
<ImageFieldFile: images/img3.jpg>

The rest of the operations are working correctly. I've been trying to solve this issue for many hours now but keep getting

TemplateSyntaxError at /menu
Could not parse the remainder: '(product__id=product.id, place='Main Product Image')[0].image.url'
from 'images.filter(product__id=product.id, place='Main Product Image')[0].image.url'

Any help that is appreciated.

Upvotes: 1

Views: 617

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476594

You can not make method calls in a Django template, the parenthesis is deliberately not allowed to prevent people from writing business logic in the template.

You can define a method, for example in the Product model:

class Product(models.Model):
    name = models.CharField(max_length=200)
    desc = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    discount = models.DecimalField(max_digits=4, decimal_places=0)

    @property
    def main_product_image(self):
        return self.image_set.filter(place='Main Product Image').first()

    def __str__(self):
        return self.name

and then render this with:

<img src="{{ product.main_product_image.image.url }}"; class="img-fluid mt-3">

but this will result in an N+1 problem, if method calls with parameters were allowed, this would also result in an N+1 problem.

You can make use of a Prefetch object [Django-doc] to load the main images in bulk instead:

from django.db.models import Prefetch

def menu(request):
    products = Product.objects.prefetch_related(
        Prefetch(
            'image_set',
            ProductImage.objects.filter(place='Main Product Image'),
            to_attr='main_images'
        )
    )

    context = {'products': products}
    return render(request, 'store/menu.html', context)

then you can render this with:

{% for product in products %}
    <img src="{{ product.main_images.0.image.url }}" class="img-fluid mt-3">;
    …
{% endfor %}

Upvotes: 1

Related Questions