首页 > 解决方案 > 在 Django/Wagtail 中创建博客文章的喜欢/不喜欢按钮

问题描述

我是 Django 和 Wagtail 的新手,并且一直在寻找一种方法来使用 Wagtail 在博客条目页面上实现“简单”的喜欢/不喜欢按钮。

我在我的页面模型中包含了一个 total_likes IntegerField,当用户单击 html 模板上的按钮时,我希望在数据库中增加或减少这个 int。

用户不应该登录。我发现的大多数教程只针对注册用户处理这个问题,这不是我想要的。

如果有人能指出我正确的方向,我会很高兴。models.py 代码如下。

我不明白如何从模板中调用函数。

    class BlogEntry(Page):
        date = models.DateField("Post date")
        intro = models.CharField(max_length=250, blank=False)
        body = RichTextField(blank=True)
        tags = ClusterTaggableManager(through=BlogEntryTag, blank=True)
        categories = ParentalManyToManyField('blog.BlogCategory', blank=False)
        total_likes = models.IntegerField(blank=False, default=0)
        image = models.ForeignKey(
            "wagtailimages.Image",
            null=True,
            blank=True,
            on_delete=models.SET_NULL,
            related_name="+"
        )
        streamBody = StreamField([
            ("text", blocks.StaticContentBlock()),
            ("quotes", blocks.QuoteBlock()),
            ("image_and_text", blocks.ImageTextBlock()),
            ("related_articles", blocks.RelatedArticlesBlock()),
            ], null=True, blank=True)
    
        sidebarBody = StreamField([
            ("text", blocks.StaticContentBlock()),
            ("quotes", blocks.QuoteBlock()),
            ("image_and_text", blocks.ImageTextBlock()),
            ("related_articles", blocks.RelatedArticlesBlock()),
            ], null=True, blank=True)
    
    
        search_fields = Page.search_fields + [
            index.SearchField('intro'),
            index.SearchField('body'),
        ]
    
        content_panels = Page.content_panels + [
            MultiFieldPanel([
                ImageChooserPanel("image"),
                FieldPanel('date'),
                FieldPanel('tags'),
                FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
            ], heading="Blog information"),
            FieldPanel('intro'),
            StreamFieldPanel("streamBody"),
        ]
    
    
        sidebar_panels = [
            MultiFieldPanel([
                FieldPanel("sidebarBody"),
                ], heading="Sidebar Content")
            ]
    
        edit_handler = TabbedInterface(
            [
                ObjectList(content_panels, heading="Custom"),
                ObjectList(Page.promote_panels, heading="Promote"),
                ObjectList(Page.settings_panels, heading="Settings"),
                ObjectList(sidebar_panels, heading="Sidebar"),
            ]
        )
    
    
        def __str__(self):
            return self.total_likes
    
        def likePost(self):
            self.total_likes += 1
    
        def dislikePost(self):
            self.total_likes -= 1

标签: htmldjangowagtail

解决方案


Overview

Welcome to Django and Wagtail, there is a lot to learn but hopefully you are finding it fun. The first thing to wrap your head around is how a website (browser / client) can talk to the server (Python code running Django/Wagtail). Even though your Page model has a likePost method, you will need to provide a way for your Page to handle this kind of request.

The web uses a system of HTTP requests, the most common being GET and POST, where GET is used to pull down data to show to the user, POST is used for when the website wants to send something back to the server.

Django's docs on working with forms may be a good place to start to understand this process a bit more. Once your website has a form (in the HTML template), you can provide a way to 'listen' to this form when submitted. Wagtail has a method that exists on all Page models that is called serve and it allows you to override the normal behaviour.

Solution

In the solution below you will need to do the following:

1. Add two forms to your template (e.g. blog_page.html)

  • Remember to load the tags wagtailcore_tags so that you can access the page's URL in the form.
  • For simplicity, two forms have been created, one with a button for like and another with a button for dislike.
  • Both forms will use the method="POST" and the action (this is the URL to POST to) being the current URL.
  • Each form contains a csrf_token, you can read more about this in the Django docs but this helps avoid some security issues.
  • Each form contains a html input that is hidden with a name and value, we will use the name only in the server code to determine what button has been clicked.
{% extends "base.html" %}
{% load wagtailcore_tags %}
... BLOG CONTENT
    <form action="{% pageurl page %}" method="POST" role="form">
        {% csrf_token %}
        <input type="hidden" name="like" value="true">
        <input type="submit" value="LIKE">
    </form>
    <form action="{% pageurl page %}" method="POST" role="form">
        {% csrf_token %}
        <input type="hidden" name="dislike" value="true">
        <input type="submit" value="DISLIKE">
    </form>

2. Override the serve method in your Page model

The serve method on the Page model takes the argument request, which is the Django request object and should return a response. Thankfully we do not have to think about how this response is built, just know that we must call the super (original) version of this after any logic.

  • Check if the current request is a POST request and if so, then check what kind of value has been submitted, depending on the value call the Page method that matches
  • Update the likePost and dislikePost methods to ensure that the model data gets Saved via self.save()
    class BlogEntry(Page):
    # ...

    def likePost(self):
        self.total_likes += 1
        self.save() ## note: you may need to ensure you call save to update data

    def dislikePost(self):
        self.total_likes -= 1
        self.save() ## note: you may need to ensure you call save to update data

    def serve(self, request):
        if request.method == 'POST':
            if request.POST.get('like'):
                self.likePost()
                # a form POST has been submitted with a value for 'like'
            if request.POST.get('dislike'):
                # a form POST has been submitted with a value for 'dislike'
                self.dislikePost()
        
        # ensure we call the super's serve method so that the page loads correctly
        return super(BlogPage, self).serve(request)


Good to know

  • As you have noted, this is a basic request that does not require authentication (sign in) to submit a like, however it is very possible you will get a lot of spam this way and you may want to consider other approaches (even third party systems) to work around this.
  • This way of storing likes (as an integer) will also not give you much data, just a number at the current point in time, it might be worth tracking individual submissions and then providing a tally to the UI.
  • Here is a great overview of HTTP from MDN that I have found to be a good reference for understanding how a server handles requests & responses.

推荐阅读