اذهب إلى المحتوى

والآن بعد أن تعرفنا على كيفية استرجاع البيانات باستخدام الاستعلامات queries وكيفية إرسال البيانات باستخدام الطفرات mutations، أصبح بإمكاننا تجربة شيء أكثر صعوبة. سنتعلم في هذا المقال كيفية إنشاء نظام لإضافة التعليقات comments والإعجابات likes.

إنشاء نظام التعليقات Comments

لنبدأ بقسم التعليقات، لكن قبل الخوض عميقًا في الشيفرات البرمجية، هناك بعض الأشياء التي يجب تذكرها.

أولًا، ولأسباب الحماية، يمكن فقط للمستخدمين الذين سجلوا دخولهم إضافة التعليقات. الأمر الثاني، يمكن لكل مستخدم ترك تعليقات متعددة، لكن كل تعليق لا يمكن أن يتبع إلا لمستخدم واحد، أي يمكن أن نجد تعليقات كثيرة لمستخدم واحد، لكن لا يمكن أن نجد تعليق يتبع لعدد من المستخدمين.

أمر آخر يمكن تجدر الإشارة له وهي أن كل مقال يمكن أن يحتوي عدة تعليقات، لكن كل تعليق يتبع لمقال واحد فقط. وأخيرًا، يجب أن يوافق المشرف على التعليق قبل ظهوره على صفحة المقال.

ما الذي يظهر للمستخدم في حال عدم تسجيل الدخول:

01 حالة عدم تسجيل الدخول

والآن بعد تسجيل الدخول:

02 بعد تسجيل الدخول

إعداد الواجهة الخلفية

مع أخذ الملاحظات السابقة في الحسبان، لنبدأ بإنشاء نموذج للتعليقات. يُفترض أن تكون الشيفرات البرمجية التالية واضحةً بالنسبة لك إذا كان لديك معرفة جيدة بكيفية العمل مع إطار العمل جانغو.

# Comment model
class Comment(models.Model):
    content = models.TextField(max_length=1000)
    created_at = models.DateField(auto_now_add=True)
    is_approved = models.BooleanField(default=False)

    # Each comment belongs to one user and one post
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    post = models.ForeignKey(Post, on_delete=models.SET_NULL, null=True)

والآن تابع وطبِّق التغييرات التي أجريتها على النماذج. انتقل إلى الطرفية terminal وفعِّل الأوامر التالية:

python manage.py makemigrations
python manage.py migrate

ستحتاج أيضًا إلى إعداد GraphQL في الواجهة الخلفية. يمكنك إضافة نمط بيانات جديد يسمى CommentType لتمثيل التعليقات والتعامل معها ضمن واجهة GraphQL على النحو التالي:

class CommentType(DjangoObjectType):
    class Meta:
        model = models.Comment

الآن بالنسبة للطفرة mutation، لاحظ أن هناك ثلاثة أشياء يحتاج جانغو إلى معرفتها لإضافة تعليق، وهي محتوى التعليق، والمستخدم الذي يريد إضافة هذا التعليق، والمقال الذي يريد ترك التعليق عليه. سنكتب الكود التالي لإضافة تعليق جديد باستخدام GraphQL:

class CreateComment(graphene.Mutation):
    comment = graphene.Field(types.CommentType)

    class Arguments:
        content = graphene.String(required=True)
        user_id = graphene.ID(required=True)
        post_id = graphene.ID(required=True)

    def mutate(self, info, content, user_id, post_id):
        comment = models.Comment(
            content=content,
            user_id=user_id,
            post_id=post_id,
        )
        comment.save()

        return CreateComment(comment=comment)
class Mutation(graphene.ObjectType):
    . . .
    create_comment = CreateComment.Field()

تذكر إضافة صنف CreateComment داخل صنف Mutation.

إعداد الواجهة الأمامية

لإعداد الواجهة الأمامية، لننتقل إلى الملف Post.vue حيث المكان الذي تظهر فيه التعليقات. يُرجى ملاحظة أن هناك بعض التعليمات البرمجية محذوفة في الأمثلة التالية، بهدف ألا تكون الشيفرات طويلة جدًا، لكن إذا كنت تريد الحصول على التعليمات البرمجية الكاملة، فيمكنك تنزيل التعليمات المصدرية من هنا.

<script>
import { POST_BY_SLUG } from "@/queries";
import CommentSectionComponent from "@/components/CommentSection.vue";

