pythonhtmldjangocachingdjango-cache

Problem with deleting cache in localized page


I have pages to view posts. Also I have header with option to switch language and when I switch language, it changes on all pages, except pages with cache. There language changes only when I reboot the server. I need to understand, how I can delete cache, when I change language. Views.py:

class DetailPostView(generic.DetailView):

def get(self, request, *args, **kwargs):
    pk = kwargs['pk']

    post = Post.objects.get(pk=pk)
    comments = Comment.objects.filter(post=post)
    form = CommentForm()

    context = {
        "post": post,
        'comments': comments,
        'form': form,
    }
    return render(request, "detail_post.html", context)

def post(self, request, pk):

    post = Post.objects.get(pk=pk)
    comments = Comment.objects.filter(post=post)
    form = CommentForm(request.POST)

    if form.is_valid():
        author = self.check_user_authenticated(request, form)

        comment = Comment(
            author=author,
            body=form.cleaned_data['body'],
            image=form.cleaned_data['image'] if 'image' in form.cleaned_data else None,
            post=post,
            user=request.user if isinstance(request.user, User) else None
        )
        comment.save()

    context = {'post': post, 'comments': comments, 'form': form}
    return render(request, "detail_post.html", context)

def check_user_authenticated(self, request, form):
    if request.user.is_authenticated:
        author = request.user.profile.name
    elif form.cleaned_data['author']:
        author = form.cleaned_data['author']
    else:
        author = 'Anonim'
    return author

Models.py:

class Post(models.Model):
    title = models.CharField(max_length=25)
    body = models.TextField()
    image = models.ImageField(blank=True, upload_to=post_directory_path)
    created_on = models.DateTimeField(auto_now_add=True)
    last_modified = models.DateTimeField(auto_now=True)
    categories = models.ManyToManyField('Category', related_name='posts', blank=True)
    profile = models.ForeignKey('Profile', verbose_name='User',
                                on_delete=models.CASCADE,
                                related_name='profile')

    def __str__(self):
        return self.title


@receiver(post_save, sender=Post, dispatch_uid="clear_cache_post")
def update_post(sender, **kwargs):
    key = make_template_fragment_key('post', [kwargs['instance'].user.id])
    cache.delete(key)

Templates(detail_post.html):

{% extends "base.html" %}
{% load i18n %}
{% load cache %}
{% block page_content %}
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{% trans "Detail blog" %}</title>
    </head>
    {% cache 500 post request.user.id %}
        <div class="col-md-8 offset-md-2">
            <h1>{{ post.title }}</h1>
            <small>
                {{ post.created_on|date:"d.m.Y H:i:s" }} |&nbsp;
                {% if post.categories.all %}
                    {% trans 'Categories' %} :&nbsp;
                    {% for category in post.categories.all %}
                        <a href="{% url 'list_category' category.name %}">{{ category.name }}</a>&nbsp;
                    {% endfor %}
                {% else %}
                    {% trans "There isn't any category" %}
                {% endif %}
            </small>
            <p>{{ post.body | linebreaks }}</p>
            {% if post.image %}
                <img src="{{ MEDIA_URL }}{{ post.image.url }}" alt=""
                     width="800px" height="800px">
            {% endif %}
            {% if request.user.id == post.profile.id %}
                <form action="/blog/edit/post/{{ post.id }}" method="get">
                    <button class="btn btn-primary" type="submit" data-toggle="collapse"
                            data-target="#collapseExample"
                            aria-expanded="false" aria-controls="collapseExample">{% trans "Edit post" %}
                    </button>
                </form>
            {% endif %}
            <h3>{% trans "Leave a comment" %}: </h3>
            <form action="/blog/{{ post.pk }}/" method="post">
                {% csrf_token %}
                {% if not request.user.is_authenticated %}
                    <div class="form-group">
                        {{ form.author }}
                    </div>
                {% endif %}
                <div class="form-group">
                    {{ form.body }}
                </div>
                <div class="form-group">
                    {% trans "image" %}: {{ form.image }}
                </div>
                <button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
            </form>
            {% if comments %}
                <h3>{% trans "Comments" %}:</h3>
                {% for comment in comments %}
                    {% if comment.user.profile.avatar %}
                        </div>
                        <img class="col-md-2" src="{{ MEDIA_URL }}{{ comment.user.profile.avatar.url }}" alt=""
                             width="100px" height="100px">&nbsp;&nbsp;
                        <b>{{ comment.author }}</b> {% trans "wrote at" %} {{ comment.created_on|date:"H:i:s d.m.Y" }}
                        <div class="col-md-8 offset-md-2">
                    {% else %}
                        <b>{{ comment.author }}</b> {% trans "wrote at" %} {{ comment.created_on|date:"H:i:s d.m.Y" }}
                    {% endif %}
                    <p>{{ comment.body }}</p>
                    {% if comment.image %}
                        <img src="{{ MEDIA_URL }}{{ comment.image.url }}" alt="" width="30%" height="30%">
                    {% endif %}
                    {% if request.user.profile %}
                        <form action="/blog/edit/comment/{{ comment.id }}" method="get">
                            <button class="btn btn-primary" type="submit" data-toggle="collapse"
                                    data-target="#collapseExample"
                                    aria-expanded="false" aria-controls="collapseExample">{% trans "Edit commentary" %}
                            </button>
                        </form>
                    {% endif %}
                    <hr>
                {% endfor %}
            {% else %}
                <h3>{% trans "There isn't any comment yet" %}</h3>
            {% endif %}
            </div>
    {% endcache %}
{% endblock %}

