0

I'd like to figure out a way to use a key-value lookup for a second model within a Django for loop.

This question on dictionary key-value loops in templates is on the right track, but I am using two normalized data models. There is a 'Parent' data object (EventDetail) that contains the relevant link and 'Child' data object (DateEvent) that has date-specific information with data on user actions.

I have a template tag, link_if_exists, that is being duplicated many times. Django-debug-toolbar tells me this is being duplicated 76 times right now. This 'duplicate' message is, itself, duplicated many times.

This is what I have now:

app_extras.py

@register.filter()
def link_if_exists(title):
    """Return a link if it exists"""
    event_link = None
    try:
        event_link = EventDetail.objects.filter(title=title).values_list('link', flat=True)[0]
    except IndexError:
        pass

    if event_link != "":
        return event_link

    return False

template.html

{% load 'app_extras' %}

{% for item in child_date_items %}
    {% if item.title|link_if_exists %}
        <a href="{{item.title|link_if_exists}}">{{item.title}}</a>
    {% endif %}
{% endfor %}

models.py

class DateEvent(models.Model)
    title = models.CharField(max_length=250)
    event_date = models.DateField(default=date.today)
    event_datetime = models.DateTimeField(auto_now=False)

class EventDetail(models.Model)
    title = models.CharField(max_length=250)
    link = models.URLField(max_length=200, default="", blank=True)

views.py

class ProblemView(TemplateView):
    template_name = "template.html"

    def get_context_data(self, **kwargs):
        context = super(ProblemView, self).get_context_data(**kwargs)
        date_today = utils.get_date_today()
        child_date_items = DateEvent.objects.filter(event_date=date_today)
        context['child_date_items'] = child_date_items
        return context

Django-debug-toolbar output

SELECT link FROM
table WHERE title='title'
...
| duplicated 74 times

Something like this is what I am after:

Add 'event_detail' to views.py

class NewView(TemplateView):
    template_name = "template.html"

    def get_context_data(self, **kwargs):
        context = super(NewView, self).get_context_data(**kwargs)
        date_today = utils.get_date_today()
        child_date_items = DateEvent.objects.filter(event_date=date_today)
        context['child_date_items'] = child_date_items
        event_detail = EventDetail.objects.all()
        context['event_detail'] = event_detail
        return context

Lookup title as key to get 'link' value in ideal_template.html

{% for item in child_date_items %}
    {% if event_detail[item.title]['link'] %}
        <a href="event_detail[item.title]['link']">{{item.title}}</a>
    {% endfor %}
{% endfor %}

I don't know if this functionality exists so I am open to other suggestions. I am also open to computing this in views.py and iterating over a common object in the template. I understand that I could duplicate the link data and just add a link column in the DateEvent model, but that seems wasteful and I'd like to avoid that if possible. This isn't the only field I need this type of logic, so adding everything to the Child object would take up a lot of extra space in the database.

Scott Skiles
  • 3,647
  • 6
  • 40
  • 64
  • For posterity, I added a quick workaround with "Template Fragment Caching": https://docs.djangoproject.com/en/2.1/topics/cache/#template-fragment-caching . This served me well but I likely do need to do some work on my models as noted by @Endre Both – Scott Skiles Mar 07 '19 at 20:39

1 Answers1

1

You need to do some work on your models. If there is a one-to-many relationship between them (several DateEvents for one EventDetail), then rather than duplicating title manually in both of them and then linking both again manually, you should formalize the relationship at the model level:

class EventDetail(models.Model)
    title = models.CharField(max_length=250)
    link = models.URLField(max_length=200, default="", blank=True)

class DateEvent(models.Model)
    event_detail = models.ForeignKey(EventDetail, on_delete=models.CASCADE)
    event_date = models.DateField(default=date.today)
    event_datetime = models.DateTimeField(auto_now=False)

Then you can refer to the link field from any DateEvent object without any conditionals and duplicate queries (if you use select_related() properly).

So if you're working with DateEvents, you'd use:

date_events = (
    DateEvent.objects
        .filter(event_date=date_today)
        .select_related('event_detail')
)

Then, in the template:

{% for item in child_date_items %}
    <a href="{{item.event_detail.link}}">{{item.event_detail.title}}</a>
{% endfor %}
Endre Both
  • 5,540
  • 1
  • 26
  • 31
  • Great answer. I saw 'select_related' elsewhere. But how do I tie in your answer to the desired result? I am looping over `DateEvent` and want to include the link from `EventDetail` along with most data in `DateEvent` in the template. After this work is done on my models, how do I get there? – Scott Skiles Mar 04 '19 at 15:53
  • Also, do you have any thoughts on the performance of this approach in general as well as considering normalization vs. denormalization? – Scott Skiles Mar 04 '19 at 15:54
  • I wouldn't give much thought to performance until the project works as it should. But generally speaking, if you're wielding your DB queries wisely, you don't need denormalization as it gives you new problems to take care of in exchange for performance gains you can usually achieve in other ways. – Endre Both Mar 04 '19 at 16:06
  • Great, thank you for updating the template code as well. – Scott Skiles Mar 04 '19 at 16:12