Efrin
Efrin

Reputation: 2423

Django - Models FK/many2many relationships

I need to understand a bit better how do FK/m2m relationships work. I've prepared Images model for uploading images and it has an additional feature - it can be categorized by adding to a specific gallery (m2m relation to gallery).

To access gallery name I just had to do a query set for example:

Images.objects.filter(gallery__gallery_name = '')

I'd like to reverse the query a little bit so from Gallery model I can access pictures which are in specific gallery (gallery_name).

How I can do that?

Models:

class Images(models.Model):
   image = models.ImageField(upload_to=update_filename, blank=True, null=True, verbose_name="Obrazek")
   gallery = models.ForeignKey('Gallery', blank=True, null=True)

class Gallery(models.Model):
    gallery_name = models.CharField(max_length=128)
    gallery_description = models.TextField(blank=True, null=True)

View:

def index(request):
    p = Gallery.objects.filter(gallery_name="main").order_by('-id')
    return TemplateResponse(request, 'gallery.html', 
                                    {'gallery': p,
                                    },)

Template:

{% for n in gallery.all %}
<h2 class="center">{{n.gallery_name}}</h2>
<hr>
    {% for n in gallery.images_set %}
       <div class="grid_4">
        {{ n.image }}
       </div>
{% endfor%}

Upvotes: 1

Views: 335

Answers (3)

zxq9
zxq9

Reputation: 13164

Try something along the lines of:

# models.py
class Gallery(models.Model):
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=200, null=True)
    images = models.ManyToMany(Image)

class Image(models.Model):
    title = models.CharField(max_length=30)
    caption = models.CharField(max_length=200, null=True)
    image = models.ImageField(upload_to=SOMEPLACE_FOR_MEDIA)

From here you should be able to do things like:

image = Image.objects.get(title="Girl Holding Cheese")
related_galleries = image.gallery_set.all()

or something similar as needed to pull what you want. The same goes the other way. To pull all images in a gallery you would do

gallery = Gallery.objects.get(name="Cheesy Wimmin")
related_images = gallery.images.all()

Though the assignments at the end aren't necessary, I usually just pass gallery.images.all() or image.gallery_set.all() directly. Note the "_set" at the end of the reference from the object that does not contain the M2M definition.

On the subject of direct usage, you can do compound references like

Image.objects.get(title="Girl Holding Cheese").gallery_set.all()

as well, but you have to decide when this makes code more clear and concise and when it just makes it more confusing to read later.

I hope this put you in the right direction.

Update

In your comment below you noticed that you cannot do

images = Images.objects.filter(gallery_set="Cheesy Wimmins")
related_galleries = images.gallery_set.all()

This is because you would be trying to filter() or all() on a queryset, not an individual model. So to make this work you can use a for loop in your template. Something like

# views.py
galleries = Gallery.objects.all()
return render(request, 'some/template.html', {'galleries': galleries})

And then

<!-- templates/some/template.thml -->
{% for gallery in galleries %}
    <div class="gallery">
    <h2>{{ gallery.name }}</h2>
    {% for item in gallery.images.all %}
        <div class="image">
            {{ item.image }}
        </div>
    {% endfor %}
    </div>
{% endfor %}

or something like this. Of course, you need to do whatever formatting steps you want to make this look right, but that's a way to get at your data.

Upvotes: 1

gallery is a QuerySet - it doesn't have a images_set.

This is where naming your variables more appropriately can easily start preventing these problems: for example, galleries would be more appropriate for a list of Gallery objects... then, galleries.images_set would immediately raise red flags.

Anyways, you need to call images_set on what you've called n

{% for n in gallery.all %}
<h2 class="center">{{n.gallery_name}}</h2>
<hr>
    {% for n in n.images_set.all %} 
       <div class="grid_4">
        {{ n.image }}
       </div>
{% endfor%}

Upvotes: 1

Chris Pratt
Chris Pratt

Reputation: 239460

The problem is with the {% for n in gallery.images_set %} bit in your template. images_set is a related manager, not a queryset. To get a queryset, you need to call all or another of the DBAPI methods that return querysets. So, just change it to gallery.images_set.all, and you're good.

Upvotes: 1

Related Questions