django-taggit common tags only visible from base url

Viewed 71

I have a basic application where Users create Groups. These Groups can have multiple tags (by way of django-taggit) and, once posted, are rendered on a home page, sorted newest to oldest.

In my views.py I have two different tag lists that the home page uses: a list of all tags and a list of common tags:

class Home(ListView):
   model = Group
   template_name = 'home.html'
   ordering = ['-posted']

   def get_common_tags(self):
          # common tags
       common_tags = Group.tags.most_common()[:2]
       return common_tags

   def get_context_data(self, **kwargs):
       kwargs['common_tags'] = self.get_common_tags()
       return super().get_context_data(**kwargs)

class TagFilter(ListView):
   model = Group
   template_name = 'home.html'
   context_object_name = 'group'
   
   def get_queryset(self):
          # all tags
       tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
       return tag_list

   def get_context_data(self, *args, **kwargs):
       tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
       tag_list_is_empty = True
       if tag_list.exists():
           tag_list_is_empty = False
       context = super(TagFilter, self).get_context_data()
       context["tag_list_is_empty"] = tag_list_is_empty
       return context

The idea is that when you click on a tag, it filters the Group results based on that tag:

{% get_taglist as tags %}


    <div>
      Filter by tag:
      {% for tag in tags %}
        <a href="{% url 'search_by_tag' tag.slug %}">#{{tag.name}}</a>
      {% endfor %}
    </div>

    <div>
      Common tags: 
      {% for tag in common_tags %}
        <a href="{% url 'search_by_tag' tag.slug %}">#{{tag.name}}</a>
      {% endfor %}
    </div>

    {% if tag_list_is_empty == True %}
      <p>Sorry! No group found with this tag</p>
    {% endif %}

    {% for group in object_list %}
      <a href="{% url 'group_detail' group.pk %}">
          <h1>{{group.name}}</h1>
          <p>{{ group.description|slice:":50"}}...</p>
      </a>
    {% endfor %}

The issue I'm running into is that my list of common tags is only visible at the base url localhost:8000/. But when you click on a tag that brings you to a url of localhost:8000/tags/slug, the list of common tags disappears.

My first thought was that it was something to do with the fact that common_tags is returned in the Home view explicitly and once the url changes it stops being displayed.

This led me to moving both functions into TagFilter, but then I couldn't access tag_list_is_empty in my template. I'm assuming because you can't have two get_context_data function sin the same view.

I'm just wondering what the best way to go about retaining all these variables and passing them to the template so that both tag lists are displayed no matter the url, and the conditional to see if tag_list_is_empty so that I can display the message <p>Sorry! No group found with this tag</p>

2 Answers

In get_common_tags you don't need the request headers or anything. So you can extract this function from Home class and you might consume/call from multiple views.

def get_common_tags(self):
    #consider using try/catch        
    try:
        return Group.tags.most_common()[:2]
    except:
        return {'no tag'}

class Home(ListView):
   model = Group
   template_name = 'home.html'
   ordering = ['-posted']

   def get_context_data(self, **kwargs):
       kwargs['common_tags'] = get_common_tags()
       return super().get_context_data(**kwargs)

class TagFilter(ListView):
   model = Group
   template_name = 'home.html'
   context_object_name = 'group'
   
   def get_queryset(self):
          # all tags
       tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
       return tag_list

   def get_context_data(self, *args, **kwargs):
       tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
       tag_list_is_empty = True
       if tag_list.exists():
           tag_list_is_empty = False
       context = super(TagFilter, self).get_context_data()
       context["tag_list_is_empty"] = tag_list_is_empty
       # add common tags to context
       context["common_tags"] = get_common_tags()
       return context

First of all, as mentioned by Egemen SOYLU in the answers, take the common tags function out of the Home view so you can use it in any other. The "context" is specific to each view and are not inherited when following links to another view. If you switch pages and there is a new view for it, a new request is sent from the client side and the server renders the corresponding page, context included.

With the above said, you have a few options.

Make a function to retrieve common tags:

def get_common_tags():  # this function needs no input, just trigger
    try:
        tags = Group.tags.most_common()[:2]
        return tags
    except:
        return []  # Return an empty list if not found.

To get the common tags context information from a view, insert the function and assign it a named list (a.k.a. "key-value pair") in the context dictionary:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs) #retrieve default view context info, i.e.: model query.
    """add your other context info here..."""
    context['common_tags'] = get_common_tags() #retrieve the common tags from the function
    """context['key_name'] = values..."""
    return context

The above should work for any view where you want to include the common tags and you should refer to it as 'common_tags' in the template.

For the filtered view, you can simplify the logic a bit by grouping functions:

class TagFilter(ListView):
    model = Group
    template_name = 'home.html'
    context_object_name = 'group' """<--- you can get rid of this line 
    as you can build your context information at will. Keep reading..."""

""" def get_queryset(self): <--- you don't need this function, send 
    everything together in the context dictionary.
    Just add the queryset to the get_context_data() function below...
        tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
        return tag_list"""  

def get_context_data(self, *args, **kwargs):
    context = super().get_context_data(**kwargs) """Whatever you need from the
    request kwargs..."""
    tag_list = [] """First define an empty list in case getting 
    the Group model information returns an empty list."""

    try:
        tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
    else:
        pass """do not modify the empty tag_list"""
    group = Group.objects.all() """ignore this line if you're using the
    context_object_name variable or add any filter by using the kwargs.get
    method. In this last scenario, don't forget to add context['group']=group"""

    context["tag_list"] = tag_list """empty if the Group filter returns empty"""

    context["common_tags"] = get_common_tags() """ this will return an empty
    queryset if there are no common tags found, as defined by the function 
    above"""

    return context

So, how do you use empty values from context dictionaries? Go to your HTML templates and follow this logic:

  • For an empty queryset, use "if" or "if not" Django template tags.
  • If the key name returns content, do your algo in the template.

So, in the template:

{% if tag_list %} <!-- if there is content in "tag_list" key name, do... -->
    {%  for tag in tag_list %}
        ...
    {% endfor %}
{% else %} <!-- if the list is empty, do something (i.e.: display a message) -->
{% endif %}

Same applies for any list in the context dict. If you have doubts with the template tags, {% if not key_from_context_dict %} works the same as {% if key_from_context_dict == Null %}}

Hope this works. :)

Related