Django Builds Personal Blog: A Praise Function Based on Local Storage

Suppose your blog has been successfully deployed online. You wrote a lot of good articles, interacted with fans and felt a sense of accomplishment.

Now you want to go a step further and strive to improve the quality of articles, make them more popular with readers, and create first-class blogs in the circle. The question is how to judge an article as "popular"? Visit volume is a method, but it can't distinguish the titled Party of the content. It's also a good way to count comments, but personal blogs usually don't have many readers. It's normal for good articles to have zero comments.

At this time, the "point praise" function becomes important. If most readers give a compliment, it shows that the article is really good.

Thoughts Before Starting

Praise function is not simple, there are many ways to achieve. Don't rush to get started, think patiently: What kind of compliments do we need for our blog?

First of all, do you require users to log in? The advantage of requiring login is that it can accurately record who users are and which articles are overpraised (many-to-many relationship) for detailed data analysis. The disadvantage is that the login requirement is cumbersome and will block most of the visitors. Bloggers tend not to require users to log in. After all, small stations usually have few users. Improving participation is the core task.

If one day your small station is on fire, give the interactive function of requiring users to log in to "Collection"!

Secondly, can users repeat their praises? Many users of video platforms can madly praise a favorite female anchor to express their very, very like. This is no problem for platforms with more users, because when the number of users is large, you can only click on a few hundred praises. However, blog sites do this easily lead to zero praise for some articles, and surprisingly high praise for some articles. Obviously, this does not represent the difference in the quality of the article.

Well, our current strategy is not to require users to log in, nor to allow users to repeat their comments. The next question is, where do you record the user's approval-related data? There is no doubt that the number of comments should be kept in the database so that the data can be retrieved and presented at any time.

But the question is, is it possible to verify where the user's favorite records are saved? It's a way to record users'IP addresses in the database, but you have to deal with the relationship between recording IP and recording logged-in users, which is a little troublesome. In addition, each user's approval needs to send a verification request to the back end, which increases the burden of the server.

Since the data is not well stored in the back-end database, can it be saved in the browser? The answer is yes, and both Cookie and Local Storage allow you to save data. The main differences between them are as follows:

Characteristic Cookie LocalStorage
life cycle Failure time can be set. The default is to fail after browser is closed. Permanent preservation unless removed
storage space About 4K Usually 5MB
Communicate with the server Every time it's carried in the HTTP header Not participating in server communication
Easy to use Source interface is not friendly Source-generated interfaces are acceptable

Comparatively, it will be found that Local Storage can store data permanently, has large storage space, and does not participate in server communication, which is very suitable for the demand of praise. Since the data is stored in the browser, there is no need to distinguish whether the user is logged in or not: in fact, every time a request for approval is made, the check is whether the current browser has already clicked on approval, not the user!

Maybe you will retort that if the user changes to a browser, he can not repeat the praise, let alone the browser-side data is very easy to tamper with. But what does that matter? Praise data doesn't need to be very accurate. Let him do it.

All modern browsers support the LocalStorage function. If you're still using IE6, consider upgrading your browser.

To sum up, our complimentary functions are as follows:

  • Users are not required to log in
  • No repetition of praise
  • Point approval is saved in server database
  • Click-on validation data is stored in the browser's Local Storage

When the user clicks on the praise, the front-end script checks whether the praise has been clicked in the Local Storage; if the praise has not been clicked, it will send the praise request to the server and record the data.

Think clearly about the need, and the problem will be solved. Next is the code implementation.

It needs to be pointed out that the above analysis does not mean that other methods are not good, but only in the blog site environment, bloggers feel that the appropriate technical path. If you have another Hamlet in your heart, find a way to achieve it.

code implementation

Preparation

The focus of this chapter is on the front-end, so first write a simple back-end code, right when warming up.

Some readers feel headache when they hear the front end. I understand your pain, but it's also essential. Python alone can't make beautiful websites.

Since the point preferences need to be saved in the database, it is necessary to modify the article model:

article/models.py

...
# Articles Model
class ArticlePost(models.Model):
    ...
    # Added Point Praise Statistics
    likes = models.PositiveIntegerField(default=0)
    ...

Migrate data:

(env) > python manage.py makemigrations
(env) > python manage.py migrate

Continue with the class view:

article/views.py

...
# Point Ratio + 1
class IncreaseLikesView(View):
    def post(self, request, *args, **kwargs):
        article = ArticlePost.objects.get(id=kwargs.get('id'))
        article.likes += 1
        article.save()
        return HttpResponse('success')

The function is to increase the number of points by 1 and return success. As for why success comes later.

Finally, routing:

article/urls.py

