Tweet Action View:

prepare a system to handle all possible actions.

  • add a list of TWEET_ACTION_OPTIONS in /Twittme/settings.py

    1. # ...
    2. TWEET_ACTION_OPTIONS = ["like", "unlike", "retweet"]
    3. # ...
  • add a action serializer in /tweets/serializers.py ```python

    TWEET_ACTION_OPTIONS = settings.TWEET_ACTION_OPTIONS

class TweetActionSerializer(serializers.Serializer): id = serializers.IntegerField() action = serializers.CharField()

  1. def validate_action(self, value):
  2. value = value.lower() # to lowercase, and remove header spaces
  3. if not value in TWEET_ACTION_OPTIONS:
  4. raise serializers.ValidationError("This is not a valid action for tweets")
  5. return value

  1. - add the action view in /tweets/views.py
  2. ```python
  3. @api_view(['POST'])
  4. @permission_classes([IsAuthenticated])
  5. def tweet_action_view(request, *args, **kwargs):
  6. # id is required
  7. # action: like, unlike, retweet
  8. serializer = TweetActionSerializer(data=request.data)
  9. if serializer.is_valid(raise_exception=True):
  10. data = serializer.validated_data
  11. tweet_id = data.get("id")
  12. action = data.get("action")
  13. qs = Tweet.objects.filter(id=tweet_id)
  14. if not qs.exists():
  15. return Response({}, status=404)
  16. obj = qs.first()
  17. if action == "like":
  18. obj.likes.add(request.user) # add the current user into the like list
  19. elif action == "unlike":
  20. obj.likes.remove(request.user) # remove the current user from the like list
  21. elif action == "retweet":
  22. # this is todo
  23. pass
  24. return Response({}, status=200)

JavaScript Tweet Action Handler:

apply the action view before.

  • add new paths in /Twittme/urls.py ```python from django.contrib import admin from django.urls import path, re_path

from tweets.views import ( home_view, tweet_detail_view, tweet_list_view, tweet_create_view, tweet_delete_view, tweet_action_view, # new )

urlpatterns = [ path(‘admin/‘, admin.site.urls), path(‘’, home_view), path(‘tweets’, tweet_list_view), path(‘create-tweet’, tweet_create_view), path(‘tweets/‘, tweet_detail_view), path(‘api/tweets//delete’, tweet_delete_view), path(‘api/tweets/action’, tweet_action_view), # new ]

  1. - update /tweets/serializers.py
  2. ```python
  3. class TweetSerializer(serializers.ModelSerializer):
  4. likes = serializers.SerializerMethodField(read_only==True) # new
  5. class Meta:
  6. model = Tweet
  7. # fields = ['content']
  8. fields = ['id', 'content', 'likes']
  9. def get_likes(self, obj): # new
  10. return obj.likes.count() # return how many likes
  11. def validate_content(self, value):
  12. if len(value) > MAX_TWEET_LENGTH:
  13. raise serializers.ValidationError("This tweet is too long")
  14. return value

to test, runserver

  1. [root@localhost Twittme]# ./manage.py runserver
  2. File "./manage.py", line 14
  3. ) from exc
  4. ^
  5. SyntaxError: invalid syntax
  6. [root@localhost Twittme]# python3 manage.py runserver

because we changed the environment, apply python3 before runserver.
image.png
now we have numbers instead of “undefined” before “likes”

  • complete handleDidLike() in home.html
    1. function handleDidLike(tweet_id, currentCount) {
    2. console.log(tweet_id, currentCount)
    3. const url = "/api/tweets/action"
    4. const method = "POST"
    5. const data = JSON.stringify({
    6. id: tweet_id,
    7. action: "like"
    8. })
    9. const xhr = new XMLHttpRequest()
    10. xhr.open(method, url)
    11. xhr.setRequestHeader("Content-Type", "application/json")
    12. xhr.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest")
    13. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
    14. xhr.onload = function() {
    15. console.log(xhr.status, xhr.response)
    16. }
    17. xhr.send(data)
    18. return
    19. }
    to test, runserver
    image.png
    got a 403 problem, because we haven’t actually save CSRF token