Templates(base.html):

{% load i18n %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
      integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container">
        <a class="navbar-brand" href="https://github.com/Gleb-bit/">{% trans 'GitHub' %}</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="{% url 'list_project' %}">{% trans 'Home' %}</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{% url 'list_post' %}">{% trans 'Blog' %}</a>
                </li>
            </ul>
            <ul class="navbar-nav nav-tabs">
                <li class="nav-item mt-2">
                    <form action="{% url 'set_language' %}" method="post">
                        {% csrf_token %}
                        <input name="text" type="hidden" value="{{ redirect_to }}">
                        <select name="language">
                            {% get_current_language as LANGUAGE_CODE %}
                            {% get_available_languages as LANGUAGES %}
                            {% get_language_info_list for LANGUAGES as languages %}
                            {% for language in languages %}
                                <option value="{{ language.code }}" {% if language.code == LANGUAGE_CODE %}
                                        selected {% endif %}>
                                    {{ language.name_local }} ({{ language.code }})
                                </option>
                            {% endfor %}
                        </select>
                        <button class="btn-primary" type="submit">{% trans 'Go' %}</button>
                    </form>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false" href="#">{% trans 'More' %}</a>
                    <div class="dropdown-menu">
                        {% if request.user.is_authenticated %}
                            <a class="dropdown-item"
                               href="/blog/user/{{ request.user.id }}">{% trans 'Detail account' %}</a>
                            <a class="dropdown-item" href="{% url 'logout' %}">{% trans 'Logout' %}</a>
                        {% else %}
                            <a class="dropdown-item" href="{% url 'login' %}">{% trans 'Login' %}</a>
                        {% endif %}
                    </div>
                </li>
            </ul>
        </div>
    </div>

</nav>

<div class="container">
    {% block page_content %}{% endblock %}
</div>

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
        integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
        crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
        integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
        crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
        integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
        crossorigin="anonymous"></script>

form for changing languages:

<form action="{% url 'set_language' %}" method="post">
                    {% csrf_token %}
                    <input name="text" type="hidden" value="{{ redirect_to }}">
                    <select name="language">
                        {% get_current_language as LANGUAGE_CODE %}
                        {% get_available_languages as LANGUAGES %}
                        {% get_language_info_list for LANGUAGES as languages %}
                        {% for language in languages %}
                            <option value="{{ language.code }}" {% if language.code == LANGUAGE_CODE %}
                                    selected {% endif %}>
                                {{ language.name_local }} ({{ language.code }})
                            </option>
                        {% endfor %}
                    </select>
                    <button class="btn-primary" type="submit">{% trans 'Go' %}</button>
                </form>

Solution

  • According to the documentation on Template fragment caching for caches using the cache template tag you can pass the current language to the template tag as an argument so that it will vary with the language, so you can write the follows:

    {% get_current_language as LANGUAGE_CODE %}
    
    {% cache 500 post request.user.id LANGUAGE_CODE %}
        <div class="col-md-8 offset-md-2">
            ...
        </div>
    {% endcache %}
    

    For views that you have cached you can use the vary_on_headers decorator as described in the section Using Vary headers of the documentation, to vary on the Accept-Language header:

    from django.views.decorators.cache import cache_page
    from django.views.decorators.vary import vary_on_headers
    
    
    @cache_page(500)
    @vary_on_headers('Accept-Language')
    def some_view(request):
        ...