...
urlpatterns = [
    ...
    # Praise + 1
    path(
        'increase-likes/<int:id>/', 
        views.IncreaseLikesView.as_view(), 
        name='increase_likes'
    ),
]

It's very simple. All that's left is to concentrate on writing the front-end code.

JS and Ajax

Because the validation data is stored in the browser, the front-end work is more.

First paste out the complete code (explained later):

templates/article/detail.html

...

<!-- Existing code, text of article -->
<div class="col-12">
    <p>{{ article.body|safe }}</p>
</div>

<!-- Added a Praise Button -->
<div style="text-align:center;" class="mt-4">
    <button class="btn btn-outline-danger"
            type="button"
            onclick="validate_is_like(
                     '{% url 'article:increase_likes' article.id %}',
                     {{ article.id }},
                     {{ article.likes }}
                     )"
            >
        <span>Give the thumbs-up</span>
        <span>
            <i class="fas fa-heart"></i>
        </span>
        <span id="likes_number">
            {{ article.likes }}
        </span>
    </button>
</div>

...
{% block script %}
...

<!-- The following are all new codes -->

<!-- csrf token -->
<script src="{% static 'csrf.js' %}"></script>
<script>
    // Principal Function of Point Praise Function
    function validate_is_like(url, id, likes) {
        // Remove data from Local Storage
        let storage = window.localStorage;
        const storage_str_data = storage.getItem("my_blog_data");
        let storage_json_data = JSON.parse(storage_str_data);
        // If the data does not exist, create an empty dictionary
        if (!storage_json_data) {
            storage_json_data = {}
        };
        // Check if the current article has been praised. Yes, status = true
        const status = check_status(storage_json_data, id);
        if (status) {
            layer.msg('Have already ordered praise yo~');
            // Quit the function immediately if you overpraise it
            return;
        } else {
            // Use Jquery to find the number of compliments and + 1
            $('span#likes_number').text(likes + 1).css('color', '#dc3545');
        }
        // Send a post request to the back end with ajax
        $.post(
            url,
            // post is just for csrf checking, so the data is empty
            {},
            function(result) {
                if (result === 'success') {
                    // Trying to modify some praise data
                    try {
                        storage_json_data[id] = true;
                    } catch (e) {
                        window.localStorage.clear();
                    };
                    // Convert the dictionary to a string for storage in the Local Storage
                    const d = JSON.stringify(storage_json_data);
                    // Attempt to store Point Praise data to Local Storage
                    try {
                        storage.setItem("my_blog_data", d);
                    } catch (e) {
                        // Cod22 error indicates that the LocalStorage space is full
                        if (e.code === 22) {
                            window.localStorage.clear();
                            storage.setItem("my_blog_data", d);
                        }
                    };
                } else {
                    layer.msg("Failure to communicate with server..Try again later.~");
                }

            }
        );
    };

    // Auxiliary Point Praise Principal Function, Verify Point Praise State
    function check_status(data, id) {
        // Trying to query the status of a comment
        try {
            if (id in data && data[id]) {
                return true;
            } else {
                return false;
            }
        } catch (e) {
            window.localStorage.clear();
            return false;
        };
    };
</script>
{% endblock script %}

There's a lot of code, so I'll break it down.

<!-- Added a little compliment code -->
<div style="text-align:center;" class="mt-4">
    <button class="btn btn-outline-danger"
            type="button"
            onclick="validate_is_like(
                     '{% url 'article:increase_likes' article.id %}',
                     {{ article.id }},
                     {{ article.likes }}
                     )"
            >
        <span>Give the thumbs-up</span>
        <span>
            <i class="fas fa-heart"></i>
        </span>
        <span id="likes_number">
            {{ article.likes }}
        </span>
    </button>
</div>

The HTML code above is simple, providing a little complimentary button that triggers a JavaScript function called validate_is_like when clicked. It is particularly important to note that'{% URL'article: increase_likes'article. ID%}' are all single quotation marks, so double quotation marks should not be used here. Readers should think about the reasons.

<script src="{% static 'csrf.js' %}"></script>

Remember csrf.js Do you? We've introduced it in the Multilevel Comments section to enable Ajax to pass csrf validation as well. If you do not have this file, please click on the link to download it.

Next is the function validate_is_like(), which occupies the most space. Let's split the contents.

// Remove data from Local Storage
let storage = window.localStorage;
const storage_str_data = storage.getItem("my_blog_data");
let storage_json_data = JSON.parse(storage_str_data);
// If the data does not exist, create an empty dictionary
if (!storage_json_data) {
    storage_json_data = {}
};

In the browser, the window object refers to the current browser window. It is also the top-level object of the current page (that is, the top-level object), all other objects are its subordinates, and so is the localStorage.

To verify the data, the data must be taken out first. Here the data is fetched using the localStorage.getItem() interface.

