Aditya Singh
Aditya Singh

Reputation: 63

Foreign method access in django template

I m beginner. I'm trying to access a related item of the model Product in the template layer of a ProductDetailView. How can I retrieve the ImageFields of the Products' Brand's BrandImages? I have to traverse one forward and one reverse ForeignKey.

Edited to include get_logo_url

What is wrong with the get_logo_url function?

products/models.py

class Product(models.Model):

    brand = TreeForeignKey('Brand', verbose_name='parent category', related_name='products', default='')
    title = models.CharField(max_length=120)
    description = models.TextField(max_length=500, blank=True, null=True)
    price = models.DecimalField(decimal_places=2, max_digits=20)
    active = models.BooleanField(default=True)
    category = TreeForeignKey('Category', verbose_name='parent category', related_name='products', default='')
    slug = models.SlugField(default='')

    objects = ProductManager()

    class Meta:
            unique_together = ('slug', 'category')

    def get_absolute_url(self):
        return reverse("product_detail", kwargs={"pk":self.pk})

    def __unicode__(self):
        return self.title

    def get_image_url(self):
        img = self.productimage_set.first()
        if img:
            return img.image.url
        return img

brands/models.py

def image_upload_to(instance, filename):
    title = instance.brand.title
    slug = slugify(title)
    file_extension = filename.split(".")[1]
    new_filename = "%s.%s" % (instance.id, file_extension)
    return "products/%s/%s" % (slug, new_filename)

class BrandImage(models.Model):

    brand = models.ForeignKey('Brand', related_name='brandimages') 
    is_slider = models.BooleanField(default=False)
    is_featured = models.BooleanField(default=False)
    is_logo = models.BooleanField(default=False)
    image = models.ImageField(upload_to=image_upload_to)

    def __unicode__(self):
        return self.brand.title

   def get_logo_url(self):
      if is_logo:
         img = self.brandimage_set.first()
         if img:
            return img.image.url
         return img

    def thumb(self):
        if self.image:
            return u'<img src="%s" width=120 height=120 />' % (self.image.url)
        else:
            return u'No image file found'
    thumb.allow_tags = True

class Brand(MPTTModel):

    title = models.CharField(max_length=50, default='')
    parent = TreeForeignKey('self', null=True, blank=True, verbose_name='parent brand', related_name='brands')
    slug = models.SlugField(unique=True)

    def get_absolute_url(self):
            return reverse('brands', kwargs={'path': self.get_path()})

    def __unicode__(self):
        return self.title

template

<div class="rightpart">
    <div class="prodbrand h2">
        <h1>{{ product.brand }}</h1>        
        <div class="brandlogo">
            {% for image in product.brand.brandimages.all %}
                <img src="{{image.get_logo_url }}"/>
            {% endfor %}
        </div>
    </div>
    <div class="prodtitle"><h2>{{ product.title }}</h2>
</div>

views.py

 class ProductDetailView(DetailView):
    model = Product
    template_name = 'products/product.html'

    def get_context_data(self , *args , **kwargs):
        context = super(ProductDetailView , self).get_context_data(*args,**kwargs)
        instance = self.get_object()
        context["related"] = Product.objects.get_related(instance)
        return context

urls.py

url(r'^$', ProductDetailView.as_view(), name='products'),

is there a way to access foreign fields in django templates like this?

Upvotes: 1

Views: 289

Answers (1)

Sebastian Wozny
Sebastian Wozny

Reputation: 17506

As you are using a ListView to display your products there's several things to notice:

  • get_context_data() must return a dictionary: return context is missing
  • super().get_context_data should be called with *args,**kwargs incase you decide to subclass the ProductListView at a later point in time.
  • super().get_context_data will contain a object_list key which contains the list of objects returned by get_queryset(), in your case objects of class Product.

When accessing a property from a template, django will attempt to call it without parameters if it is callable. This is often useful e.g.: for {{ request.user.is_authenticated }} or product.brand.brandimages.all

Your template should look like this:

product_list.html

{% for product in object_list %}
    <div class="rightpart">
        <div class="prodbrand h2">
            <h1>{{ product.brand }}</h1>      
            <div class="brandlogo">    
                {% for image in product.brand.brandimages.all %}
                    <img src="{{image.image.url}}"/>
                {% endfor %}
            </div><!-- End brandlogos -->
        </div><!-- End prodbrand -->
        <div class="prodtitle">
            <h2>{{ product.title }}</h2>
        </div>
    </div><!-- End rightpart -->
{% endfor %}

Take into account that this will incur several database lookups from the template. You generally want to avoid the case that your presentation layer reaches into the database, which is why you should prefer to do the database lookup in the corresponding View. Also for property access consider using select_related and prefetch_related as appropriate to avoid unneeded database queries.

views.py

class ProductListView(ListView):

    model = Product
    queryset = Product.objects.all().active()

    def get_context_data(self, *args, **kwargs):
        context = super(ProductListView, self).get_context_data(*args, **kwargs)
        context["now"] = timezone.now()
        return context

    def get_queryset(self, *args, **kwargs):
        # We call super() for potential subclasses
        queryset = super(ProductListView, self).get_context_data(*args, **kwargs)
        queryset.prefetch_related('brand__brandimages')
        return queryset

Upvotes: 2

Related Questions