export default {
  name: "PostView",

  components: { CommentSectionComponent },

  data() {
    return {
      postBySlug: null,
      comments: null,
      userID: null,
    };
  },

  computed: {
    // Filters out the unapproved comments
    approvedComments() {
      return this.comments.filter((comment) => comment.isApproved);
    },
  },

  async created() {
    // Get the post before the instance is mounted
    const post = await this.$apollo.query({
      query: POST_BY_SLUG,
      variables: {
        slug: this.$route.params.slug,
      },
    });
    this.postBySlug = post.data.postBySlug;
    this.comments = post.data.postBySlug.commentSet;
  },
};
</script>

الملف query.js:

export const POST_BY_SLUG = gql`
  query ($slug: String!) {
    postBySlug(slug: $slug) {
      . . .
      commentSet {
        id
        content
        createdAt
        isApproved
        user {
          username
          avatar
        }
        numberOfLikes
        likes {
          id
        }
      }
    }
  }
`;

بدايةً، يمكنك في الخطاف create()‎ استرجاع المقال المطلوب، إضافةً إلى التعليقات باستخدام الاستعلام POST_BY_SLUG الذي يظهر في الأعلى.

ستحتاج بعد ذلك في خاصية computed إلى تصفية التعليقات التي لم يوافق عليها المشرف. وأخيرًا، ستعمل على تمرير التعليق ومعرف المنشور post ID ومعرف المستخدم user ID إلى CommentSectionComponent.

الملف CommentSectionComponent.vue:

<template>
  <div class="home">
    . . .
    <!-- Comment Section -->
    <!-- Pass the approved comments, the user id and the post id to the comment section component -->
    <comment-section-component
      v-if="this.approvedComments"
      :comments="this.approvedComments"
      :postID="this.postBySlug.id"
      :userID="this.userID"
    ></comment-section-component>
  </div>
</template>

لنلقِ الآن نظرة عميقة على مكون قسم التعليقات. يحتوي هذا المكون على قسمين، القسم الأول هو نموذج يسمح للمستخدم بترك التعليقات، والذي يظهر فقط عندما يسجل المستخدم دخوله، والقسم الثاني هو قائمة بالتعليقات الموجودة.

الملف CommentSection.vue

<script>
import { SUBMIT_COMMENT } from "@/mutations";
import CommentSingle from "@/components/CommentSingle.vue";
import { useUserStore } from "@/stores/user";

export default {
  components: { CommentSingle },
  name: "CommentSectionComponent",

  setup() {
    const userStore = useUserStore();
    return { userStore };
  },

  data() {
    return {
      commentContent: "",
      commentSubmitSuccess: false,
      user: {
        isAuthenticated: false,
        token: this.userStore.getToken || "",
        info: this.userStore.getUser || {},
      },
    };
  },
  props: {
    comments: {
      type: Array,
      required: true,
    },
    postID: {
      type: String,
      required: true,
    },
    userID: {
      type: String,
      required: true,
    },
  },
  async created() {
    if (this.user.token) {
      this.user.isAuthenticated = true;
    }
  },
  methods: {
    submitComment() {
      if (this.commentContent !== "") {
        this.$apollo
          .mutate({
            mutation: SUBMIT_COMMENT,
            variables: {
              content: this.commentContent,
              userID: this.userID,
              postID: this.postID,
            },
          })
          .then(() => (this.commentSubmitSuccess = true));
      }
    },
  },
};
</script>

من المُفترض أنك على معرفة جيدة بكيفية استخدام الوحدة Pinia التي ثبتناها في المقال السابق للتحقق مما إذا كان المستخدم قد سجل دخوله، وكيفية استخدام props لتمرير المعلومات بين المكونات المختلفة، لذلك سنتخطى هذه الأمور وسنركز على تابع SubmitComment()‎.

عند استدعاء هذا التابع، فإنها ستختبر ما إذا كان التعليق فارغًا، وإذا لم يكن كذلك، فسوف تستخدم طفرة SUBMIT_COMMENT لإنشاء تعليق جديد. تُعرّف طفرة SUBMIT_COMMENT كالتالي:

الملف mutations.js:

export const SUBMIT_COMMENT = gql`
  mutation ($content: String!, $userID: ID!, $postID: ID!) {
    createComment(content: $content, userId: $userID, postId: $postID) {
      comment {
        content
      }
    }
  }
`;

الشيفرة التالية هي كود HTML ضمن ملف CommentSection.vue وهي المسؤولة عن عرض قسم التعليقات في واجهة المستخدم. لاحظ أننا استخدمنا مكونًا آخر في نهاية الشيفرة وهو CommentSingle.vue وذلك لعرض تعليق واحد.

قسم HTML من ملف CommentSection.vue