Although LocalStorage is stored in a standard key-value pair type (similar to Python's dictionary), it's odd that the stored values only support string types. So here we use JSON.parse() to restore the string as an object.

The first time a user praises, there must be no data in the Local Storage, so the function of the if statement is to create an empty dictionary to be used.

// Check if the current article has been praised. Yes, status = true
const status = check_status(storage_json_data, id);
if (status) {
    layer.msg('Have already ordered praise yo~');
    // Quit the function immediately if you overpraise it
    return;
} else {
    // Use Jquery to find the number of compliments and + 1
    $('span#likes_number').text(likes + 1).css('color', '#dc3545');
}

Next, call the function check_status immediately to check if the user has overpraised this article. If it's clicked, pop up the window prompt and terminate the validate_is_like function with return immediately, then the following code will not execute; if it hasn't been clicked, let the button's point approval + 1.

But at this time, in fact, the background database has not been updated. Then look down.

// Send a post request to the back end with ajax
$.post(
    url,
    // post is just for csrf checking, so the data is empty
    {},
    function(result) {
        if (result === 'success') {
            // Trying to modify some praise data
            try {
                storage_json_data[id] = true;
            } catch (e) {
                window.localStorage.clear();
            };

            const d = JSON.stringify(storage_json_data);
            // Attempt to store Point Praise data to Local Storage
            try {
                storage.setItem("my_blog_data", d);
            } catch (e) {
                // Cod22 error indicates that the LocalStorage space is full
                if (e.code === 22) {
                    window.localStorage.clear();
                    storage.setItem("my_blog_data", d);
                }
            };
        } else {
            layer.msg("Failure to communicate with server..Try again later.~");
        }

    }
);

Here we start trying to communicate with the back end and update the point preferences. The whole block of code is wrapped in $. post(), which is actually Ajax's post request. function(result) {...} is a callback function that is executed when the request succeeds, and the parameter result is the return value of the back end. If the communication succeeds, try to save the checksum data in the Local Storage. Any errors that occur during this period (especially when the Local Storage is full) will clear all data in the Local Storage for subsequent data records.

As can be seen, the data structure adopted by bloggers is relatively simple, such as:

{
    2: true,
    31: true
    ...
}

The key represents the id of the article, and the Boolean value represents the status of the point praise. The above data means that articles with IDS 2 and 31 have been overrated. Readers may want articles, reviews and other content to be complimentary in the future, which requires more complex data structures.

// Auxiliary Point Praise Principal Function, Verify Point Praise State
function check_status(data, id) {
    // Trying to query the status of a comment
    try {
        if (id in data && data[id]) {
            return true;
        } else {
            return false;
        }
    } catch (e) {
        window.localStorage.clear();
        return false;
    };
};

As for the check_status() function, it's very simple. The function is to query whether it has been clicked, to return true, or to return false.

The entire JavaScript script is complete.

Debugging interface

Readers may have various problems in debugging. Press Ctrl + Shift + I to open the Console interface of the browser console. Use the following command debug:

  • Local Storage: View Local Storage data
  • localStorage.clear(): Clear all data
  • localStorage.getItem(): Get some data
  • localStorage.setItem(): Save a data

test

After the code is finished, I will open the article details page to test it.

Click on the Praise button and click on the Praise + 1; click again on the Praise button, the Praise will not increase, and the pop-up window will remind the user that the Praise has been clicked.

You can try to close the page or browser at will, and the saved checksum data will not disappear.

This completes a simple compliment function.

Of course, we can continue to optimize:

  • Love without praise should be shown in grey and red, so that it can be humanized.
  • It's better to have a little more cool compliment animation, or prompting text.
  • Would you like to send a notice to the recipient?
  • ...

The length of the course is limited. If you don't want to go any further, you should do your homework as a reader's friend. You should finish it with your heart. Give you some compliments!

Tip 1: When loading the page initially, the love is uniformly displayed in grey. Then JavaScript script is called to compare the data in Local Storage. Jquery is used flexibly to change the color of the dotted love to red.

summary

Our blog project now has a hierarchical user interaction structure: the lightest browser data, the most popular evaluation of the type of article; the more balanced point praise data, the popularity of the evaluation of article content; the most cumbersome comment data, but also the highest value. In the future, when developing functions, readers should also think clearly about the core requirements like this.

Another point to be made is that only non-sensitive and unimportant data is stored in the Local Storage, and don't rely too much on it.

Again, for the sake of easy explanation, the code files have become larger and larger. Please divide it into smaller components at the appropriate time for easy maintenance and reuse.

Tags: Python Database JSON JQuery

Posted on Fri, 06 Sep 2019 08:07:18 -0700 by matthewlesh