يفتقر تطبيق الاقتراعات الذي نعمل على إنشائه إلى آلية جيّدة للتصويت على الأسئلة التي يتم عرضها للمستخدم، لذا يجب علينا توفير استمارة تتيح للمستخدم التصويت على الإجابة التي يرغب بها. إضافة إلى ذلك، سنتعرف في هذا الدرس على العروض العامة التي تهدف إلى اختصار الوقت والجهد عبر التخلّص من العمليات المتكررة بصورة دائمة، مثل جلب البيانات من قاعدة البيانات وعرض النتائج في صفحة مستقلة. وفي نهاية الدرس سنتعرف على الملفات الساكنة وسنضيف بعض التنسيقات إلى تطبيق الاقتراعات.
إنشاء استمارة بسيطة
يفترض أن يكون المستخدم قادرًا على اختيار إحدى الإجابات الخاصة بسؤال معين، ولكن قالب detail
بشكله الحالي لا يوفّر هذا اﻷمر، لذا سنحتاج إلى إضافة استمارة إلى هذا القالب. توجّه إلى الملف polls/detail.html
في مجلد القوالب templates
ثم أضف إليه الشيفرة التالية:
<h1>{{ question.question_text }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> {% endfor %} <input type="submit" value="صوّت" /> </form>
يعمل السطر الأول على جلب نص السؤال ووضعه داخل وسم <h1>
لعرضه بشكل واضح وكبير.
في السطر الثاني يتم التحقق مما إذا كان المتغير error_message
يحمل قيمة أم ﻻ، فإن كان كذلك يتم عرض هذه القيمة وإلا فلا، سنستخدم هذا المتغير بعد قليل في العرض المسؤول عن التصويت، حيث سيحمل هذا المتغير قيمة نصية وهي عبارة عن رسالة نخبر المستخدم فيها أنّه لم يقم باختيار أي إجابة من الإجابات المعروضة.
في السطر الثالث أنشأنا استمارة يتم توليد قيمة الحدث action
فيه بصورة ديناميكية، وذلك باستخدام الوسم url
والمسار vote
والمتغير question.id
والذي يمثّل الرقم المعرف للسؤال، وستكون طريقة إرسال الحدث هي POST
، وبما أننا نعتمد هذه الطريقة، فعلينا الانتباه إلى حماية المعلومات من تزوير الطلب عبر المواقع Cross-site request forgery والمعروف اختصارًا بـ CSRF. ولكن لا تقلق، يوفّر Django نظامًا سهل الاستخدام لتجنّب هذه المشكلة. باختصار، يجب عليك وضع الوسم {% csrf_tocken %}
في أي استمارة تعتمد طريقة POST
ﻹرسال اﻷحداث.
تعمل الحلقة for على إضافة زر اختيار Radio button إلى الإجابات الخاصة بالسؤال الذي اختاره المستخدم، ويتم جلب هذه الإجابات من باستخدام العبارة question.choice_set.all
.
والآن لنقم بإنشاء العرض الذي سيتحكم في البيانات المرسلة إلى الخادوم.
أعتقد أن سير العمل في Django قد أصبح واضحًا في هذه المرحلة، ففي البداية نقوم بتعريف المسار، ثم نربط هذا المسار بالعرض الذي سيكون مسؤولًا عن القيام بأي شيء نرغب به، وبعد ذلك نربط هذا العرض بالقالب الذي سيظهر النتائج.
وقد قمنا فعلًا بالخطوة الأولى من سلسلة الخطوات هذه في الدرس الرابع من هذه السلسلة عندما بدأنا الحديث عن المسارات، حيث يتضمن ملف polls/urls.py
المسار التالي:
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote')
هذا المسار مرتبط بالعرض view والذي قمنا بإنشائه في الدرس نفسه:
def vote(request, question_id):
return HttpResponse("أنت تصوت على السؤال %s." % question_id)
لنقم الآن بكتابة الشيفرة المسؤولة عن عملية التصويت ضمن هذا العرض، لذا توجّه إلى ملف views.py وأضف الشيفرة التالية في بداية الملف:
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Choice, Question
ثم عدّل دالة vote
لتصبح بالشكل التالي:
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question':question, "error_message": "لم تقم بالتصويت على السؤال"})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
لنتكلم عن الشيفرة السابقة بشيء من التفصيل، فهناك عدد من الأمور الجديدة فيها:
-
request.POST
هو عنصر شبيه بالقاموس يتيح الوصول إلى البيانات المرسلة من الاستمارة بواسطة اسم المفتاح، وفي حالتنا هذا فإنrequest.POST['choice']
سوف يرجع قيمة المعرّف الخاص بالجواب الذي اختاره المستخدم، وتكون هذه القيمة على هيئة سلسلة نصية.
وجدير بالذكر أن Django يقدّم عنصرًا آخر باسمrequest.GET
ويستخدم عندما تكون طريقة إرسال البيانات في الاستمارة هيGET
. -
يطلق العنصر
request.POST['choice]
خطأً مفتاحيًاKeyError
وهذا النوع من الأخطاء يُطلق عندما لا يكون المفتاح المطلوب (ضمن القاموس) موجودًا ضمن مجموعة المفاتيح المتوفرة، وفي تطبيقنا ينطلق هذا الخطأ عندما ﻻ يختار المستخدم أي إجابة من الإجابات المعروضة أمامه، ويعمل على إعادة المستخدم إلى صفحة التصويت مرة أخرى، ولكن هذه المرة مع إضافة العبارة: “لم تقم بالتصويت على السؤال” إلى المتغيرerror_message
، وبعد أن أصبح هذا المتغير يحمل قيمة معينة، سيقوم قالبdetail
بعرض الرسالة في المكان المناسب. -
إن تمت عملية التصويت بنجاح يتم إضافة صوت واحد إلى الأصوات الخاصة بالإجابة المختارة من قبل المستخدم، وقد استخدمنا الصنف
HttpResponseRedirect
بدلًا من الصنفHttpResponse
لإعادة توجيه المستخدم إلى صفحة النتائج. هذا الصنف في الواقع هو أحد الأصناف الفرعية للصنفHttpResponse
ويأخذ معاملًا واحدًا وهو عنوانURL
الذي ستتم إعادة التوجيه إليه. والهدف من استخدام هذا الصنف هو تجنّب إرسال البيانات مرتين في حال ضغط المستخدم على زر الرجوع في المتصفح، وينصح باستخدام هذا الصنف في كل مرة يتم فيها التعامل بنجاح مع بياناتPOST
، وهذا الأمر ليس خاصًا بـ Django فقط، بل هو من الممارسات الجيدة في مجال تطوير الويب باستخدام أي لغة برمجية. -
استخدمنا الدالة
reverse()
في مشيّد الصنفHttpResponseRedirect
لنتجنّب الإدخال اليدوي للمسار الذي نرغب بإعادة توجيه المستخدم إليه. يأخذ المعامل الأول في هذه الدالة اسم العرض أو اسم نمطURL
المطلوب إعادة التوجيه إليه. في هذا المثال سيتم توجيه المستخدم إلى المسار الذي يحمل اسمpolls:results
، ولكن هذا المسار يتضمن متغيّرًا، لذا سنخبر Django بأن يأخذ قيمته من المتغيّرquestion.id
وذلك من خلال المعاملargs
.
بمعنى أنّه لو اختار المستخدم السؤال الذي يحمل الرقم 3، فإن الصنفHttpResponseRedirect
سيعيد السلسلة النصية التالية:polls/3/results/
حيث أن قيمةquestion.id
في هذه الحالة هو 3.
بعد أن يقوم المستخدم بالتصويت على سؤال معين، تعمل دالة العرض vote()
على إعادة توجيهه إلى صفحة النتائج الخاصة بذلك السؤال، فلنقم إذًا بكتابة دالة العرض الخاصّة بالنتائج. توجّه إلى ملف polls/views.py
وعدّل الدالة results
لتصبح بالشكل التالي:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
والآن، قم بإنشاء القالب المسؤول عن عرض النتائج. في مجلد templates/polls/
أنشئ الملف results.html
، وأضف إليه الشيفرة التالية:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">صوّت مرة أخرى</a>
والآن توجّه إلى العنوان التالي في متصفحك بعد تشغيل الخادوم الخاص بـ Django:
127.0.0.1:8000/polls/1/
وستحصل على نتيجة مشابهة لهذه:
وبعد التصويت سيتم توجيهك إلى صفحة النتائج:
وفي حال عدم التصويت على أي إجابة والضغط على زرّ التصويت، تظهر رسالة الخطأ بالشكل التالي:
العروض العامة Generic Views
لنلق نظرة على ملف views.py
بعد إضافة جميع الدوال إليه:
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Choice, Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
return render(request, 'polls/index.html', {'latest_question_list': latest_question_list})
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question':question, "error_message": "لم تقم بالتصويت على السؤال"})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
هل لاحظت أمرًا ما؟ الدالتان detail()
وresults()
متشابهتان كثيرًا، وفي الواقع الفارق الوحيد بينهما هو القالب الذي يرتبط بكل دالة. إضافة إلى ذلك فإن هاتين الدالتين إضافة إلى دالة index()
تقوم بعمل واحد وبسيط، وهو عرض مجموعة من المعلومات على المتصفح.
تمثل هذه العروض حالة شائعة جدًّا في عملية تطوير الويب، وهي الحصول على البيانات من قاعدة البيانات بالاستناد إلى معامل يتم تمريره في عنوان URL
في المتصفح، ثم تحميل قالب معين، ثم تصيير ذلك القالب على متصفح الإنترنت.
لقد ذكرنا في بداية هذه السلسلة أنّ إطار العمل Django يركّز على مبدأ عدم التكرار، وقد رأينا هذا بشكل واضح عند استخدامنا للدوال المختصرة render()
و get_object_or_404()
.
كذلك يوفّر Django في حالتنا هذه طريقة مختصرة للتعامل مع هذه العمليات الشائعة، وهي العروض العامّة. تعمل هذه العروض على اختصار الأنماط المتكررة من الشيفرات إلى درجة لا تعود فيها بحاجة إلى كتابة شيفرة بايثون لكتابة التطبيق.
ولاستخدام العروض العامّة يتوجّب علينا إجراء بعض التعديلات البسيطة على شيفرتنا السابقة، ويمكن تلخيص هذه التعديلات بالنقاط التالية:
-
تعديل أنماط
URL
. - الاستغناء عن بعض العروض التي لسنا بحاجة إليها بعد الآن.
- كتابة عروض جديدة بالاعتماد على عروض Django العامة.
تعديل أنماط URL
توجّه إلى ملف polls/urls.py
وعدّله بالشكل التالي:
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
لاحظ أنّنا استبدلنا <question_id>
بـ <pk>
في مساري detail
و results
، وسنتعرف على سبب ذلك بعد قليل.
تعديل العروض
التعديلات التي سنجريها على العروض ستشمل دوال index(), detail(), results()
أما دالة vote()
فستبقى دون تغيير:
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
لاحظ كيف استغنينا بشكل كامل عن الدوال الثلاثة سابقة الذكر لتحلّ محلّها أصناف تحمل أسماءً مشابهة، ونظرًا لاستخدام الأصناف في هذا النوع من العروض، فإنّها تحمل اسم العروض المستندة إلى الأصناف Class based views.
لقد استخدمنا نوعين من أنواع العروض العامة، وهما ListView
و DetailView
. يلخّص العرض اﻷول عملية عرض جميع العناصر، في حين يلخّص العرض الثاني عملية عرض التفاصيل المرتبطة بعنصر معيّن.
يحتاج كل عرض عامٍّ إلى التعرّف على النموذج الذي سيتعامل معه، ويتم التصريح عن ذلك في المعامل model
. إلى جانب ذلك، يطلب العرض DetailView
أن يتم إسناد قيمة المفتاح الرئيسي المأخوذة من عنوان URL
إلى متغير يحمل الاسم pk
، لهذا السبب قمنا بتبديل question_id
بالاسم الجديد في ملف المسارات urls.py
.
يبحث العرض DetailView
بصورة افتراضية عن القالب المرتبط به في المسار <app name>/<model_name>_detail.html
، وهذا يعني أنّ هذا العرض سيستخدم القالب polls/question_detail.html
بشكل تلقائي. ويمكن تجاوز هذه القيمة التلقائية من خلال تعيين اسم القالب باستخدام المعامل template_name
. ينطبق الأمر ذاته على العرض ListView
والذي سيستخدم بشكل تلقائي القالب polls/question_list.html
.
بقي أن نشير إلى أنّنا كنا نزوّد كل قالب بمتغيرات السياق التي سيستخدمها، وفي مثالنا هذا كانت المتغيرات هي question
و latest_question_list
.
عند استخدام العروض العامّة يتم تزويد العرض DetailView
بالمتغير question
تلقائيًا، وذلك ﻷننا نستخدم النموذج Question
، حيث يستطيع Django تحديد اسم مناسب لمتغيرات السياق بناء على النموذج الذي يرتبط بالعرض العام. ويمكن تجاوز القيمة التلقائية هذه بسهولة وذلك بإسناد اسم المتغير الذي نرغب به إلى المعامل context_object_name
، وبهذا نخبر Django بأنّنا نرغب باستخدام هذا الاسم بدلًا من الاسم الذي سينشئه بصورة تلقائية وهو في هذه الحالة question_list
.
كما يمكنك الإبقاء على الاسم الافتراضي question_list
إن كنت ترغب في ذلك، ولكن هنا عليك تغيير اسم المتغير في أي مكان يرد فيه ضمن القوالب.
الملفات الساكنة Static Files
يبدو تطبيقنا بشعًا أليس كذلك؟ لنقم إذًا بإضافة بعض التنسيقات البسيطة إليه، ولنتعرف على مفهوم الملفات الساكنة.
إلى جانب ملفات HTML التي ينشئها الخادوم، تحتاج تطبيقات الويب بشكل عام إلى بعض الملفات اﻹضافية - مثل الصور وملفات جافا سكربت وملفات CSS - لعرض صفحات الويب بشكل منسّق ومرتّب، ويطلق Django على هذه الملفات اسم الملفات الساكنة.
سنبدأ أولًا بإنشاء مجلد باسم static
في مجلد polls
الرئيسي، ثم أنشئ داخل المجلد static
مجلّدًا جديدًا باسم polls
. بمعنى آخر يجب أن يكون مسار ملف CSS بالشكل التالي: polls/static/polls/style.css
. كما هو الحال مع القوالب، فإن Django يبحث بنفس الطريقة عن الملفات الساكنة، واستخدام هذه المجلدات هو أمر تنظيمي يتيح لـ Django التمييز بين الملفات الساكنة الخاصة بكل تطبيق.
والآن توجّه من خلال محرّر النصوص إلى ملف style.css
وأضف إليه الشيفرة التالية:
li a {
color: green;
text-decoration: none;
font-size: 1.3em;
}
والآن توجّه قالب index.html
وأضف الشيفرة التالية في بداية الملف:
{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
يعمل الوسم load staticfiles
في بداية الملف على تحميل الوسم static
المسؤول بدوره عن توليد عنوان URL المطلق للملف الساكن المطلوب.
والآن أعد تحميل الصفحة الرئيسية وستلاحظ أن الأسئلة قد تلونت باللون اﻷخضر.
ولإضافة صورة للخلفية قم بإنشاء مجلد images
في نفس المجلد الذي يحتوي الملف style.css
ثم ضع فيه الصورة التي ترغب في جعلها خلفية للصفحة، وعلى فرض استخدام صورة باسم background.gif
أضف الشيفرة التالية إلى ملف style.css
:
body {
background: white url("images/background.gif") no-repeat right bottom;
}
أعد تحميل الصفحة الرئيسية، وستلاحظ أن الصورة قد أصبحت خلفية للصفحة.
من الجدير بالذكر أنّه لا يمكن استخدام الوسم {% static %}
استخدام داخل الملفات الساكنة والتي لا يتم توليدها بواسطة Django، لذا يجب استخدام المسارات النسبية وليست المطلقة لربط الملفات الساكنة بعضها ببعض.
ختامًا
تعرّفنا في هذا الدرس على كيفية استخدام نماذج الاستبيان في Django بصورة مبسطة، واستخدمنا بعد ذلك العروض العامّة التي تختصر الكثير من الوقت والجهد، وفي النهاية تعرّفنا على مفهوم الملفات الساكنة وكيفية استخدامها في تطبيقات Django. في الدرس القادم وهو الدرس اﻷخير ضمن هذه السلسلة، سنتحدّث بشيء من التفصيل عن لوحة التحكم التي يتم إنشاؤها بصورة تلقائية مع كل مشروع في Django.
المصدر:
توثيقات Django
أفضل التعليقات
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.