1

I'm trying to understand the best way to display ForeignKey filtered data in a Django model.

I have three models reduced to this:

// models.py
class Publisher(models.Model)
    def publisher_name = models.TextField()
    def publisher_slug = models.SlugField()
    def founded_year = models.IntegerField()

class Album(models.Model)
    def album_name = models.TextField()
    def publisher = models.ForeignKey('Publisher', related_name='albums')

class Song(models.Model)
    def song_name = models.TextField()
    def album = models.ForeignKey('Album', related_name='songs')
    def published_year = models.IntegerField()

I have a URL that is composed of: /<publisher>/<published_year>/

The view I'm having trouble composing is supposed to be details like this:

Title of: Publisher.publisher_name List of All Albums by the publisher: List of All songs from that album published the same year as the publisher__published_year: List of All songs from that album published as the url

The way, I've tried to do this that works right now is similar to this:

// views.py
class SongYearView(TemplateView):
    def get_context_data(self, **kwargs):
        context = super(SongYearView, self).get_context_data(**kwargs)
        context['publisher'] = Publisher.objects.get(slug=kwargs['publisher_slug']
        album_list=[]
        for album in context['publisher'].albums.all():
            single_album = dict()
            single_album['album'] = album
            single_album['publisher_year_song'] = album.songs.filter(published_year=context['publisher'].published_year)
            single_album['filtered_year_song'] = album.songs.filter(published_year=kwargs['published_year']
            album_list.append(single_album)
        context['albums'] = album_list
        return context

Then in the template I'm doing (with stripped out formatting)

// template.html
{{ publisher.name }}
{% for album in albums %}
    {{  album.album.album_name }}

    {% for song in album.publisher_year_song %}
        {{ song.song_name }}
    {% endfor %}

    {% for song in album.filtered_year_song %}
        {{ song.song_name }}
    {% endfor %}
{% endfor %}

While this does work, it's not pretty and I'm pretty sure there are better ways of doing this.

This is an odd example, but just a basic example of my more detailed models. The way to think about it is Publisher -> Album -> Song or A -> B -> C. And I'm trying to get a view of all B items, that are only linked with a specific A item and then get two sets of C items for each B item, where one set is filtered on an A property and the other set is filtered on a passed argument from a URL.

I tried to get a custom model.Manager to help get this constructed, but didn't have much luck.

Coding Monkey
  • 990
  • 10
  • 17
  • I'm wondering if this should better be in [Code review](http://codereview.stackexchange.com/) – oliverpool Oct 16 '15 at 13:03
  • Ahh, I didn't know about the code review section. This query is more to show I've tried something, but I would like to know the correct way program the scenario. – Coding Monkey Oct 16 '15 at 13:08
  • Since your code works, it fits perfectly into Code Review guidelines, but you might have much less exposition... (and I think, that posting it here on SO is still fine) – oliverpool Oct 16 '15 at 13:14
  • I renamed the template filter: I find it better like this. (if my answer solves your question, please mark it as accepted - check near the arrows) – oliverpool Oct 16 '15 at 13:36

1 Answers1

1

You could do add a custom template filter of_year:

@register.filter
def of_year(songs, year):
    return songs.filter(published_year=year)

And change your template to

// template.html
{{ publisher.name }}
{% for album in publisher.albums %}
    {{  album.album.album_name }}

    {% for song in album.songs|of_year:publisher.founded_year %}
        {{ song.song_name }}
    {% endfor %}

    {% for song in album.songs|of_year:filtered_year %}
        {{ song.song_name }}
    {% endfor %}
{% endfor %}

And clean your view:

// views.py
class SongYearView(TemplateView):
    def get_context_data(self, **kwargs):
        context = super(SongYearView, self).get_context_data(**kwargs)
        context['publisher'] = Publisher.objects.get(slug=kwargs['publisher_slug'])
        context['filtered_year'] = kwargs['published_year']
        return context

Edit: rename the template filter

Community
  • 1
  • 1
oliverpool
  • 1,624
  • 13
  • 30
  • Is using filters in this way the right approach? Wouldn't this be better handled via a custom model manager instead? As I've read that the templates should avoid functionality. – Coding Monkey Oct 16 '15 at 15:09
  • I'm not good enough at django to handle it via a custom model manager. `As I've read that the templates should avoid functionality.` I do share this point of view and I think that in your case the filtering has more to do with the presentation (template), than the model (I try to limit my filters to one additional argument and 2 lines of code) – oliverpool Oct 16 '15 at 15:24