Question

I have two models:

class Product(models.Model):
    name = models.CharField(max_length=255)

class ProductPhoto(models.Model):
    product = models.ForeignKey('Product', related_name='photos')
    is_live = models.IntegerField(choices=LIVE_CHOICES, default=1)

    live = LiveManager()

class LiveManager(Manager):
    def get_query_set(self):
        return super(LiveManager, self).get_query_set().filter(is_live=1)

I am trying to get live photos from product detail templates.

Tried,

{% for photo in product.photos.live %}

which doesn't work and looked at docs and couldn't find examples. Is it possible to call the foreign key's manager from a template? Should I make a function in Product model that returns product photo queryset?

Thank you.

Was it helpful?

Solution

Well, the way you're using it is wrong, anyways. You'd just be passing the manager into the for loop, not a queryset that could be iterated over. However, photos is itself a "related manager", not the actual ProductPhoto model, and related managers are based off the first listed manager or objects (the default manager).

Since, you define live, but do not also define objects, you don't actually have an objects manager on this model, i.e. the this will fail: ProductPhoto.objects.all(). Remember, if you define a custom manager on your model, Django will no longer automatically add one named objects.

The good news is that because live is the default manager now, you can use it just like:

{% for photo in product.photos.all %}

And, you'll only get "live" objects. The bad news is that this will break a lot of other things that depend on the default manager being the full collection of objects (admin for example). You're essentially hiding the block of "non-live" objects.

What you should have is:

class ProductPhoto(models.Model):
    product = models.ForeignKey('Product', related_name='photos')
    is_live = models.IntegerField(choices=LIVE_CHOICES, default=1)

    objects = models.Manager()
    live = LiveManager()

Notice that objects is defined manually and it's first, meaning it will remain the default manager. However, that then no longer allows you to use your live manager in the template. Generally, for something like this, it's best to just use a single manager and add a method to it to return "live" objects:

class ProductPhotoQuerySet(models.query.QuerySet):
    def live(self):
        return self.filter(is_live=1)

class ProductPhotoManager(models.Manager):
    use_for_related_fields = True

    def get_query_set(self):
        return ProductPhotoQuerySet(self.model)

    def live(self, *args, **kwargs):
        return self.get_query_set().live(*args, **kwargs)

Here, we're actually subclassing both QuerySet and Manager. This will allow you to chain live anywhere instead of just at the front. For example, if you just had a custom manager without a custom queryset, you would only be able to do ProductPhoto.objects.live().filter(...) and not ProductPhoto.objects.filter(...).live().

So, you then add that to your model as objects (taking the place of the default one Django provides):

class ProductPhoto(models.Model):
    product = models.ForeignKey('Product', related_name='photos')
    is_live = models.IntegerField(choices=LIVE_CHOICES, default=1)

    objects = ProductPhotoManager()

And, finally, you'll be able to use it in your template:

{% for photo in product.photos.live %}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top