Search code examples
pythondjangovariablestemplatetag

Issue in Django count string in a template


I am facing issues trying to get a count. Hope you can help me.

In the template, if I insert {{ message.count }} not happens 'coz message is a string. If I insert {% load inbox %} {% inbox_count as message %} it returns all unread messages, but I need to get the unread messages sent by a given user, in my case, a candidate to a job position.

How can I do it?

#models.py
 class CandidateToJob(models.Model):
     job = models.ForeignKey(Job, related_name='applied_to')
     candidate = models.ForeignKey(Candidate, related_name='from_user')
     STATUS_CHOICES = (
        ('0', 'New'),
        ('1', 'Not approved'),
        ('2', 'Approved')
     )
     status = models.CharField(max_length=2, choices=STATUS_CHOICES)

     def __unicode__(self):
        return self.candidate.user.get_full_name()

 AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')

 class MessageManager(models.Manager):

    def inbox_for(self, user):
        """
        Returns all messages that were received by the given user
        """
        return self.filter(
            recipient=user
        )

class Message(models.Model):
    """
    A private message from user to user
    """
    body = models.TextField(_("Mensagem"))
    sender = models.ForeignKey(AUTH_USER_MODEL, related_name='sent_messages')
    recipient = models.ForeignKey(AUTH_USER_MODEL, related_name='received_messages', null=False, blank=True)
    sent_at = models.DateTimeField(null=True, blank=True)
    read_at = models.DateTimeField(_("read at"), null=True, blank=True)

    objects = MessageManager()

    def __unicode__(self):
        return self.sender.username

def inbox_count_for(user):
    """
    returns the number of unread messages for the given user but does not
    mark them seen
    """
    return Message.objects.filter(recipient=user, read_at__isnull=True).count()

The view

#views.py
class Screening(generic.DetailView):
    model = Job
    template_name = 'dashboard/screening.html'

    def get_context_data(self, **kwargs):
        context = super(Screening, self).get_context_data(**kwargs)
        context['candidate_list'] = self.object.applied_to.all().order_by('candidate')
        context['messages_received'] = Message.objects.inbox_for(self.request.user)
        return context

The template

#template.html

{% for candidate in candidate_list %}

    {{ candidate.get_full_name }}
    {{ candidate.candidate.city }}

    {% for message in messages_received %}
        {% if candidate.candidate.user == message.sender %}
            {{ message }}
        {% endif %}
    {% endfor %}

{% endfor %}

the templatetag

#inbox.py
from django.template import Library, Node, TemplateSyntaxError

class InboxOutput(Node):
    def __init__(self, varname=None):
        self.varname = varname

    def render(self, context):
        try:
            user = context['user']
            count = user.received_messages.filter(read_at__isnull=True).count()
        except(KeyError, AttributeError):
            count = ''
        if self.varname is not None:
            context[self.varname] = count
            return ""
        else:
            return "%s" % (count)

 def do_print_inbox_count(parser, token):
     """
    A templatetag to show the unread-count for a logged in user.
    Returns the number of unread messages in the user's inbox.
    Usage::

        {% load inbox %}
        {% inbox_count %}

        {# or assign the value to a variable: #}

        {% inbox_count as my_var %}
        {{ my_var }}

    """
    bits = token.contents.split()
    if len(bits) > 1:
        if len(bits) != 3:
            raise TemplateSyntaxError("inbox_count tag takes either no arguments or exactly two arguments")
        if bits[1] != 'as':
            raise TemplateSyntaxError("first argument to inbox_count tag must be 'as'")
        return InboxOutput(bits[2])
    else:
        return InboxOutput()

 register = Library()
 register.tag('inbox_count', do_print_inbox_count)

Solution

  • You can't perform complex logic like you want in the template language. I like that fact because it helps prevent you from introducing business logic into the template (where it doesn't belong).

    You should be able to accomplish what you want with the following:

    from django.db.models import Count
    class Screening(generic.DetailView):
        model = Job
        template_name = 'dashboard/screening.html'
    
        def get_context_data(self, **kwargs):
            context = super(Screening, self).get_context_data(**kwargs)
    
            # Fetch the sender_id for each unread message.
            # Count the number of times that the sender_id was included.
            # Then convert the list of tuples into a dictionary for quick lookup.
            sent_messages = dict(self.request.user.received_messages.filter(
                read_at__isnull=True
            ).values('sender_id').annotate(
                messages_sent=Count('user_id')
            ).values_list('sender_id', 'messages_sent'))
    
            candidates = []
            for candidate in self.object.applied_to.all().order_by('candidate'):
                candidate.messages_sent = sent_messages.get(candidate.id, 0)
                candidates.append(candidate)
            context['candidate_list'] = candidates
            return context
    

    Then in your template you'd use:

    {% for candidate in candidate_list %}
    
        {{ candidate.get_full_name }}
        {{ candidate.candidate.city }}
    
        {{ candidate.messages_sent }}
    
    {% endfor %}
    

    The downside to this approach is that you wouldn't have access to the individual messages.

    Edit: You can read about annotations and aggregations here. There's a lot more information there.