والآن بعد أن تعرفنا على كيفية استرجاع البيانات باستخدام الاستعلامات queries وكيفية إرسال البيانات باستخدام الطفرات mutations، أصبح بإمكاننا تجربة شيء أكثر صعوبة. سنتعلم في هذا المقال كيفية إنشاء نظام لإضافة التعليقات comments والإعجابات likes.
- إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الأول
- إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثاني
- إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثالث
إنشاء نظام التعليقات Comments
لنبدأ بقسم التعليقات، لكن قبل الخوض عميقًا في الشيفرات البرمجية، هناك بعض الأشياء التي يجب تذكرها.
أولًا، ولأسباب الحماية، يمكن فقط للمستخدمين الذين سجلوا دخولهم إضافة التعليقات. الأمر الثاني، يمكن لكل مستخدم ترك تعليقات متعددة، لكن كل تعليق لا يمكن أن يتبع إلا لمستخدم واحد، أي يمكن أن نجد تعليقات كثيرة لمستخدم واحد، لكن لا يمكن أن نجد تعليق يتبع لعدد من المستخدمين.
أمر آخر يمكن تجدر الإشارة له وهي أن كل مقال يمكن أن يحتوي عدة تعليقات، لكن كل تعليق يتبع لمقال واحد فقط. وأخيرًا، يجب أن يوافق المشرف على التعليق قبل ظهوره على صفحة المقال.
ما الذي يظهر للمستخدم في حال عدم تسجيل الدخول:
والآن بعد تسجيل الدخول:
إعداد الواجهة الخلفية
مع أخذ الملاحظات السابقة في الحسبان، لنبدأ بإنشاء نموذج للتعليقات. يُفترض أن تكون الشيفرات البرمجية التالية واضحةً بالنسبة لك إذا كان لديك معرفة جيدة بكيفية العمل مع إطار العمل جانغو.
# 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.
اقرأ أيضًا
- المقال السابق: إنشاء تطبيق حديث باستخدام Django و Vue | الجزء الثاني: ربط الواجهة الخلفية والأمامية
- مدخل إلى المكتبة GraphQL واستعمالاتها في بناء تطبيقات الويب الحديثة
- بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React
- إنشاء نماذج جانغو Django Models وربطها بقاعدة البيانات
- تعرف على أمان تطبيقات جانغو
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.