বাংলায় জ্যাঙ্গো

এই টিউটোরিয়ালটিতে জ্যাঙ্গোতে বিভিন্ন পোস্ট বা ইমেজে কিভাবে লাইক বাটন যোগ করা যায় সেই বিষয়ে জানার চেষ্টা করবো। এই কাজটি আমরা Asynchronous উপায়ে (অর্থাৎ সম্পূর্ণ পেইজটি লোড না করে শুধু মাত্র কিছু অংশকে লোড করে সার্ভার আপডেট করার মাধ্যমে) করার চেষ্টা করবো। আর এই কাজটি করার জন্য আমরা Ajax ব্যবহার করবো। Ajax সম্পর্কে জানার জন্য এই লিঙ্কটি দেখতে পারেন।

STEP-01:

প্রথমেই ধরে নিই blog নামের একটি অ্যাপের models.py তে Post নামের একটি মডেল ক্লাস রয়েছে। blog/models.py
class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE,related_name='blog_posts')
    title = models.CharField(max_length=250)     
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    
    ---------------------
    --------------------
এখন আমরা চাই প্রতিটা পোস্টে লাইক দেওয়ার ব্যবস্থা থাকবে এবং কতটি লাইক দেওয়া হয়েছে সেটাও দেখাবে সাথে লাইক দেওয়ার পর লাইক undo করার জন্য আনলাইক অপশনও থাকবে। প্রতিটা পোস্টে লাইকের হিসাব রাখার জন্য আমরা users_like নামে আরেকটা ফিল্ড Post Model Class এ যুক্ত করবো- blog/models.py
class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE,related_name='blog_posts')
    users_like = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='posts_liked',blank=True)
    title = models.CharField(max_length=250)     
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    
    ---------------------
    --------------------
users_like ফিল্ডটিতে ManyToManyField ব্যবহার করা হয়েছে কারণ একজন ইউজার একাধিক পোস্টে লাইক দিতে পারে আবার একটি পোস্টে একাধিক ইউজারের লাইক থাকবে। এর মাধ্যমে জ্যাঙ্গো দুটি মডেলেরই প্রাইমারী কি ব্যবহার করে intermediary join টেবিল তৈরি করে। ForeignKey এর মতো ManyToManyField ও many-t0-many manager প্রদান করে যার মাধ্যমে relative object এর মাধ্যমে ডাটা retrieve করা যায়, যেমন- post.users_like.all() বা user.posts_liked.all() ইত্যাদি।

STEP-02:

এখন post_details.html টেমপ্লেটের মধ্যে লাইক বাটন যোগ করা যাক – templates/post_details.html:
{% extends 'base/base.html' %}
..............................
..............................
 
{% with total_likes=post.users_like.count users_like=post.users_like.all %}
   <div class="image-info">
      <div>
         <span class="count">
            <span class="total">{{total_likes}}</span> like{{total_likes|pluralize}}
                <a href="#" data-id="{{post.id}}" data-action="{% if request.user in users_like %}un{%endif%}like" class="like btn btn-primary">
                {% if request.user not in users_like %}
                  Like 
                {% else %}
                  Unlike
                {% endif %}  
                </a> 
            </span>
       </div>
    </div>
{% endwith %}
  • প্রথমেই base.html টেমপ্লেটটি extend করা হয়েছে।
  • {%with%} ট্যাগ ব্যবহার করা হয়েছে দুটি ভ্যারিয়েবলের মধ্যে ভ্যালু স্টাের করে রাখার জন্য। total_likes ভ্যারিয়েবলটিতে মোট লাইকের সংখ্যা কুয়েরী করে রাখা হয়েছে এবং users_like ভ্যারিয়েবলটিতে লাইক দেওয়া ইউজাদের লিস্ট রাখা হয়েছে।
  • তারপর {{total_likes}} এর মাধ্যমে মোট লাইকের সংখ্যা দেখানো হয়েছে। total_likes|pluralize এর মাধ্যমে মোট লাইকের সংখ্যা একের অধিক হলে like এর শেষে s যুক্ত হবে।
  • data-id অ্যাট্রিবিউটের মধ্যে post এর id এবং data-action অ্যাট্রিবিউটের মধ্যে Like বা Unlike  স্ট্রিং স্টোর করে রাখা হয়েছে। এখানে if কন্ডিশনাল অপারেটর ব্যবহার করা হয়েছে যার মাধ্যমে চেক করা হয়েছে যে, বর্তমান ইউজার users_like লিস্টে আছে কিনা। যদি লিস্টের মধ্যে থাকে, সেক্ষেত্রে Unlike এবং যদি লিস্টের মধ্যে না থাকে তবে like স্ট্রিং স্টোর করা হবে। এই ডাটা পরবর্তীতে ajax অ্যাকশনের URL এর মাধ্যমে views ফাংশনের কাছে পাঠাবে এবং ডাটাবেস আপডেট হবে।