<template>
  <div class=". . .">
    <p class="font-bold text-2xl">Comments:</p>

    <!-- If the user is not authenticated -->
    <div v-if="!this.user.isAuthenticated">
      You need to
      <router-link to="/account">sign in</router-link>
      before you can leave your comment.
    </div>

    <!-- If the user is authenticated -->
    <div v-else>
      <div v-if="this.commentSubmitSuccess" class="">
        Your comment will show up here after is has been approved.
      </div>
      <form action="POST" @submit.prevent="submitComment">
        <textarea type="text" class=". . ." rows="5" v-model="commentContent" />

        <button class=". . .">Submit Comment</button>
      </form>
    </div>

    <!-- List all comments -->
    <comment-single
      v-for="comment in comments"
      :key="comment.id"
      :comment="comment"
      :userID="this.userID"
    >
    </comment-single>
  </div>
</template>

والآن دعونا نلقي نظرة عميقة على الملف CommentSingle.vue

قسم HTML من ملف CommentSingle.vue:

<template>
  <div class="border-2 p-4">
    <div
      class="flex flex-row justify-start content-center items-center space-x-2 mb-2"
    >
      <img
        :src="`http://127.0.0.1:8000/media/${this.comment.user.avatar}`"
        alt=""
        class="w-10"
      />
      <p class="text-lg font-sans font-bold">
        {{ this.comment.user.username }}
      </p>
    </div>

    <p>
      {{ this.comment.content }}
    </p>
  </div>
</template>

قسم جافا سكريبت من ملف CommentSingle.vue:

<script>
export default {
  name: "CommentSingleComponent",
  data() {
    return {
      . . .
    };
  },
  props: {
    comment: {
      type: Object,
      required: true,
    },
    userID: {
      type: String,
      required: true,
    },
  },
};
</script>

إنشاء نظام التفاعل بالإعجاب Like

فيما يتعلق بنظام الإعجاب Like، فهناك أيضًا بعض الأمور التي يجب أخذها في الحسبان. بدايةً، يجب على المستخدم تسجيل الدخول للتفاعل والإعجاب بالتعليق، كما هو الحال مع كتابة التعليقات، لكن يمكن لأي مستخدم رؤية عدد الإعجابات حتى وإن لم يكن مسجلًا.

الأمر الثاني هو أنه يمكن لكل مستخدم وضع إعجاب واحد فقط لكل مقال، وسيؤدي النقر فوق زر "أعجبني" مرةً أخرى إلى إزالة الإعجاب الأول. وأخيرًا، يمكن للمقال الواحد الحصول على إعجابات عديدة من مستخدمين مختلفين.

إعداد الواجهة الخلفية

لنبدأ مرة أخرى بالنماذج models. بما أنه يمكن لكل مقال الحصول على العديد من الإعجابات من عدة مستخدمين، ويمكن لكل مستخدم إضافة العديد من الإعجابات للعديد من المقالات، فإن هذه تُعَد علاقة متعدد إلى متعدد بين المنشور post والمستخدم user.

لاحظ أيضًا أننا في هذه المرة أنشأنا دالة get_number_of_likes()‎‎ لتحصيل إجمالي عدد الإعجابات. تذكر تطبيق هذه التغييرات على قاعدة البيانات باستخدام الأوامر التي تحدثنا عنها سابقًا.

# Post model

class Post(models.Model):
    . . .

    # Each post can receive likes from multiple users, and each user can like multiple posts
    likes = models.ManyToManyField(User, related_name='post_like')

    . . .

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

ومن ثم نضيف الأنواع types والطفرات mutations.

class PostType(DjangoObjectType):
    class Meta:
        model = models.Post

    number_of_likes = graphene.String()

    def resolve_number_of_likes(self, info):
        return self.get_number_of_likes()

لاحظ التعليمة self.get_number_of_likes()‎ في السطر 8 التي تستدعي الدالة get_number_of_likes()‎ التي عرّفتها في النموذج. ولإضافة إعجاب إلى منشور، يجب عليك معرفة كل من معرّف id المقال، ومعرّف id المستخدم الذي يريد التفاعل بإعجاب مع هذا المقال.

لاحظ الكود المكتوب من السطر 11 إلى السطر 14، إذا كان المنشور يحتوي على إعجاب من المستخدم الحالي، فسيؤدي الضغط على زر الإعجاب إلى إزالة الإعجاب السابق، وإذا لم يكن كذلك، سيُضاف إعجاب إلى المقال.

إعداد الواجهة الأمامية

سنحتاج الآن إلى إضافة زر الإعجاب إلى صفحة النشر، لذا ارجع إلى Post.vue.

قسم HTML من Post.vue.

