Django: Как написать JavaScript fetch для url с параметром slug?

Новичок в django и async, пытаюсь улучшить простое приложение доски объявлений. Я уверен, что вы все видели эту проблему десятки раз, но я не могу найти решение...

В настоящее время, когда пользователю нравится опубликованное сообщение, обновляется вся страница. Я хотел бы использовать простой JavaScript с fetch API, чтобы предотвратить это, не прибегая к Ajax, поскольку я никогда его не использовал. Проблема в том, что я очень новичок в методе fetch, и мне трудно найти правильный синтаксис для url в запросе fetch, поскольку он использует поле slug модели поста в качестве параметра. Например, так:

urls.py

urlpatterns = [
    ...
    path('post/<slug:slug>/', views.FullPost.as_view(), name='boards_post'), 
    path('like/<slug:slug>/', views.PostLike.as_view(), name='post_like'),
    ...
]

models.py

...
class Post(models.Model):
    """
    Model for message posts
    """

    STATUS = ((0, "Draft"), (1, "Published"))

    title = models.CharField(max_length=200, unique=True)
    slug = models.SlugField(max_length=200, unique=True)
    author = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="board_posts"
    )
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        default="",
        related_name="category_posts"
    )
    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)
    content = models.TextField()
    post_image = CloudinaryField('image', default='placeholder')
    status = models.IntegerField(choices=STATUS, default=0)
    likes = models.ManyToManyField(User, related_name="post_likes")

    class Meta:
        # Orders posts in descending order
        ordering = ['-created_on']

    def __str__(self):
        return self.title

    def number_of_likes(self):
        return self.likes.count()

    def get_absolute_url(self):
        """
        Method to tell django how to find the url to any specific
        instance of a post when the Post model is instantiated,
        (i.e. a new post created). Returns the url generated by the
        'boards_post' path from the FullPost class view, with
        this model's slug field as a keyword argument. This
        effectively acts as a redirect to the full_post.html template.
        """

        return reverse('boards_post', kwargs={'slug': self.slug})
...

views.py

...
class FullPost(View):
    """
    View for a single post, selected by the user, displaying
    comments and likes. The url for each individual post is derived
    from the Post model's slug field which is, in turn,
    populated by the title.
    """

    def get(self, request, slug, *args, **kwargs):
        """
        Method to get post object.
        """

        queryset = Post.objects.filter(status=1)
        post = get_object_or_404(queryset, slug=slug)
        comments = post.comments.order_by('created_on')
        liked = False
        if post.likes.filter(id=self.request.user.id).exists():
            liked = True

        return render(
            request,
            "full_post.html",
            {
                "post": post,
                "comments": comments,
                "liked": liked,
                "comment_form": CommentForm() 
            },
        )

    def post(self, request, slug, *args, **kwargs):
        """
        Post method for comment form.
        """

        queryset = Post.objects.filter(status=1)
        post = get_object_or_404(queryset, slug=slug)
        comments = post.comments.order_by("-created_on")
        liked = False
        if post.likes.filter(id=self.request.user.id).exists():
            liked = True

        comment_form = CommentForm(data=request.POST)
        if comment_form.is_valid():
            comment_form.instance.name = self.request.user
            comment = comment_form.save(commit=False)
            comment.post = post
            comment.save()
        else:
            comment_form = CommentForm()

        return redirect(self.request.path_info)
...
...
class PostLike(View):
    """
    View for liking and unliking posts.
    """

    def post(self, request, slug):
        """
        Method to toggle liked/unliked state on a particular post.
        """

        post = get_object_or_404(Post, slug=slug)

        liked = True
        if post.likes.filter(id=self.request.user.id).exists():
            post.likes.remove(request.user)
            liked = False
        else:
            post.likes.add(request.user)

        likes = post.number_of_likes()

        # ----- The original return statement is the one commented out below:
        # return HttpResponseRedirect(reverse('boards_post', args=[slug]))
        return JsonResponse({"likes": likes, "liked": liked})
...

Отрывок из шаблона полного поста

...
<div class="row">
    <div class="col-1">
    {% if user.is_authenticated %}
        <strong>
            <form class="d-inline" action="{% url 'post_like' post.slug %}" method="POST">
            {% csrf_token %}
            {% if liked %}
<!-- I used 'post.id' for the argument passed to the like function here, as I couldn't get 'post.slug' to work -->
                <button class="btn-like" type="submit" name="post_id" value="{{ post.slug }}" onclick="like({{ post.id }})">
                    <i id="like-btn" class="fas fa-thumbs-up"></i>
                </button>
                <span id="likes-count">{{ post.number_of_likes }}</span>
            {% else %}
                <button class="btn-like" type="submit" name="post_id" value="{{ post.slug }}" onclick="like({{ post.id }})">
                    <i id="like-btn" class="far fa-thumbs-up"></i>
                </button>
                <span id="likes-count">{{ post.number_of_likes }}</span>
            {% endif %}
            </form>
        </strong>
    {% else %}
        <strong class="text-secondary"><i class="far fa-thumbs-up"></i> <span id="likes-count">{{ post.number_of_likes }}</span></strong>
    {% endif %}
</div>
...

Все, что у меня пока есть в JavaScript, это следующее...

function like(post_id) {
    let likeButton = document.getElementById("like-btn");
    let likeCount = document.getElementById("likes-count");
    
    console.log(likeCount.innerText);
    console.log(post_id);
    
    // -------- I've no idea what works here! I've tried both of the below
    // -------- and several variations.
    // -------- Obviously, none work.
    //   const url = "{%  url 'post_like' post.slug %}";
    //or const url = `/like/${post.slug}`;

    fetch(url, {method: "POST"})
        .then(response => response.json())
        .then(data => {
            console.log(data);
            // ------ If I ever get the json data, I'll use it here to manipulate the
            // ------ likeButton and likeCount variables in the DOM. Something like:
            likeCount.innerHTML = data["likes"];
            if (data["liked"] === True) {
                likeButton.className = "fas fa-thumbs-up";
            } else {
                likeButton.className = "far fa-thumbs-up";
            }
        })
        .catch((e) => alert("Unable to like/unlike post."));
        // -------- or something.
}

Кроме того, я знаю, что мне нужно как-то обрабатывать токен csrf, но я понятия не имею как. Как я уже сказал, я полный новичок. Итак, может ли кто-нибудь помочь? И есть ли у кого-нибудь совет? Ваше здоровье!

Для csrf_token проблемы добавьте к просмотру этот декоратор:

from django.utils.decorators import method_decorator

@method_decorator(csrf_exempt, name='dispatch')
class PostLike(View):
    ...

Для выборки вам нужно иметь полный путь, а не только относительный. Это должно работать:

url = "{{ request.scheme }}://{{ request.get_host }}{% url 'post_like' post.slug %}"
Вернуться на верх