CSRF & Client Side Action Buttons

this section handle CSRF token and other client side action buttons.

  • CSRF stands for Cross Site Request Forgery
  • about CSRF protection in django: https://docs.djangoproject.com/en/3.0/ref/csrf/
  • again, CSRF token is to prevent Cross Site Request Forgery, we use {% csrf_token %} in templates to apply it.
  • to avoid pass CSRF token for each POST request, another method is “on each XMLHttpRequest, set a custom X-CSRFToken header (as specified by the CSRF_HEADER_NAME setting) to the value of the CSRF token.”

  • add getCookie() and CSRF token realted changes to handleDidlike() in home.html

    1. function getCookie(name) {
    2. var cookieValue = null;
    3. if (document.cookie && document.cookie !== '') {
    4. var cookies = document.cookie.split(';');
    5. for (var i = 0; i < cookies.length; i++) {
    6. var cookie = cookies[i].trim();
    7. // Does this cookie string begin with the name we want?
    8. if (cookie.substring(0, name.length + 1) === (name + '=')) {
    9. cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
    10. break;
    11. }
    12. }
    13. }
    14. return cookieValue;
    15. }
    16. //...
    17. function handleDidLike(tweet_id, currentCount) {
    18. console.log(tweet_id, currentCount)
    19. const url = "/api/tweets/action"
    20. const method = "POST"
    21. const data = JSON.stringify({
    22. id: tweet_id,
    23. action: "like"
    24. })
    25. const xhr = new XMLHttpRequest()
    26. const csrftoken = getCookie('csrftoken')
    27. xhr.open(method, url)
    28. xhr.setRequestHeader("Content-Type", "application/json")
    29. xhr.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest")
    30. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
    31. xhr.setRequestHeader("X-CSRFToken", csrftoken) //add header of CSRF token
    32. xhr.onload = function() {
    33. console.log(xhr.status, xhr.response)
    34. }
    35. xhr.send(data)
    36. return
    37. }

    to test, runserver
    this time, click likes button of “rest create view”, we see the id is 50, (number of ) likes is 0, and status code is 200(OK)
    image.png
    refresh and we have 1 likes from the root user right now, the adding is successful, but since we have no unlike right now, we cannot cancel it.
    image.png

  • return data of that liked tweet

    1. @api_view(['POST'])
    2. @permission_classes([IsAuthenticated])
    3. def tweet_action_view(request, *args, **kwargs):
    4. # ...
    5. if action == "like":
    6. obj.likes.add(request.user) # add the current user into the like list
    7. serializer = TweetSerializer(obj)
    8. return Response(serializer.data, status=200)
    9. # ...

    to test, runserver
    image.png
    now we have more detailed data.

  • Load the status change instantly by loadTweets() in home.html

    1. function handleDidLike(tweet_id, currentCount) {
    2. console.log(tweet_id, currentCount)
    3. const url = "/api/tweets/action"
    4. const method = "POST"
    5. const data = JSON.stringify({
    6. id: tweet_id,
    7. action: "like"
    8. })
    9. const xhr = new XMLHttpRequest()
    10. const csrftoken = getCookie('csrftoken')
    11. xhr.open(method, url)
    12. xhr.setRequestHeader("Content-Type", "application/json")
    13. xhr.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest")
    14. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
    15. xhr.setRequestHeader("X-CSRFToken", csrftoken)
    16. xhr.onload = function() {
    17. //console.log(xhr.status, xhr.response)
    18. //react.js
    19. loadTweets(tweetsContainerElement)
    20. }
    21. xhr.send(data)
    22. return
    23. }

    and 0->1 likes status change don’t need to refresh.

  • next is “unlike”, update home.html to separate actions ```javascript //function handleDidLike(tweet_id, currentCount) { function handleDidLike(tweet_id, currentCount, action) { //… const data = JSON.stringify({

    1. id: tweet_id,

    //action: “like”

    1. action: action

    }) //… return }

//… function LikeBtn(tweet) { return “