আমরা Ajax ফাংশনালিটি jQuery ফ্রেমওর্য়াকের মাধ্যমে সম্পন্ন করবো। এর জন্য প্রথমেই আমাদেরকে jQuery লোড করতে হবে। আমরা Google এর CDN থেকে jQuery লোড করবো ( অথবা https://jquery.com/ থেকে ডাউনলোড করে তা static ডিরেক্টরিতে যুক্ত করেও করতে পারি)। আমরা যেহেতু base.html টেমপ্লেটটি সকল টেমপ্লেটে extend করবো তাই jQuery CDN  টি আমরা base.html টেমপ্লেটের একদম নিচে </body> ট্যাগের পূর্বে যুক্ত করবো। base.html:
https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js

  $(document).ready(function(){
    {% block domready %}
    {% endblock %}
  });
  • জাভাস্ক্রিপ্ট কোড যুক্ত করার জন্য ট্যাগটি যুক্ত করা হয়েছে।
  • $(document).ready() হচ্ছে jQuery ফাংশন যা DOM (Document Object Model) কন্সট্রাক্টেড হওয়ার পর execute হয়। যখন কোন ওয়েব পেইজ লোড হয়, তখনই DOM কন্সট্রাক্ট হয়। কোন কোড এই ফাংশনের মধ্যে অন্তর্ভুক্ত করার মাধ্যমে এটা নিশ্চিত করা হয় যে,  যে HTML elements গুলো এই কোডের সাথে interact থাকবে সেগুলো load হবে।
  • যেহেতু base.html অন্য টেমপ্লেটে extend হবে, সেহেতু অন্য টেমপ্লেট থেকে এই ফাংশনের ভিতরে কোড লিখার domready নামে একটা block তৈরি করা হয়েছে। অন্য যেকোন টেমপ্লেট যেখানে এই base.html extend করা হবে, সেখানে domready block এর মধ্যে কোন কোড লিখলে সেটা এখানে execute হবে।

STEP-03:

এখন post_details.html থেকে প্রাপ্ত ডাটাগুলো (post id, action)  নিয়ে সার্ভার বা ডাটাবেস আপডেট করার জন্য  view ফাংশন  লিখতে হবে যেখানে ajax অ্যাকশনের সাথে ডাটাবেস আপডেট হবে। blog/views.py:
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.views.decorators.http import require_POST

from .models import Post


@login_required
@require_POST
def post_like(request):
    post_id = request.POST.get('id')
    action = request.POST.get('action')
    if post_id and action:
        try:
            post = Post.objects.get(id=post_id)
            if action == 'like':
                post.users_like.add(request.user)
            else:
                post.users_like.remove(request.user)
            return JsonResponse({'status':'ok'})
        except:
            pass
  • এখানে আমরা দুটি ডেকোরেটর ব্যবহার করেছি, login_required এর মাধ্যমে লগইন ছাড়া ইউজারদেরকে প্রটেক্ট করা হয়েছে এবং  require_POST ডেকোরেটরটি, যদি  HTTP রিকুয়েস্টটি POST এর মাধ্যমে সম্পন্ন না হয় তবে HttpResponseNotAllowed রিটার্ন করবে।
  • post_id ভ্যারিয়েবলে get প্যারামিটারের মাধ্যমে Ajax রিকুয়েস্ট হতে প্রাপ্ত (step-6) ডাটার ‘id’ এর ভ্যালু এবং action ভ্যারিয়েবলে ‘action’ ডাটার স্ট্রিং স্টোর করে রাখা হয়েছে।
  • তারপর id থেকে কুয়েরী করে post অবেজক্টটি  খুঁজে বের করা হয়েছে।
  • action যদি Like হয় তবে add() মেথডের মাধ্যমে user কে ঐ post অবজেক্টের users_like ফিল্ডে যুক্ত করা হয়েছে এবং Unlike হলে remove() মেথডের মাধ্যমে ইউজারকে users_like লিস্ট থেকে রিমুভ করা হয়েছে।

STEP-04:

এখন উপরের view ফাংশনকে এক্সিকিউট করার জন্য একটি URL তৈরি করতে হবে। urls.py ফাইলটি edit করি- blog/urls.py:
path('like/',views.post_like,name='post_like'),

STEP-05:

Cross-Site Request Forgery (CSRF) in AJAX requests:

Django প্রতিটা POST রিকুয়েস্টের জন্য {% csrf_token %} ট্যাগের মাধ্যমে CSRF token চেক করে থাকে। কিন্তু প্রতিটা ajax রিকুয়েস্টর জন্য এটা কিছুটা অসুবিধাজনক।  এই কাজটাকে সহজ করার জন্য জ্যাঙ্গো প্রতিটা ajax রিকুয়েস্টের জন্য CSRF token থেকে প্রাপ্ত ভ্যালুর মাধ্যমে কাস্টম X-CSRF header যুক্ত করার ব্যবস্থা করে দিয়েছে। এই কাজটি দুটি পদ্ধতির মধ্য দিয়ে সম্পন্ন হয়-
  • প্রথমে ব্রাউজারের csrftoken cookies থেকে CSRF token সংগ্রহ করতে হবে।
  • প্রাপ্ত token কে X-CSRFToken header এর মাধ্যমে প্রতিটা রিকুয়েস্টের জন্য send করতে হবে।
উপরের কাজ দুটি করার জন্য নিচের কোডটুকু base.html এ যুক্ত করতে হবে- base.html:
  https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js

  https://cdn.jsdelivr.net/npm/[email protected]/src/js.cookie.min.js

  <script>
    var csrftoken = Cookies.get('csrftoken');
  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }
  $.ajaxSetup({
      beforeSend: function(xhr, settings) {
          if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
              xhr.setRequestHeader("X-CSRFToken", csrftoken);
          }
      }
  });
  $(document).ready(function(){
    {% block domready %}
    {% endblock %}
  });
  • প্রথমে পাবলিক CDN থেকে JS cookies plugin যুক্ত করা হয়েছে CSRF token পাওয়ার জন্য।
  • তারপর safe মেথডগুলোর জন্য একটি ফাংশন লিখা হয়েছে।
  • তারপর ajaxSetup ফাংশনের মধ্যে ajax request পাঠানোর পূর্বে চেক করা হয়েছে এটা safe মেথড (যেমন-GET) কিনা, যদি safe মেথড (যেমন- POST) না হয়, তবে csrftoken ভ্যালুর মাধ্যমে X-CSRFToken header যুক্ত করে দেওয়া হয়েছে।