<template>
  <div class="home">
    . . .

    <!-- Like, Comment and Share -->
    <div class=". . .">
      <div v-if="this.liked === true" @click="this.updateLike()">
        <i class="fa-solid fa-thumbs-up">
          <span class="font-sans font-semibold ml-1">{{
            this.numberOfLikes
          }}</span>
        </i>
      </div>
      <div v-else @click="this.updateLike()">
        <i class="fa-regular fa-thumbs-up">
          <span class="font-sans font-semibold ml-1">{{
            this.numberOfLikes
          }}</span>
        </i>
      </div>
      . . .
    </div>

    . . .
  </div>
</template>

قسم جافا سكريبت من Post.vue

<script>
import { POST_BY_SLUG } from "@/queries";
import { UPDATE_POST_LIKE } from "@/mutations";
. . .

export default {
  . . .
  async created() {
    . . .
    // Find if the current user has liked the post
    let likedUsers = this.postBySlug.likes;

    for (let likedUser in likedUsers) {
      if (likedUsers[likedUser].id === this.userID) {
        this.liked = true;
      }
    }

    // Get the number of likes
    this.numberOfLikes = parseInt(this.postBySlug.numberOfLikes);
  },

  methods: {
    updateLike() {
      if (this.liked === true) {
        this.numberOfLikes = this.numberOfLikes - 1;
      } else {
        this.numberOfLikes = this.numberOfLikes + 1;
      }
      this.liked = !this.liked;

      this.$apollo.mutate({
        mutation: UPDATE_POST_LIKE,
        variables: {
          postID: this.postBySlug.id,
          userID: this.userID,
        },
      });
    },
  },
};
</script>

لاحظ أننا حذفنا بعض التعليمات البرمجية في المثال السابق كي لا تكون طويلةً جدًا، ولكن ما تزال هناك أربعة أمور نحتاج إلى التحدث عنها في هذا المثال.

أولاً، فيما يتعلق بالاستعلام POST_BY_SLUG الذي تستخدمه لاسترجاع المقال، يجب التأكد من أنه يجلب عدد الإعجابات والمستخدمين الذين تفاعلوا مع المقال.

الملف query.js:

export const POST_BY_SLUG = gql`
  query ($slug: String!) {
    postBySlug(slug: $slug) {
      . . .
      numberOfLikes
      likes {
        id
      }
      . . .
    }
  }
`;

الخطوة التالية، في الخطاف created()‎، وبعد أن تسترجع المنشور، يجب عليك تحديد ما إذا كان المستخدم الحالي موجودًا في قائمة المستخدمين الذين تفاعلوا مع المنشور.

فيما بعد، عند استدعاء الدالة updateLike()‎، ستعمل هذه الدالة على تغيير عدد الإعجابات وذلك في حال تفاعل المستخدم مع بالمنشور أو لم يتفاعل. وأخيرًا، ستعمل الدالة updateLike()‎،على تحديث عدد إعجابات المنشور في الواجهة الخلفية باستخدام طفرة UPDATE_POST_LIKE.

الملف mutations.js:

export const UPDATE_POST_LIKE = gql`
  mutation ($postID: ID!, $userID: ID!) {
    updatePostLike(postId: $postID, userId: $userID) {
      post {
        id
        title
        likes {
          id
        }
      }
    }
  }
`;

تحدي برمجي

والآن بعد أن تعلمنا كيفية إنشاء نظام التعليقات ونظام التفاعل بالإعجاب، دعنا نفكر في تحدٍّ أكبر، فماذا لو أردنا إنشاء نظام تعليق متداخل، بحيث نتيح للمستخدمين إمكانية التعليق على تعليق آخر؟ إذًا كيف يمكننا تغيير الشيفرات البرمجية السابقة لجعل ذلك ممكنًا؟ جرب العمل على ذلك وفكر وكيف يمكننا إنشاء نظام للتفاعل بإعجاب على التعليقات.

يمكنك الاطلاع على كامل الكود المصدري الذي يحتوي على التطبيق العملي للوظائف التي مرت معنا في سلسلة المقالات هذه.

الخلاصة

بهذا نكون قد تعرفنا على طريقة إضافة التعليقات والإعجابات على المقالات ووصلنا لنهاية هذه السلسلة من المقالات التي شرحنا من خلالها كيفية إنشاء تطبيق ويب حديث من صفحة واحدة باستخدام إطارالعمل جانغو Django كواجهة خلفية، وإطار العمل فيو Vue كواجهة أمامية، مع استخدام GraphQL كلغة للتفاعل مع واجهة برمجة التطبيقات API التي تربط بينهما.

ترجمة -وبتصرّف- للمقال Create a Modern Application with Django and Vue #3.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...