Search code examples
pythondjangodjango-templatesdjango-template-filters

Django Custom Template Filter - How To Mention Users And Categories


I am making a social networking site, and I want to be able to mention users or categories by using the # symbol. (Example: #User #Category #Other Category). Using a template filter, I want to first find all the # in the string, and then get the word after it. Then I would check in the models for categories:

class Category(models.Model):
    name = models.CharField(max_length=250, unique=True)
    category_detail = models.TextField(max_length=1250, default='No information is known on this category.')

And if it does not match a category I would check if the word after the # is a user. If It is a category, I want to link it using the <a> tag, and if it is a user I also want to link it using the <a> tag.

The urls:

urlpatterns = [
   path('u/@<str:username>', UserPostListView.as_view(), name='user-posts'),  # User Profile Link. Link This If After The # The Word Is A User
   path('c/<str:cats>/', views.category_view, name='category-detail'),  # View Category Link. Link This If After The # The Word Is A Category
]

Usage: Going back to the example, let's say I have a comment like this:

This is such a great post! #some_user #category #other_category

And in my template I render it like:

{{ comment.text|safe|filter_to_parse }}

I want the template filter filter_to_parse to return:

This is such a great post! <a href="/u/@some_user">#some_user</a> <a href="/c/category">#category</a> <a href="/c/other_category">#other_category</a> 

So how would I make a template filter in Django to do this?

I have found some things that would've helped me do this:

But the code from these links don't work. Thanks.

Edit 2: Ok so thanks to @flaunder, I have added mark_safe and tried to debug the filter. Here's the working code I have so far:

from django import template
from django.template.defaultfilters import stringfilter
from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
register = template.Library()


@register.filter(name='mention', is_safe=True)
@stringfilter
def mention(value):
    my_list = value.split()
    for i in my_list:
        if i[0] == '@':
            try:
                stng = i[1:]
                user = User.objects.get(username=stng)
                if user:
                    profile_link = user.userprofile.get_absolute_url()
                    i = f"<a href='{profile_link}'>{i}</a>"
                    res = res + i + ' '
                    return mark_safe(res)
            except User.DoesNotExist:
                return value
        else:
            return value

This worked and in the templates it has a link to the user profile, but the rest of the comment is missing. Example: "@da hi!" became "@da", with no hi! This is because it returns res, which is the tag and a space, so I need help returning the rest of the comment too.

The other problem with this code is that if you are mentioning multiple users like @da @user2, then only the first user becomes a url and the rest are just text.


Solution

  • this is fully untested code, but to get you going: def mention(original): listAts = list()

     for i in my_list:
        if i[0] == '@':
            try:
              stng = i[1:]
              user = User.objects.get(username = stng)
              if user:
                 profile_link = user.userprofile.get_absolute_url()
                 i = f"<a href='{profile_link}'>{i}</a>"
    
            except User.DoesNotExist:
            print("Could not get the data")