STEP-06:

এখন আমাদেরকে post_details.html টেমপ্লেটে ajax অ্যাকশন সম্পন্ন করতে হবে, সেজন্য এই টেমপ্লেটের একদম শেষে domready block টি ব্যবহার করা হয়েছে।- templates/post_detials.html:
{% block domready %}
  $('a.like').click(function(e){
    e.preventDefault();
    $.ajax({
        type: "POST",
        url: "{% url 'blog:post_like' %}",
        data: {'id': $(this).data("id"),'action': $(this).data("action")},
        dataType: "json",
        success: function(data){
            var previous_action = $('a.like').data('action');

          // toggle data-action
          $('a.like').data('action', previous_action == 'like' ? 'unlike' : 'like');
          // toggle link text
          $('a.like').text(previous_action == 'like' ? 'Unlike' : 'Like');

          // update total likes
          var previous_likes = parseInt($('span.count .total').text());
          $('span.count .total').text(previous_action == 'like' ? previous_likes + 1 : previous_likes - 1);
        },
        error: function(res,e){
            alert("Something Went Wrong");
        }
    });
  });
{% endblock %}
  • প্রথমেই $(‘a.like’) সিলেক্টরের মাধ্যমে like ক্লাসের a ট্যাগটিকে সিলেক্ট করা হয়েছে।
  • তারপর ‘click’ ইভেন্ট এর জন্য একটি handler ফাংশন ডিফাইন করা হয়েছে। অর্থাৎ যখন ইউজার Like/Unlike বাটনে ক্লিক করবে তখন এই ফাংশনটি এক্সিকিউট হবে।
  • লিঙ্কের ডিফল্ট আচরণ avoid করার জন্য e.preventDefault() ব্যবহার করা হয়েছে।
  • তারপর ajax রিকুয়েস্ট ফাংশন লিখা হয়েছে।
  • type এর মধ্যে রিকুয়েস্টটি কোন ধরণের(POST/GET/PUT/DELETE) হবে সেটা দেওয়া হয়েছে।
  • url এর মাধ্যমে POST রিকুয়েস্টটি কোথায় সম্পন্ন হবে তা নির্ধারণ করে দেওয়া হয়েছে। এই লিঙ্কের মাধ্যমেই প্রাপ্ত ডাটা view ফাংশনে এক্সিকিউট হবে।
  • কোন ডাটাগুলো আমরা ঐ URL এর অ্যাড্রেসে পাঠাবো সেগুলো দিয়ে দেয়া হয়েছে।
  • data type কেমন হবে সেটা বলে দেওয়া হয়েছে। এখান ডাটা JSON ফরমেটে পাঠানো হয়েছে।
  • তারপর success এবং error নামে মূলত  callback() ফাংশন লিখা হয়েছে। অর্থাৎ যদি ajax রিকুয়েস্ট view ফাংশনে এক্সিকিউট হয়ে “ok” return করে তবে success ফাংশনটি এক্সিকিউট হবে।
  • তারপর previous_action এর মাধ্যমে পূর্বের Like or Unlike ভ্যালু নেওয়া হয়েছে।
  • এখন Like/Unlike এর উপর ভিত্তি করে data-action এর ভ্যালু পরিবর্তন করে দেওয়া হয়েছে। অর্থাৎ যদি পূর্বে Like দেওয়া হয়, তবে data-action এ Unlike ভ্যালু স্টোর হবে এবং link text এর ভ্যালুও পরিবর্তন হয়ে Unlike হয়ে যাবে পরবর্তী অপারেশন বা Unlike এর জন্য।
  • যেহেতু সম্পূর্ণ পেইজটি reload হচ্ছে না, সেজন্য server update হওয়ার পর সেই ডাটা total_likes ভ্যারিয়েবলে জমা হবে না। তাই Like/Unlike এর উপর ভিত্তি করে Total Likes এর সংখ্যাটা ১ বাড়িয়ে বা কমিয়ে তা দেখানো হয়।
ধন্যবাদ সাথে থাকার জন্য… 🙂 [If u find any inconsistency, please let me know]  

Leave a Reply

Your email address will not be published. Required fields are marked *