فصل چهارم: اپلیکیشن بلاگ(بخش دوم)
فهرست مطالب
- متد ایجاد URL برای پست ها
- forloop
- صفحه بندی (pagination)
- View های مبتنی بر کلاس (Class-based views)
- آشنایی با ساختار فرم ها
- آشنایی با Form (ساخت فرم تیکت)
- کار با تمپلیت فرم ها
- اعتبار سنجی فرم ها و نمایش خطا ها
- آشنایی با forms.ModelForm (ساخت فرم کامنت-بخش اول و دوم)
- ایجاد تمپلیت تگ سفارشی (simple tag)
- ایجاد تمپلیت تگ سفارشی (Inclusion tags)
- کار با aggregate و annotate (تگ سفارشی برای کوئری ست)
- ایجاد تمپلیت فیلتر سفارشی و کار با markdown
- استایل دهی فرم های آماده
- تمرینات فصل چهارم (مهم)
متد ایجاد URL برای پست ها
متد ایجاد url برای مدل ها استفاده میشود. / این متد مثل تمپلیت تگ {% url %} که برای url ها استفاده میشود کاربرد دارد.
در بدنه این متد از تابع reverse() استفاده میکنیم. / لازمه قبل از استفاده، reverse را ایمپورت کنیم.
app directory/models.py
from django.url import reverse
از این متد داخل کلاس های ایجاد شده در models.py استفاده میکنیم. / اسم این متد get_absolute_url هست.
تابع reverse چندین آرگومان دارد، اولین و مهم ترین آنها viewname هست که میتواند یک الگوی url باشد. | آرگومان های args و kwargs عملکرد تقریباً یکسانی دارند ولی نمیتوان همزمان از هر دو استفاده کرد(در ادامه کامل تر و با مثال توضیح داده میشوند).
برای آدرس url از الگوی app_name:name-in-path استفاده میکنیم.
خب بریم این متد را برای url جزئیات پست پیاده سازی کنیم. / url مربوط به post_detail:
app directory/urls.py
app_name = 'blog' urlpatterns = [ # ... # URL For Post-Detail path('posts/<int:id>', views.post_detail, name="post_detail") ]
خب url ما دارای بخش ثابت و یک متغیر میباشد(اسم متغیر id هست)./ اگر متغیر داشته باشیم از args ویا kwargs استفاده میکنیم.
آرگومان args: یک لیست یا تاپل است که مقادیر متغیرهای موجود در URL را به ترتیب مشخص میکند. در args ترتیب بسیار مهم است، زیرا این مقادیر باید به ترتیب پارامترهایی که در مسیر URL تعریف شدهاند قرار گیرند.
آرگومان kwargs: یک دیکشنری است که کلیدهای(key) آن نام متغیرهایی است که در URL وارد کردهایم(در این مثال id اسم متغیر میباشد) و مقدارهای(value) آن مقادیر متغیرها است. در kwargs ترتیب اهمیتی ندارد.
در اینجا منظور از متغیر id در واقع آیدی هر پست میباشد./ آیدی هر پست را با استفاده از post.id بدست می آوریم ولی در کلاس از self به عنوان هر instance از آن کلاس استفاده میکنیم پس برای هر پست از self.id استفاده میکنیم.
براساس این url میخواهیم متد get_absolute_url را ایجاد کنیم.
app directory/models.py
# structure def get_absolute_url(self): return reverse('app_name:name', <args or kwargs>) # ------------------------- # first mode: def get_absolute_url(self): return reverse('blog:post_detail', args=[self.id]) # second mode: def get_absolute_url(self): return reverse('blog:post_detail', kwargs={'id': self.id})
خب بریم این متد را توی مدل Post ببینیم(انتهای کد):
app directory/models.py
from django.db import models from django.utils import timezone from django.urls import reverse class Post(models.Model): class Status(models.TextChoices): DRAFT = 'DF', 'Draft' PUBLISHED = 'PB', 'Published' REJECTED = 'RJ', 'Rejected' # field for the selected mode (using the top class) status = models.CharField(max_length=2, choices=Status.choices, default=Status.DRAFT) # Relational field(Many To One) author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_posts") # title of the post title = models.CharField(max_length=250) # post description description = models.TextField() # The main part of the address is after the domain name slug = models.SlugField(max_length=250) # Date # post publish date publish = models.DateTimeField(default=timezone.now) # post creation date created = models.DateTimeField(auto_now_add=True) # post update date updated = models.DateTimeField(auto_now=True) # ------------------------------------------------------------------------- objects = models.Manager() published = PublishedManager() # ------------------------------------------------------------------------- # To sort the table class Meta: # Sort by ordering = ['-publish'] # (-) is used to reverse the sort. => sort by latest post # Indexing by indexes = [ models.Index(fields=['-publish']) ] # ------------------------------------------------------------------------- # first mode: # def get_absolute_url(self): # return reverse('blog:post_detail', kwargs={'id': self.id}) # second mode: def get_absolute_url(self): return reverse('blog:post_detail', args=[self.id]) def __str__(self): return self.title
حالا هرجا خواستیم از url مربوط به post_detail استفاده کنیم میتوان از حالت زیر استفاده کرد/ حتی میتوان در تمپلیت ها از آن استفاده کرد:
post.get_absolute_url
{{ post.get_absolute_url }}
forloop
تمپلیت تگ for در جنگو؛ از یک متغیر داخلی به نام forloop استفاده میکند که شامل اطلاعاتی دربارهی حلقهی فعلی است. این متغیر دارای چندین ویژگی است که به تحلیل و کنترل حلقه کمک میکند(از forloop میتوان برای مدیریت حلقهها استفاده کرد).
متغیر forloop، در هر iteration (هر مرحله پیمایش حلقه) شامل اطلاعات مفیدی میباشد.
مفهوم iteration: به معنای پیمایش روی یک لیست یا مجموعه از آبجکت ها است(با استفاده از حلقه for)
ویژگیهای اصلی forloop عبارتند از:
forloop.counter: این ویژگی نشاندهندهی شمارهی فعلی از حلقه است. مقدار این ویژگی از ۱ شروع شده و با هر ایتریشن یکی افزایش مییابد.
forloop.counter0: این ویژگی مانند forloop.counter است، با این تفاوت که شمارش از صفر شروع میشود. این برای مواقعی که شمارهگذاری از صفر مهم است (مانند استفاده در آرایهها و لیستها که شمارهگذاری از صفر طبیعیتر است) مفید است.
forloop.revcounter: این ویژگی نشاندهندهی شمارهی فعلی از حلقه است، اما شمارش از آخر به اول انجام میشود. | مثلا اگر 4 آبجکت داشته باشیم از 4 شروع میشود و در مرحله یکی کم میشود.
forloop.revcounter0: مانند forloop.revcounter، اما شمارش از آخر به اول و با شمارهگذاری از صفر.
forloop.first: یک مقدار بولی است که نشان میدهد آیا این ایتریشن اولین ایتریشن حلقه است یا خیر.
forloop.last: یک مقدار بولی است که نشان میدهد آیا این ایتریشن آخرین ایتریشن حلقه است یا خیر.
forloop.parentloop: این ویژگی وجود دارد تنها اگر شما در یک حلقه دیگر (یک حلقه درونی) داخل حلقهی اصلی هستید، و اطلاعات حلقه اصلی را ارائه میدهد.
صفحه بندی (pagination)
زمانی که میخواهیم چندین داده مثل لیست محصولات و یا پست ها را در یک صفحه نمایش بدهیم ولی تعداد آنها زیاد است از صفحه بندی استفاده میکنیم.
صفحه بندی کمک میکنه تا بجای نمایش همه اطلاعات در یک صفحه و به صورت یکجا آنها را به صفحات کوچک تقسیم کنیم
برای صفحه بندی در این فصل از دو روش استفاده میکنیم، روش اول را در این concept و روش دوم را در concept بعدی توضیح میدهیم.
برای شروع لازمه در اسکریپت views.py، کلاس Paginator را ایمپورت کنیم.
app directory/views.py
from django.core.paginator import Paginator
خب بریم سراغ view که قراره صفحه بندی بشه. / ما میخواهیم post_list را که قراره تمام پست های منتشر شده را نمایش بدهد صفحه بندی بکنیم، پس به سراغ view لیست پست ها میرویم.
1- ابتدا یک QuerySet از داده های خود ایجاد میکنیم، ما تمام پست های منتشر شده را از دیتابیس دریافت میکنیم.
app directory/views.py
posts_list = Post.published.all()
2- یک نمونه از کلاس Paginator ایجاد میکنیم. / حالا داخل پرانتزهای آن دو مقدار را مشخص میکنیم، اولین مقدار: QuerySet هست که در مرحله قبل دریافت کردیم | دومین مقدار: یک عدد هست که مشخص میکنیم در هر صفحه چندتا از داده ها (در مثال ما Post) نمایش داده شود.
app directory/views.py
paginator = Paginator(posts_list, 10)
نکته: request.GET، یکسری اطلاعات را از صفحه دریافت میکنه و آنها را در قالب یک دیکشنری برمیگرداند.(پس request.GET یک دیکشنری میباشد)
نکته: متد get() مربوط به دیکشنری هاست که به عنوان آرگومان اول برایش مشخص میکنیم چه کلیدی را از دیکشنری برایمان دریافت کنه و برای آرگومان دوم که اختیاری است برایش یک مقداری را مشخص میکنیم، حالا اگه این کلید(مقدار آرگومان اول) وجود نداشت این مقدار پیشفرض را نشان میدهد.
3- فرض کنیم کاربر در صفحه 5 هست و روی صفحه 2 کلیک میکنه، خب حالا برای اینکه بفهمیم چه صفحه ای را قراره نمایش بدهیم(کاربر چه صفحه ای را میخواهد ببینه) از روش زیر استفاده میکنیم(در مثال ما صفحه 2 را برمیگرداند) | پیشفرض را صفحه 1 در نظر گرفته ایم:
app directory/views.py
page_number = request.GET.get('page', 1)
4- خب حالا بریم اطلاعات صفحه ای که کاربر در مرحله قبل انتخاب کرده را بدست بیاوریم(مثلا صفحه 2):
app directory/views.py
page_obj = paginator.page(page_number)
متغیر page_obj شامل: لیست اقلام (پست های صفحه فعلی) و اطلاعات مربوط به صفحه بندی (شماره صفحه فعلی، تعداد کل صفحات، تعداد کل پست ها و...)
در صفحه بندی قرار نیست کل پست ها را در یک صفحه نمایش بدیم پس page_obj که شامل پست های صفحه فعلی میباشد را به تمپلیت ارسال میکنیم.
متغیر page_obj متدهایی دارد در اینجا چند مورد را توضیح میدهیم:
1- عبارت page_obj.number: شماره صفحه فعلی که در آن قرار دارید. برای مثال، اگر در صفحه 3 باشید، page_obj.number برابر با 3 خواهد بود.
2- عبارت page_obj.has_next: یک بولین (True/False) که نشان میدهد آیا صفحه بعدی برای نمایش وجود دارد یا خیر.(بررسی میکند صفحه بعدی وجود داره یا نه)
3- عبارت page_obj.has_previous: یک بولین که نشان میدهد آیا صفحه قبلی برای نمایش وجود دارد یا خیر.(بررسی میکند صفحه قبلی وجود داره یا نه)
4- عبارت page_obj.next_page_number: شماره صفحه بعدی در صورت وجود، مشخص میکند که بعد از صفحه فعلی کدام صفحه نمایش داده خواهد شد.(شماره صفحه بعدی را برمیگرداند)
5- عبارت page_obj.previous_page_number: شماره صفحه قبلی در صورت وجود، مشخص میکند که قبل از صفحه فعلی کدام صفحه نمایش داده شده بود.(شماره صفحه قبلی را برمیگرداند)
خب بریم ساختار view را ببینیم:
app directory/views.py
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger def post_list(request): posts_list = Post.published.all() paginator = Paginator(posts_list, 10) page_number = request.GET.get('page', 1) try: page_obj = paginator.page(page_number) except EmptyPage: # تعداد صفحات را برمیگرداند که ما به عنوان شماره صفحه آخر از آن استفاده میکنیم page_obj = paginator.page(paginator.num_pages) except PageNotAnInteger: page_obj = paginator.page(1) context = { 'page_obj': page_obj, } return render(request, 'blog/list.html', context)
توی url صفحه مربوطه (یعنی url پست لیست ها، در بخش url مرورگر) عبارت (?page=page-number) نشان میدهد در صفحه چندم از صفحه بندی هستیم.
چنانچه کاربری در url، در بخش page-number شماره صفحه ای را وارد کند که وجود ندارد خطا می دهد، برای مدیریت استثنا از EmptyPage استفاده کرده و برایش مشخص میکنیم که صفحه آخر را نشان دهد.
در حالتی دیگر ممکن است کاربر در url، برای شماره صفحه رشته بنویسد این هم موجب خطا میشه برای رفع این استثنا از PageNotAnInteger استفاده میکنیم و این بار مشخص میکنیم که صفحه اول را نمایش دهد.
ایجاد لینک ها و ساختار صفحه بندی برای تمپلیت
میتوان ساختار مربوط به صفحه بندی را در تمپلیت post_list بنویسیم ولی چون ممکن است در بخش های مختلف وبسایت به صفحه بندی نیاز داشته باشیم، تمپلیت pagination را جداگانه میسازیم و هر جا نیاز داشتیم با کمک {% include %} از آن استفاده میکنیم.
اول یک ساختار ساده جهت درک بخش های مهم نوشته میشه و در ادامه ساختار با جزئیات بیشتر ارائه میشود.
متغیر page همان متغیر page_obj خواهد بود که در ادامه داخل {% include %} تعریفش میکنیم.
templates/partials/pagination.html
<div class="pagination"> <!-- بررسی میکند که قبل از صفحه فعلی صفحه ای وجود داره --> {% if page.has_previous %} <!-- با کلیک روی لینک به آدرس صفحه قبل میرود --> <a href="?page={{ page.previous_page_number }}">previous</a> {% endif %} <!-- نشان میدهد در صفحه چندم از کل صفحات هستیم --> page {{ page.number }} of {{ page.paginator.num_pages }} <!-- بررسی میکند که بعد از صفحه فعلی صفحه ای وجود داره --> {% if page.has_next %} <!-- با کلیک روی لینک به آدرس صفحه بعدی میرود --> <a href="?page={{ page.next_page_number }}">next</a> {% endif %} </div>
ساختار با جزئیات بیشتر:
templates/partials/pagination.html
<div class="pagination"> {% if page.has_previous %} <a href="?page={{ page.previous_page_number }}">« قبلی</a> {% if page.number > 3 %} <a href="?page=1">1</a> {% if page.number > 4 %} <span>...</span> {% endif %} {% endif %} {% endif %} {% for num in page.paginator.page_range %} {% if page.number == num %} <a href="?page={{ num }}">{{ num }}</a> {% elif num > page.number|add:'-3' and num < page.number|add:'3' %} <a href="?page={{ num }}">{{ num }}</a> {% endif %} {% endfor %} {% if page.has_next %} {% if page.number < page.paginator.num_pages|add:'-3' %} <span>...</span> <a href="?page={{ page.paginator.num_pages }}">{{ page.paginator.num_pages }}</a> {% elif page.number < page.paginator.num_pages|add:'-2' %} <a href="?page={{ page.paginator.num_pages }}">{{ page.paginator.num_pages }}</a> {% endif %} <a href="?page={{ page.next_page_number }}">بعدی »</a> {% endif %} </div>
عبارت page_range: بازه صفحات را نشان میدهد مثلا اگر 7 صفحه داشته باشیم، میشه (1, 7).
این بار ما "page_obj" را که پست های صفحه را در خود دارد بجای "posts" به تمپلیت "post_list" ارسال کرده ایم پس هرجا "posts" داریم باید با "page_obj" جایگزین کنیم.
خب حالا وقتشه بریم "pagination" را به تمپلیت "post_list" اضافه کنیم | در انتهای کد قابل مشاهده است:
templates/blog/post_list.html
{% extends 'parent/base.html' %} {% load static %} {% block title %} Post Lists {% endblock %} {% block head %} <link rel="stylesheet" href="{% static 'css/style.css' %}"> {% endblock %} {% block content %} <div class="container"> {% for post in page_obj %} <div class="post"> <a href="{% url blog:post_detil post.id %}">{{ post.title }}</a> <p>{{ post.description|truncatewords:5 }}</p> </div> {% endfor %} </div> {% include 'partials/pagination.html' with page=page_obj %} {% endblock %}
در کد بالا، داخل تمپلیت تگ {% include %}، به کمک "with" متغیر "page" را ایجاد کرده و آنرا برابر با "page_obj" قرار داده ایم. | از این متغیر "page" داخل همین تمپلیت "pagination" استفاده شده.
View های مبتنی بر کلاس (Class-based views)
ساختار "view" هایی که ایجاد میکنیم، دو حالت دارد: 1- "view" بر مبنای تابع(function) هستند 2- "view" بر مبنای کلاس
خود جنگو، "view" های مبتنی بر کلاس به صورت built-in دارد. | این "view" ها باید ایمپورت بشوند.
ما در اینجا از "ListView" و "DetailView" استفاده میکنیم.
برای "view" لیست پست ها این بار از "ListView" استفاده میکنیم:
app directory/views.py
from django.view.generic import ListView
یک کلاس ایجاد میکنیم که از "ListView" ارث بری میکنه. | حالا اتریبیوت ها و متد های مورد نیاز را override میکنیم. | از اتریبیوت کلاس استفاده میکنیم
1- با استفاده از اتریبیوت "model" و مشخص کردن مدل مربوطه تمام پست ها را شامل میشود ولی چون ما فقط پست های منتشر شده را میخواهیم از اتریبیوت "queryset" استفاده کرده و پست های منتشر شده را انتخاب میکنیم.
2- برای مشخص کردن تمپلیت مربوطه از اتریبیوت "template_name" باید استفاده کنیم.
3- در concept قبلی در مورد صفحه بندی صحبت کردیم اینجا روش دوم را بیان میکنیم، اینجا فقط کافیه مشخص کنیم در هر صفحه چند پست نمایش داده شود و این کار را با اتریبیوت "paginate_by" انجام میدهیم. | نکته مهم اینه که حتما از "page_obj" در تمپلیت تگ {% include %} برای ایجاد متغیر "page" استفاده کرده باشیم.
4- وقتی از تابع برای view استفاده می کنیم به کمک context لیست پست ها را به تمپلیت ارسال میکنیم حالا برای این کار از اتریبیوت "context_object_name" استفاده کرده و یک اسم برایش مشخص میکنیم حالا میتوانیم با آن نام در تمپلیت به تمام پست های منتشر شده که در مرحله "1" مشخص کرده ایم دسترسی داشته باشیم.
app directory/views.py
from django.view.generic import ListView, DetailView class PostListView(ListView): # تمام پست های منتشر شده را انتخاب میکنیم queryset = Post.published.all() # مسیر فایل تمپلیت را مشخص میکنیم template_name = 'blog/post_list.html' # مشخص میکنیم در هر صفحه چند پست نمایش داده شود paginate_by = 10 # یه متغیر مشخص میکنیم که با استفاده از آن در تمپلیت به تمام پست های منتشر شده دسترسی داشته باشیم context_object_name = 'posts' class PostDetailView(DetailView): model = Post template_name = 'blog/post_detail.html'
چون از کلاس بجای تابع استفاده کرده ایم باید در اسکریپت urls.py تغییراتی ایجاد کنیم:
app directory/urls.py
# _________________________ post-list _________________________ # 1- url for function view path('posts/', views.post_list, name='post_list'), # ------------------------------------------------------------- # 2- url for class view path('posts/', views.PostListView.as_view(), name='post_list'), # _________________________ post-detail _________________________ # 1- url for function view path('posts/<int:post_id>/', views.post_detail, name='post_detail'), # ------------------------------------------------------------- # 2- url for class view path('posts/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
در url جزئیات پست که از ساختار کلاس استفاده شده باید از متغیر pk ویا slug استفاده کنیم.
آشنایی با ساختار فرم ها
زمانی که لازم داریم یکسری اطلاعات را از کاربر دریافت کنیم این کار را به کمک فرم ها انجام میدهیم.
در برخی از فرم ها، اطلاعات دریافتی توی دیتابیس ذخیره میشوند مثل اطلاعات پروفایل فرم های دیگر لازم نداریم که اطلاعات در دیتابیس ذخیره بشوند مثل فرم مربوط به تماس با ما چون اطلاعات به ایمیل ما ارسال میشوند.
فیلدهای فرم میتوانند حالات مختلفی داشته باشند، متنی باشند، عددی باشند، انتخابی باشند (مثل انتخاب شهر)، تاریخ را دریافت کنند، تصویر و یا فایل دریافت کنند و...
درخواست(request)، میتواند متد post ویا get باشد. / البته متد های دیگری هم وجود دارد ولی این دو پرکاربرد ترند.
وقتی اطلاعات توسط کاربر با دکمه submit ارسال شد، حالا نوبت به اعتبارسنجی فیلدهای فرم ارسالی میرسد. | این اعتبارسنجی را در اسکریپت forms.py انجام میدهیم.
اگر اطلاعات فیلدهای ارسالی مشکلی داشتند خطا نمایش داده و محتوای آن فیلدها را پاک میکند.
اگر هم اطلاعات ارسالی معتبر بودند به یک صفحه ای که مشخص میکنیم redirect میشه.
به طور کلی، متدهای POST و GET دو روش ارسال اطلاعات از سمت کاربر به سرور در HTTP هستند که در وب برای انتقال اطلاعات بین کلاینت (مرورگر وب) و سرور استفاده میشوند. این دو متد دارای کاربردها و ویژگیهای خاص خود هستند:
1. متد GET:
- ویژگیها:
- اطلاعات به صورت پارامترها در URL ارسال میشوند.
- اطلاعات درخواست به صورت قابل خواندن در URL قابل مشاهده هستند.
- محدودیتی برای طول درخواست URL وجود دارد (مرورگرها ممکن است حدود ۲۰۰۰ کاراکتر داشته باشند).
-
مناسب برای درخواستهایی که اطلاعات خواندنی (مانند جستجوی وبسایت) یا کم حجم هستند.
-
کاربردها:
- درخواست دادن اطلاعات از سرور.
- جستجوها، فیلترینگ و مرتبسازی.
- ارسال اطلاعات کم حجم که احتمال تغییر آنها ندارد (مثلاً شناسه محصول برای نمایش جزئیات آن).
2. متد POST:
- ویژگیها:
- اطلاعات به صورت body درخواست ارسال میشوند (پنهان از URL).
- اطلاعات درخواست به صورت خصوصیتر و غیر قابل مشاهده ارسال میشوند.
- محدودیتی در طول ارسال اطلاعات وجود ندارد.
-
مناسب برای ارسال اطلاعات حساس (مانند نام کاربر، رمز عبور، اطلاعات کارت اعتباری) یا اطلاعات بزرگ و تغییر پذیر.
-
کاربردها:
- ایجاد، ویرایش یا حذف اطلاعات در سرور (عملیات CRUD).
- ارسال اطلاعات فرم (مثلاً ثبتنام، ارسال پیام تماس با ما).
- ارسال فایلها (مثلاً آپلود تصاویر یا فایلهای دیگر).
مواقع استفاده:
- GET:
- استفاده از GET مناسب است زمانی که دادهها خواندنی و کم حجم هستند.
- برای درخواست دادن اطلاعات و نمایش آن به کاربران.
-
برای جستجوها و فیلترینگ.
-
POST:
- استفاده از POST مناسب است زمانی که دادهها حساس یا بزرگ و یا نیاز به ارسال مکرر دارند.
- برای ایجاد، ویرایش یا حذف اطلاعات در سرور.
- برای ارسال اطلاعات فرم و فایلها.
- برای ارسال اطلاعاتی که نیاز به تغییر در آنها دارید و از جمله اطلاعات حساس استفاده می شود
آشنایی با Form (ساخت فرم تیکت)
برای استفاده از فرم ها جنگو، یک فریمورک built-in برای فرم ها داره. | دو کلاس پایه، Form و ModelForm داریم که برای ساخت فرم ها از آنها استفاده میکنیم(ارث بری میکنیم).
لازمه اول اسکریپت form را ایجاد کنیم، پس در دایرکتوری اپ(app) خود forms.py را ایجاد میکنیم تا فرم هایمان را آنجا ایجاد کنی.
فرم ها را در قالب کلاس تعریف میکنیم، که هرکدام از این کلاس ها از forms.Form ویا forms.ModelForm ارث بری میکنند.(باتوجه به نیاز خود)
خب میخواهیم برای تیکت وبسایت فرم ایجاد کنیم، ولی قبلش باید یک model برای تیکت ایجاد کنیم تا بتوانیم اطلاعات را توی دیتابیس ذخیره کنیم.
app directory/models.py
class Ticket(models.Model): full_name = models.CharField(max_length=250) subject = models.CharField(max_length=150) message = models.TextField() email = models.EmailField() phone = models.CharField(max_length=11) class Meta: ordering = ['-full_name'] indexes = [models.Index(fields=['-full_name'])] def __str__(self): return self.full_name
شماره تلفن و کدملی را از نوع CharField انتخاب میکنیم، چون قرار نیست روی آنها محاسباتی انجام بشه که عددی باشند.
دستورات makemigrations و migrate فراموش نشه!
برای نمایش تیکت در پنل ادمین، در اسکریپت admin.py باید آنرا مشخص کنیم تا نشان داده شود.
app directory/admin.py
@admin.register(Ticket) class TicketAdmin(admin.ModelAdmin): list_display = ['full_name', 'subject', "email"]
باید در اسکریپت فرم، forms را ایمپورت کنیم:
app directory/forms.py
from django import forms
توی بدنه کلاس ها به کمک class attributes فیلدهای فرم خود را ایجاد میکنیم. | ساختار ایجاد این فیلدها مشابه با ایجاد فیلد در models.py میباشد.
app directory/forms.py
title = forms.CharField()
توی فرم ها برخلاف مدل ها TextField نداریم، بجای آن از CharField استفاده کرده و برایش از آرگومان widget استفاده میکنیم.
app directory/forms.py
message = forms.CharField(widget=forms.Textarea)
اگر بخواهیم فیلدی را اجباری کنیم که کاربر حتما باید آنرا پر کند، برای آن فیلد از آرگومان required=True استفاده میکنیم.
فرم ها را همنام با مدل ها قرار ندهید چون وقتی آنها را توی view ایمپورت کنیم به مشکل و تداخل میخوریم. | بهتره نام ها به یکدیگر مربوط باشند مثله مدل: Ticket و فرم: TicketForm.
موضوع(subject) را در فرم تیکت به حالت انتخابی ایجاد میکنیم. | فیلد آنرا هم برخلاف مدل ها باید ChoiceField مشخص کنیم.
app directory/forms.py
from django import forms from .models import * class TicketForm(forms.Form): SUBJECT_CHOICES = ( ('پیشنهاد', 'پیشنهاد'), ('گزارش', 'گزارش'), ('انتقاد', 'انتقاد'), ) subject = forms.ChoiceField(choices=SUBJECT_CHOICES) full_name = forms.CharField(max_length=250) message = forms.CharField(widget=forms.Textarea, required=True) email = forms.EmailField() phone = forms.CharField(max_length=11)
ایجاد url برای فرم تیکت:
خب حالا باید برای این فرم تیکت، یک url ایجاد کنیم، چرا؟ برای اینکه اطلاعات فرم به آن url فرستاده بشوند تا بتوانیم در view از آنها استفاده کنیم.
app directory/urls.py
path('ticket/', views.ticket, name='ticket'),
بریم یک view برای ticket ایجاد کنیم:
خب باید فرم ها را در views.py ایمپورت کنیم.
app directory/views.py
from .forms import *
وقتی در یک صفحه یک فرم خالی را نمایش می دهیم ، متد get هست و زمانی که آن فرم را پر کرده و روی دکمه ارسال کلیک کنیم متد post خواهد بود.
وقتی متد POST باشد، یعنی کاربر روی دکمه ارسال کلیک کرده و اطلاعات ارسال شده اند.
app directory/views.py
if request.method == 'POST': # ...
وقتی اطلاعات به url ارسال شدند با استفاده از request.POST آنها را دریافت میکنیم. | اطلاعات در قالب دیکشنری ذخیره میشوند. کلیدها، اسم فیلدها هستند و مقادیر، محتوای فیلد هستند که توسط کاربر وارد شده اند.
app directory/views.py
request.POST
در واقع request.POST یک QueryDict میباشد. | بریم یک نمونه از آن را ببینیم:
Terminal
<QueryDict: {'csrfmiddlewaretoken': [token-text], 'name': ['Reza'], 'message': ['python programming language.']}>
برای مدیریت و بررسی(اعتبارسنجی) اطلاعات دریافتی، آنها را به فرم مربوطه میفرستیم؛ برای این کار یک نمونه از فرم ایجاد کرده و request.POST را برایش مشخص میکنیم.
app directory/views.py
form = TicketForm(request.POST)
حالا با استفاده از متد isvalid() بررسی میکنیم که آیا اطلاعات ارسالی معتبر بودند یا خیر.
app directory/views.py
if form.isvalid(): cd = form.cleaned_data
عبارت form.cleaned_data یک دیکشنری هست که شامل فیلدها و مقادیر آن فیلدها در فرم مربوطه میباشد.
متغیر cd یک دیکشنری میباشد پس برای دسترسی به مقدار یک کلید:
cd[key] cd.get(key)
وقتی اطلاعات valid نباشند شرط form.isvalid() برقرار نخواهد بود و بدنه آن اجرا نمیشه، ولی همچنان مقادیر وارد شده توسط کاربر توی فیلدها هستند؛ دلیلش این هست که چون ری دکمه ارسال کلیک شده یعنی متد، POST بوده و اطلاعات فرم دریافت شده اند(form = TicketForm(request.POST)) و این فرم به تمپلیت ارسال میشه در نتیجه فرم خالی نیست.
ذخیره اطلاعات در دیتابیس
برای اینکه اطلاعاتی را در دیتابیس ذخیره کنیم، برای مدل مدنظر از دستورات ORM استفاده کرده و یک آبجکت جدید از مدل خود ایجاد میکنم سپس فیلدهای آن مدل را باتوجه به اطلاعاتی که داریم پر میکنیم.
در اینجا اطلاعاتی را که کاربر وارد کرده ، از دیکشنری cd دریافت کرده و آن مقادیر را برای فیلد مربوطه در مدل مشخص میکنیم.
داخل بدنه شرط if form.isvalid(): از مدل مدنظر آبجکت ایجاد میکنیم.
app directory/views.py
# Structure to create data in the database ModelName.objects.create() # ________________________________________ Ticket.objects.create( subject=cd['subject'] message=cd['message'], full_name=cd['full_name'], email=cd['email'], phone=cd['phone'], )
پس از ذخیره اطلاعات در دیتابیس با استفاده از shortcut یا همان تابع redirect مشخص میکنیم که به چه صفحه ای منتقل شود(وقتی کاربر اطلاعات معتبر وارد کرد پس از ثبت به صفحه ای که مشخص میکنیم منتقل میشود). | باید ایمپورت شود، کنار render آنرا ایمپورت میکنیم.
آدرس صفحه مدنظر را به صورت app_name:name-of-path مینویسیم.
app directory/views.py
from django.shortcuts import render, redirect # structure of redirect function redirect('Blog:index')
برای نمایش فرم خالی (زمانی که کاربر فرم را پر نکرده است)، برای شرطی که بررسی میکند متد، POST هست یا نه؛ else نوشته و در بدنه آن یک نمونه از فرم بدون اینکه چیزی دریافت کند مینویسیم. | وقتی else اجرا میشه یعنی متد، get میباشد و فرم خالی نمایش داده میشود.
form = TicketForm()
برای تیکت وبسایت، یک تمپلیت ایجاد کرده و آنرا به view تیکت متصل میکنیم.(templates/forms/ticket.html)
باید form را به تمپلیت ارسال کنیم.(با استفاده از context)
خب بریم کل این ساختارها را یکجا در کد خود ببینیم:
app directory/views.py
def ticket(request): if request.method == 'POST': # instance of form form = TicketForm(request.POST) if form.is_valid(): cd = form.cleaned_data # object of model Ticket.objects.create( subject=cd['subject'] message=cd['message'], full_name=cd['full_name'], email=cd['email'], phone=cd['phone'], ) return redirect('Blog:index') else: # instance of form form = TicketForm() return render(request, 'forms/ticket.html', {'form': form})
ایجاد فرم در تمپلیت (تیکت)
از تمپلیت پایه ارث بری کرده و ساختار تمپلیت را ایجاد میکنیم.
در تمپلیت برای ایجاد فرم دو حالت داریم: 1- اتوماتیک فرم را از روی فیلدهای کلاس فرم در forms.py ایجاد کنیم. 2- با ساختار html آنها را ایجاد کنیم(به صورت دستی)
در تمپلیت یک تگ form ایجاد میکنیم.
نکته: باید برای تگ form یک method مشخص کنیم.
templates/forms/ticket.html
<form method="post"> ... </form>
چنانچه متد فرم post باشد؛در بدنه تگ form باید از تمپلیت تگ {% csrf_token %} استفاده کنیم.
تمپلیت تگ csrf_token از باگ امنیتی جلوگیری میکند. | یک توکن ایجاد میکند و با آن توکن تشخیص میدهد کاربری که دارد این درخواست را ارسال میکنداز جانب همین کاربر هست یا نه!
وقتی از تمپلیت تگ csrf_token استفاده میکنیم، یک تگ input از نوع hidden ایجاد میشود که value آن توکنی هست که برای بررسی ایجاد میکند.
نوشتن فرم به صورت اتوماتیک
از فرمی که از طرف view به تمپلیت ارسال کردیم استفاده کرده و با استفاده از متدهایش فرم را ایجاد میکنیم.
templates/forms/ticket.html
<form method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="ارسال"> </form>
از کلاس فرم، اتوماتیک فیلدهای فرم را میخواند، حالا هر یک از آن فیلدها را با تگ p ایجاد میکند.
برای ارسال این داده ها از (تگ input با تایپ submit) استفاده میکنیم.
انواع متد، برای ایجاد فرم به حالت اتوماتیک:
form rendering options
form.as_p => <p>
<input type="">
</p>form.as_div => <div>
<input type="">
</div>form.as_table => <tr>
<input type="">
</tr>form.as_ul => <li>
<input type="">
</li>
در حالت اتوماتیک از روی اسم فیلدهای کلاس فرم مدنظر، label ها را ایجاد میکند چنانچه بخواهیم اسم این label ها را شخصی سازی کنیم در اسکریپت forms.py، برای هر فیلد از آرگومان label استفاده کرده و برایش اسم دلخواه خود را مشخص میکنیم.
app directory/forms.py
class TicketForm(forms.Form): SUBJECT_CHOICES = ( ('پیشنهاد', 'پیشنهاد'), ('گزارش', 'گزارش'), ('انتقاد', 'انتقاد'), ) subject = forms.ChoiceField(choices=SUBJECT_CHOICES) full_name = forms.CharField(max_length=250, label="نام و نام خانوادگی") message = forms.CharField(widget=forms.Textarea, required=True, label="متن پیام") email = forms.EmailField(label="ایمیل") phone = forms.CharField(max_length=11, label="شماره تماس")
کار با تمپلیت فرم ها
برای اینکه این حالت اتوماتیک ایجاد فرم، را یکم شخصی سازی کنیم روی form حلقه زده و هر فیلد را نمایش میدهیم:
templates/forms/ticket.html
<form method="post"> {% csrf_token %} {% for field in form %} {{ field.label_tag }} {{ field }} {% endfor %} <input type="submit" value="ارسال"> </form>
چون ایجاد فرم در حالت اتومات را شخصی سازی کرده ایم، دیگه ارورها را نمایش نمیده برای نمایش ارورهای هرفیلد، عبارت {{ field.errors }} را به حلقه اضافه میکنیم.
templates/forms/ticket.html
<form method="post"> {% csrf_token %} {% for field in form %} {{ field.label_tag }} {{ field }} {{ field.errors }} {% endfor %} <input type="submit" value="ارسال"> </form>
خب حالا فیلدهای form را به صورت دستی ایجاد میکنیم:
برای نوشتن input از ساختار زیر استفاده میکنیم و چنانچه خواستیم میتوانیم اتریبیوت های دیگری به آن اضافه کنیم.
اتریبیوت required: برای اجباری کردن فیلد استفاده میشه.
با اتریبیوت maxlength میتوان حداکثر تعداد کاراکتر برای آن فیلد مشخص کرد.
<input type="" name="<field_name>">
باتوجه به نوع فیلد مدنظر، یک type برای تگ input مشخص میکنیم. | مثلا برای فیلد CharField از input با تایپ text استفاده میکنیم.
نکته مهم این هست که حتما باید برای اینپوت ها اتریبیوت name مشخص کنیم، و مقداری که برای اتریبیوت name مشخص میکنیم باید با اسم فیلد مدنظر در کلاس forms.py تطابق داشته باشه.(همان اسم)
بریم توی مثال ببینیم:
app directory/forms.py
class TicketForm(forms.Form): # ... full_name = forms.CharField(max_length=250) # ...
templates/forms/ticket.html
<form method="post"> {% csrf_token %} <input type="text" name="full_name"> <input type="submit" value="ارسال"> </form>
فیلد full_name از نوع CharField هست پس اینپوت را از نوع text ایجاد میکنیم، چون اسم فیلد full_name هست برای اتریبیوت name در تگ input، اسم آن فیلد (یعنی full_name) را وارد میکنیم.
انواع فیلد در html:
ساختار فرم به صورت دستی
templates/forms/ticket.html
<form method="post" class="form"> {% csrf_token %} <select name="subject"> <option value="گزارش<"گزارش</option> <option value="انتقاد<"انتقاد</option> <option value="پیشنهاد<"پیشنهاد</option> </select> <input type="text" name="name" placeholder="your name"> <textarea name="message" cols="20" rows="5" placeholder="your text...."></textarea> <input type="email" name="email" placeholder="email"> <input type="text" name="phone" placeholder="phone number"> <input type="submit" value="send-ticket"> </form>
اعتبار سنجی فرم ها و نمایش خطا ها
وقتی ساختار فرم را به صورت دستی و با استفاده از تگ input ایجاد میکنیم، وقتی فیلدها معتبر نباشند ارورها را نمایش نمیده و و تمام فیلدها را هم پاک میکنه در نتیجه، این ویژگی ها را باید دستی اضافه کنیم.
نمایش خطا ها
برای نمایش ارورهای هر فیلد زیر یا کنار همان فیلد(بعد از هر input از تگ span با ساختار زیر استفاده میکنیم):
templates/forms/ticket.html
<span>{{ form.field-name.errors }}</span>
templates/forms/ticket.html
<form method="post"> {% csrf_token %} <input type="text" name="full_name"> <span>{{ form.full_name.errors }}</span> <input type="submit" value="ارسال"> </form>
نمایش همه ارورها به صورت یکجا
بیرون از form ساختار زیر را پیاده سازی میکنیم:
templates/forms/ticket.html
{% if form.errors %} {% for field in form %} {% if field.errors %} {% for error in field.errors %} {{ field.label }}: {{ error }} {% endfor %} {% endif %} {% endfor %} {% endif %}
بعضی وقتا ممکنه ارورهایی داشته باشیم که مربوط به فیلدها نیستند:
templates/forms/ticket.html
{% if form.non_field_errors %} {{ form.non_field_errors }} {% endif %}
زمانی که فیلد و یا فیلدهایی valid نیستند برای اینکه فیلدها اطلاعاتشون پاک نشه به صورت زیر عمل میکنیم:
{% if form.field-name.value %}{{ form.field-name.value }}{% endif %}
templates/forms/ticket.html
<form method="post"> {% csrf_token %} <input type="text" name="full_name" {% if form.full_name.value %}value="{{ form.ful_name.value }}"{% endif %}> <textarea name="message"> {% if form.message.value %}{{ form.message.value }}{% endif %} </textarea> <input type="submit" value="ارسال"> </form>
اعتبارسنجی شخصی سازی شده
اعتبارسنجی فرم ها را در فایل forms.py انجام می دهیم. | در کلاس فرم مربوطه اعتبارسنجی را انجام میدهیم.
برای اعتبارسنجی فیلدها دو حالت وجود دارد:
اعتبارسنجی را با یکی از متدهای clean_<field_name>() و یا clean() انجام میدهیم.
حالت اول
app directory/forms.py
# structure: def clean_FieldName(self): # ... # ________________________________ def clean_phone(self): phone = self.cleaned_data["phone"] if phone: if not phone.isdigit(): raise forms.ValidationError("phone number is invalid") return phone
متدهای clean_<field_name>() برای اعتبارسنجی فیلدهای خاص در فرمها استفاده میشوند(یعنی برای اعتبارسنجی هر فیلد از یک متد clean_<field_name>() استفاده میکنیم | هرفیلد جداگانه اعتبارسنجی میشود).
نکته: <field-name> باید با نام فیلد در فرم مطابقت داشته باشد.
1- حالا مقدار آن فیلد را از دیکشنری cleaned_data دریافت کرده و در یک متغیر ذخیره میکنیم.
2- اکنون بررسی میکنیم که فیلد مورد نظر مقدار دارد یا نه؟!(اگر خالی باشه شرط False خواهد بود.) | برای اعتبارسنجی باید فیلد مقدار داشته باشد مگرنه ارور میدهد.
3- حالا وقتشه اعتبارسنجی های مدنظر را پیاده سازی کنیم.
در مثال بالا برای شماره تلفن اعتبارسنجی میکنیم که فقط عدد باشد، اگر فقط عدد نباشد مثلا حروف هم داشته باشد یک پیغام خطا را نمایش میدهیم. | و چنانچه معتبر باشد آن را return میکنیم.
نکته مهم این هست که حتما برای زمانی که مقدار فیلد معتبر میباشد باید آن مقدار را برگردانیم(return کنیم)
حالت دوم
app directory/forms.py
# structure: def clean(self): cleaned_data = super().clean() # ... return cleaned_data # ________________________________ def clean(self): cleaned_data = super().clean() phone = cleaned_data.get('phone') if phone: if not phone.isdigit(): raise forms.ValidationError("phone number is invalid") return cleaned_data # _______________ تفاوت در ایجاد پیام خطا _______________ def clean(self): cleaned_data = super().clean() phone = cleaned_data.get('phone') if phone: if not phone.isdigit(): msg = "phone number is invalid." self.add_error("phone", msg) return cleaned_data
متد clean() برای اعتبارسنجی دادههای کل فرم استفاده میشود.
در این متد، اعتبارسنجیهای بین فیلدی (برای اعتبارسنجی چند فیلد همزمان) انجام میشود. البته میتوان در این متد فقط یک فیلد را نیز اعتبارسنجی کرد، اما اعتبارسنجی یک فیلد بهتر است بهطور جداگانه در متدهای clean_<field_name>() انجام شود.
این متد به شما امکان میدهد که اعتبارسنجیهای بین فیلدی را انجام دهید؛ یعنی اعتبارسنجیهایی که به بیش از یک فیلد وابسته هستند. مثلا اعتبارسنجی password و repeat_password
آشنایی با forms.ModelForm (ساخت فرم کامنت-بخش اول و دوم)
میخواهیم برای پست های خود قابلیت کامنت گذاری ایجاد کنیم.
برای اینکه کامنت ها توی دیتابیس ذخیره بشوند لازمه برای کامنت ها یک مدل ایجاد کنیم.
app directory/models.py
class Comment(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') name = models.CharField(max_length=250) letter = models.TextField() created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) active = models.BooleanField(default=False) class Meta: ordering = ['-created'] indexes = [models.Index(fields=['-created'])] def __str__(self): return f'{self.name}: {self.post}'
کامنت باید به پست متصل شود تا مشخص شود هر پست چه کامنت هایی دارد یا یک کامنت متعلق به چه پستی میباشد.
یک پست میتواند چندین کامنت داشته باشد ولی هر کامنت فقط به یک پست تعلق داره پس فیلد آن از نوع Many To One خواهد بود.(ForeignKey)
یادآوری:
# تمام کامنت های یک پست
post.comments.all()
# تشخیص پست برای یک کامنت
comment.post
دستور makemigrations و migrate فراموش نشه!
نمایش کامنت ها در پنل ادمین
app directory/admin.py
@admin.register(Comment) class CommentAdmin(admin.ModelAdmin): list_display = ['name', 'post', 'created', 'active'] list_filter = ['created', 'active'] search_fields = ['name', 'letter'] list_editable = ['active']
ایجاد فرم کامنت در forms.py
این بار فرم خود را از ModelForm ایجاد میکنیم.
وقتی از ModelForm استفاده میکنیم، دیگه فیلدها را ما ایجاد نمیکنیم بلکه اتوماتیک این کار را از روی فیلدهای مدل انجام میدهد.
داخل بدنه کلاس ، یک کلاس Meta ایجاد میکنیم، حالا در بدنه کلاس Meta مشخص میکنیم که فیلدها را از روی چه مدلی ایجاد کند.
app directory/forms.py
class CommentForm(forms.ModelForm): class Meta: model = Comment
حالا با استفاده از fields ویا exclude، مشخص میکنیم فرم را از روی کدام فیلدهای مدل ایجاد کند.
متغیر fields: توی یک لیست برایش مشخص میکنیم، که فرم را از روی کدام فیلدهای مدل ایجاد کند.
متغیر exclude: برایش یک لیست مشخص میکنیم و اسم فیلدهایی که نمیخواهیم توی فرم باشند را توی آن می نویسیم. | هر فیلدی که توی لیست مشخص میکنیم یعنی همه فیلدهای مدل بجز این فیلدها را انتخاب و از فیلدهای انتخابی فرم را ایجاد کن.
explanation and example
# select all fields fields = '__all__' # ------------------------------------------------------- # select name and letter fields fields = ['name', 'letter'] # ------------------------------------------------------- # all fields except 'post', 'created', 'updated', 'active' exclude = ['post', 'created', 'updated', 'active']
کد کامل فرم کامنت
app directory/forms.py
class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ['name', 'letter']
ایجاد view برای کامنت
میتوانیم دریافت اطلاعات کامنت و... را در view جزئیات پست(post_detail) را انجام دهیم ولی جهت موارد آموزشی جدید آنرا جداگانه ایجاد میکنیم.
ما میتوانیم یک view را به یک متد(get, post) محدود کنیم با استفاده از دکوراتورهای جنگو. | باید ایمپورت بشوند.
app directory/views.py
from django.views.decorators.http import require_POST @require_POST def post_comment(request, post_id): # ...
پستی که برایش کامنت گذاشته میشود را باید مشخص کنیم.
app directory/views.py
from django.shortcuts import render, get_object_or_404 @require_POST def post_comment(request, post_id): post = get_object_or_404(Post, id=post_id, status=Post.Status.Published)
زمانی که فرم خود را از ModelForm ایجاد کرده ایم، برای ذخیره کردن اطلاعات توی دیتابیس ، چون کلاس فرم به مدل متصل شده است دیگه نیاز نیست که از مدل، آبجکت ایجاد کنیم بلکه فقط کافیست برای فرم از متد save() استفاده کنیم تا اطلاعات توی دیتابیس ذخیره بشوند.
متد save() ابتدا یک آبجکت از آن مدل ایجاد میکند و سپس آنرا در دیتابیس ذخیره میکند، ولی اگه از آرگومان commit=False استفاده کنیم آبجکت را ایجاد میکنه اما آنرا در دیتابیس ذخیره نمیکنه بنابراین ما میتوانیم تغییراتی در آبجکت ایجاد کرده و سپس آنرا در دیتابیس ذخیره کنیم.
وقتی میخواهیم اطلاعات فیلدی را تغییر بدیم یا اطلاعاتی را دستی به دیتابیس اضافه کنیم برای متد save() از آرگومان commit=False استفاده کرده و تغییرات را اعمال میکنیم و در پایان با متد save() اطلاعات را در دیتابیس ذخیره میکنیم | مثلا توی کامنت فیلد پست باید اتوماتیک مشخص بشه پس برایش تعیین میکنیم که فیلد پست را همین پست که در بالا مشخص کردیم انتخاب کن.
کد کامل view کامنت
app directory/views.py
@require_POST def post_comment(request, post_id): post = get_object_or_404(Post, id=post_id, status=Post.Status.Published) comment = None form = CommentForm(request.POST) if form.is_valid(): comment = form.save(commit=False) comment.post = post comment.save() context = { 'comment': comment, 'post': post, 'form': form } return render(request, "forms/comment.html", context=context)
برای کامنت یک تمپلیت در دایرکتوری forms ایجاد میکنیم. | در آن پیغام ثبت موفقیت آمیز کامنت ، خطاهای فیلدها و اصلاح فیلدهای فرم در صورت وجود خطا را خواهیم داشت.
templates/forms/comment.html
{% extends 'parent/base.html' %} {% block title %} post comment {% endblock %} {% block content %} {% if comment %} your comment has been sent. <br> <a href="{{ post.get_absolute_url }}">back to post</a> {% else %} <form method="post"> {% csrf_token %} <div style="width: 250px"> <input type="text" id="name" placeholder="name" name="name"> <br> <textarea name="letter" id="text" cols="30" rows="10" placeholder="your comment..."></textarea> </div> <input type="submit" value="send"> </form> {% if form.errors %} {% for field in form %} {% if field.errors %} {% for error in field.errors %} {{ field.label }}: {{ error }} {% endfor %} {% endif %} {% endfor %} {% endif %} {% endif %} {% endblock %}
برای view جزئیات پست(post_detail) از تابع بجای class based view استفاده میکنیم. | حواسمون باشه url را هم تغییر بدیم!!!
app directory/urls.py
path('posts/<int:post_id>/', views.post_detail, name='post_detail'),
برای کامنت، یک url ایجاد میکنیم.
app directory/urls.py
path('posts/<int:post_id>/comment/', views.post_comment, name='post_comment'),
در تمپلیت detail_post هم فرم کامنت را جهت ثبت کامنت داریم ، ولی یه نکته داره، آن هم این است که افزون بر اتریبیوت method که باید نوشته شود، باید از اتریبیوت دیگری بنام action هم استفاده کنیم.
وقتی از اتریبیوت action استفاده نمیکنیم اطلاعات فرم به url همان صفحه ارسال میشه ولی اگه بخواهیم اطلاعات به url دیگری ارسال بشه تا در view آن url روی اطلاعات فرم کار کنیم باید از اتریبیوت action استفاده کنیم. برای action آدرس آن url را مشخص میکنیم.
templates/blog/post_detail.html
<form action="{% url 'Blog:post_comment' post.id %}" method="post"> ... </form>
templates/blog/post_detail.html
{% extends 'parent/base.html' %} {% load blog_tags %} {% block title %} post detail {% endblock %} {% block content %} <h3 style="text-align: center">Author: <i style="color: #010138" >{{ post.author }}</i></h3> <br><br> <h2>Title: {{ post.title }}</h2> <h3 style="text-align: center">description:</h3> <p>{{ post.description | linebreaks }}</p> <p style="color: black">{{ post.publish }}</p> <br><hr><br> <form action="{% url 'Blog:post_comment' post.id %}" method="post"> {% csrf_token %} <input type="text" id="name" placeholder="name" name="name"> <br><br> <textarea name="letter" id="text" cols="30" rows="10" placeholder="your comment..."></textarea> <input type="submit" value="send"> </form> {% endblock %}
در view مربوط به post_detail فرم کامنت را خواهیم داشت و برای نمایش کامنت ها هم لازمه توی post_detail تمام کامنت های پست را دریافت کرده و به تمپلیت ارسال کنیم.
app directory/views.py
def post_detail(request, post_id): post = get_object_or_404(Post, id=post_id, status=Post.Status.Published) form = CommentForm() comments = post.comments.filter(active=True) context = { 'post': post, 'form': form, 'comments': comments, } return render(request, 'blog/post_detail.html', context)
خب حالا بریم توی تمپلیت post_detail تعداد کامنت ها و خود کامنت ها را نمایش دهیم:
وقتی به یکسری داده از دیتابیس نیاز داریم برای جلوگیری از اینکه هربار آن داده را از دیتابیس صدا بزنیم، یکبار آنرا صدا میزنیم و مقدارش را در یک متغیر ذخیره میکنیم حالا هربار لازم داشتیم از آن متغیر استفاده میکنیم.
در تمپلیت برای ایجاد یک متغیر از تمپلیت تگ with استفاده میکنیم. | از آن متغیر فقط در بدنه with میتوان استفاده کرد.
<!-- structure 1 --> {% with variable=value %} ... {% endwith %} or {% with variable1=value1 variable2=value2 %} ... {% endwith %} <!-- structure 2 --> {% with data as variable-alias %} ... {% endwith %}
یکی از قابلیت های تمپلیت تگ for حالت empty میباشد، زمانی بدنه آن اجرا میشود که آن چیزی که روی آن حلقه زده ایم خالی است، چیزی برای حلقه زدن وجود نداره.
کد کامل تمپلیت post_detail
templates/blog/post_detail.html
{% extends 'parent/base.html' %} {% load blog_tags %} {% block title %} post detail {% endblock %} {% block content %} <h3>Author: <i>{{ post.author }}</i></h3> <br><br> <h2>Title: {{ post.title }}</h2> <h3>description:</h3> <p>{{ post.description | linebreaks }}</p> <p>{{ post.publish }}</p> <br><hr><br> <form action="{% url 'Blog:post_comment' post.id %}" method="post"> {% csrf_token %} <input type="text" id="name" placeholder="name" name="name"> <br><br> <textarea name="letter" cols="30" rows="10" placeholder="your comment..."></textarea> <input type="submit" value="send"> </form> <!-- نمایش تعداد کامنت ها --> <div> {% with comments.count as cm_count %} {{ cm_count }} comment{{ cm_count|pluralize }} {% endwith %} </div> <!-- نمایش کامنت ها --> {% for cm in comments %} <div> Name: {{ cm.name }} <br> {{ cm.letter|linebreaks }} </div> {% empty %} کامنتی وجود ندارد {% endfor %} {% endblock %}
ایجاد تمپلیت تگ سفارشی (simple tag)
simple_tag: یک string (رشته) را return میکند.
inclusion_tag: یک تمپلیت رندر شده را return میکند.
برای ایجاد تمپلیت تگ سفارشی، توی دایرکتوری app یک پکیج بنام templatetags ایجاد میکنیم.
پکیج یک دایرکتوری میباشد که داخل آن فایل init.py وجود دارد. | این فایل خالی است و محتوایی ندارد.
حالا در این پکیج یک فایل پایتونی با نام دلخواه ایجاد میکنیم(مثلا blog_tags). | ساختار تمپلیت تگ سفارشی در این فایل نوشته میشوند.
ایجاد تمپلیت تگ برای 1-تعداد پست ها 2- تعداد کامنت ها 3- تاریخ انتشار آخرین پست
در فایل پایتونی blog_tags باید مواردی را ایمپورت کنیم(مدل ها و template):
app directory/templatetags/blog_tags.py
from django import template from ..models import *
در فایل blog_tags.py باید یک متغیر بنام register ایجاد کنیم. | از آن در دکوراتور استفاده میکنیم.
app directory/templatetags/blog_tags.py
from django import template from ..models import * register = template.Library()
برای هر تمپلیت تگ، یک تابع ایجاد میکنیم و داخل بدنه اش کاری که میخواهیم را انجام میدهیم در ضمن باید برای هر تمپلیت تگ از دکوراتور simple_tag استفاده کنیم.
اگر اسمی در پرانتزهای simple_tag مشخص کنیم، اسم تمپلیت تگ آن اسم خواهد بود ولی اگر اسمی مشخص نکنیم، اسم تابع را به صورت پیشفرض انتخاب میکند.
@register.simple_tag(name="<favorite-name>")
کد مربوط به ایجاد تمپلیت تگ سفارشی
app directory/templatetags/blog_tags.py
from django import template from ..models import * register = template.Library() # محاسبه تعداد کل پست های منتشر شده @register.simple_tag() def total_posts(): return Post.published.count() # محاسبه تعداد کل کامنت ها @register.simple_tag() def total_comments(): return Comment.objects.filter(active=True).count() # تاریخ انتشار آخرین پست @register.simple_tag() def last_post_date(): # آخرین پست را انتخاب کرده و فیلد انتشار را صدا میزنیم تا تاریخ انتشار آنرا نمایش دهیم last_post = Post.published.last().publish return last_post
برای اینکه بتوانیم از تمپلیت تگ های ایجاد شده استفاده کنیم باید توی تمپلیت مدنظر فایل پایتونی که تمپلیت تگ ها آنجا نوشته شده اند را load کنیم:
{% load blog_tags %}
حالا از ساختار تمپلیت تگ استفاده میکنیم:
{% load blog_tags %} {% <template_tag name> %}
ایجاد تمپلیت تگ سفارشی (Inclusion tags)
همان طور که گفتیم یک تمپلیت را return میکنه.
این بار میخواهیم تمپلیت تگی را ایجاد کنیم که چند پست آخر را نمایش میدهد.
برای ایجاد تمپلیت تگ از روش inclusion tag، از دکوراتور inclusion_tag قبل از تابع استفاده میکنیم.
برای دکوراتور inclusion_tag، باید مسیر یک تمپلیت را مشخص کنیم.
این تمپلیت را در مسیر templates/partials ایجاد میکنیم.
app directory/templatetags/blog_tags.py
@register.inclusion_tag("partials/latest_posts.html")
مثله simple tag یک تابع نوشته و کاری که میخواهیم انجام دهد را مشخص میکینیم.
خب برای نمایش چند پست آخر، یک تابع مینویسیم که به عنوان ورودی تعداد را دریافت کنه ولی پیشفرض برایش 4 را مشخص میکنیم.(برای نمایش 4 پست آخر)
حالا برای دسترسی به چند پست های آخر، 1- ابتدا تمام پست های منتشر شده را بدست می آوریم(Post.published)، 2- حالا با order_by آنها را براساس زمان انتشار مرتب میکنیم(از جدید تا قدیم) 3- و درنهایت با slicing مشخص میکنیم چند تا از پست ها را نمایش دهد.
app directory/templatetags/blog_tags.py
@register.inclusion_tag(filename='partials/latest_posts.html') def latest_posts(count=4): L_posts = Post.published.order_by('-publish')[:count] context = {'l_posts': L_posts} return context
مثله view که متغیر را به کمک context به تمپلیت ارسال میکردیم و از آن متغیر در تمپلیت استفاده میکردیم اینجا هم به همان صورت عمل میکنیم تا آخرین پست ها را در تمپلیت نمایش دهیم حالا وقتی این تمپلیت تگ را صدا بزنیم، محتویات تمپلیت را نمایش میدهد.
templates/partials/latest_posts.html
<div> <h2>آخرین پست ها</h2> {% for post in l_posts %} <a href="post.get_absolute_url"> {{ post.title }} </a> <br> {% endfor %} </div>
<!-- نمایش 7 پست آخر --> {% latest_posts 7 %}
کار با aggregate و annotate (تگ سفارشی برای کوئری ست)
در ارتباط با کار کردن با کوئری ست ها هستند.| دستورات کوئری ست هستند.
دستور aggregate
برای درک بهتر کارکرد aggregate، برای مدل Post یک فیلد از نوع PositiveIntegerFiel، برای زمان مطالعه ، ایجاد میکنیم.
نکته مهم: وقتی یک فیلد جدید را به مدل اضافه میکنیم چنانچه از قبل داده هایی توی دیتابیس ذخیره داشته باشیم زمانیکه میخواهیم از makemigrations استفاده کنیم به مشکل خورده و دو گزینه در اختیار ما قرار میدهد:
1- از ما درخواست میکنه یک مقدار برای این فیلد جدید، برای دیتاهایی که از قبل ذحیره شده اند مشخص کنیم چون این فیلد برای آنها مقداردهی نشده است. | پس از انتخاب این گزینه یک مقدار را مشخص میکنیم تا آن دیتاهای قبلی را مقداردهی کند.
2-از ما درخواست میکنه که به مدل مربوطه مراجعه کنیم و برای آن فیلد با استفاده از آرگومان default یک مقدار پیشفرض مشخص کنیم.
app directory/models.py
class Post(models.Model): class Status(models.TextChoices): Draft = 'DF', 'Draft' Published = 'PB', 'Published' Rejected = 'RJ', 'Rejected' # ️ ⬆️ choice-field ⬆️ status = models.CharField(max_length=2, choices=Status.choices, default=Status.Draft) # relation author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_posts') # information's title = models.CharField(max_length=250) description = models.TextField() slug = models.SlugField() # data for time(date) publish = models.DateTimeField(default=timezone.now) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) reading_time = models.PositiveIntegerField(default=0) # managers objects = models.Manager() published = PublishedManager() # ordering & indexing class Meta: ordering = ['-publish'] indexes = [models.Index(fields=['-publish'])] def get_absolute_url(self): # return reverse('Blog:post_detail', kwargs={'slugs': self.id}) return reverse('Blog:post_detail', args=[self.id]) def __str__(self): return self.title
ساختار استفاده از aggregate:
# structure ModelName.Manager.aggregate(func(field-name)) # example Post.published.aggregate(Sum('reading_time')) # ⬆️مجموع زمان های مطالعه تمام پست های منتشر شده را نشان میدهد⬆️
برای نمایش کارکرد aggregate و annotate از shell استفاده میکنیم.
مدل ها و متدهای aggregate را باید ایمپورت کنیم:
به ترتیب از چب به راست: تعداد، بیشترین، کمترین، میانگین و مجموع
shell
>>> from blog.models import * >>> from django.db.models import Count, Max, Min, Avg, Sum >>> Post.published.aggregate(Sum('reading_time')) 65 >>> Post.published.aggregate(Avg('reading_time')) 13 >>> Post.published.aggregate(Min('reading_time')) 0 >>> Post.published.aggregate(Max('reading_time')) 25
دستور annotate
به این صورت عمل میکند که یک فیلد داینامیک برای مدل مدنظر ایجاد میکنه ولی چیزی توی دیتابیس اضافه نمیشه (این فیلد جدید برای کوئری ست وجود داره و قابل استفاده است.)
ساختار استفاده از annotate:
# structure ModelName.Manager.annotate(variable=value) ModelName.Manager.annotate(variable=func(field_name)) # example Post.published.annotate(comments_count=Count('comments')) # ⬆️برای هر پست یک فیلد اضافه میکنه که تعداد کل کامنت های آن پست را نشان میدهد⬆️
shell
>>> from blog.models import * >>> from django.db.models import Count >>> posts = Post.published.annotate(comments_count=Count('comments')) >>> posts[1].comments_count 3
برای استفاده از این از این فیلد جدید، باید یک کوئری را انتخاب کنیم؛ یا روی مجموعه کوئری ها حلقه میزنیم و یا با ایندکس یک مورد را انتخاب کرده و سپس آن فیلد را صدا میزنیم.
بریم یک تمپلیت تگ ایجاد کنیم که از annotate استفاده میکنه:
میخواهیم تمپلیت تگی ایجاد کنیم که معروف ترین پست ها را نشان میدهد. (براساس تعداد کامنت ها)
میتوانیم مشخص کنیم که چند پست محبوب را نشان دهد.
app directory/templatetags/blog_tags.py
@register.simple_tag def most_popular_posts(count=5): return Post.published.annotate(comments_count=Count('comments')).order_by('-comments_count')[:count]
خروجی این تمپلیت تگ یک مجموعه کوئری میباشد.
میتوانیم برای تمپلیت تگ یک اسم مستعار ایجاد کرده تا از آن استفاده کنیم.
template
{% most_popular_posts as pop_posts %} {% for post in pop_posts %} <a href="{{ post.get_absolute_url }}"> {{ post.title }}: with {{ post.comments_count }} comment{{ post.comments_count|pluralize }} </a> {% endfor %}
ایجاد تمپلیت فیلتر سفارشی و کار با markdown
ساختار تمپلیت فیلتر:
{{ value | filter }} {{ value | filter:<filter-value> }} {{ value | filter | filter }}
میخواهیم تمپلیت فیلتری ایجاد کنیم، که متن مارک داون را به html تبدیل میکند.
برای استفاده از markdown باید آن پکیج را نصب و سپس آنرا ایمپورت کنیم.
pip install markdown
from markdown import markdown
تمپلیت فیلترها و تمپلیت تگ ها در یک فایل نوشته میشوند.
از دکوراتور فیلتر استفاده میکنیم؛ حالا مشابه با تمپلیت تگ ها یک تابع نوشته و داخل آن وظیفه اش را مشخص میکنیم.
براساس ساختار تمپلیت فیلترها که در بالا مثال زدیم، فیلتر روی value اعمال میشه و تغییراتی روی آن ایجاد میکند، پس لازمه برای تابع خود یک پارامتر اجباری مشخص کنیم و توی بدنه تابع تغییرات را روی آن لحاظ کنیم.
تمپلیت فیلتر افزون بر پارامتر اول که اجباریه میتواند پارامتر دوم نیز داشته باشد برای مثال در تمپلیت فیلتر add که برایش مقداری برای جمع شدن مینویسیم آن مقدار میشه پارامتر دوم.
در پرانتز دکوراتور فیلتر میتوانیم از آرگومان name="" استفاده کرده اسم دلخواه خود را برای آن مشخص کنیم.
نکته مهم: برای نمایش صحیح خروجی markdown در قالب html لازمه در فایل blog_tags ماژول mark_safe را ایمپورت کنیم.
app directory/templatetags/blog_tags.py
from markdown import markdown from django.utils.safestring import mark_safe @register.filter(name='markdown') def to_markdown(text): return mark_safe(markdown(text))
استایل دهی فرم های آماده
در تمپلیت با دو روش میشه فرم را ایجاد کرد: 1- به صورت اتوماتیک(با متدهای form ارسال شده) 2- ایجاد فرم به صورت دستی (ایجاد فرم با استفاده از تگ input)
وقتی خودمان فرم را ایجاد میکنیم میتوانیم هر اتریبیوتی که خواستیم برای input مشخص کنیم ولی در حالت اتوماتیک چطور اتریبیوت اضافه کنیم (مثل اتریبیوت class)
خب در فایل forms.py به دو روش میتوانیم فرم ایجاد کنیم؛ یکی با ارث بری از forms.Form و دیگری با ارث بری از forms.ModelForm صورت میگیرد.
فرم ایجاد شده از Form
به منظور اضافه کردن اتریبیوت، برای هر کدام از فیلدها از آرگومان widget استفاده کرده و نوع input آنرا را مشخص میکنیم.
برای CharField برای widget عبارت TextInput را مشخص میکنیم، برای EmailField عبارت EmailInput را مشخص میکنیم.
میتوانید تگ input و widget مربوط به هر نوع از فیلدهای فرم را در آدرس webforefront.com مشاهده کنید.
حالا برای widget های مشخص شده از آرگومان attrs استفاده کرده و برایش یک دیکشنری تعریف میکنیم، کلیدهای آن میشه اسم اتریبیوت مدنظر و مقدار آن کلید میشه مقدار اتریبیوت مشخص شده.
widget=forms.<type-of-widget>(attrs={'attr_name': 'value'})
بریم یک نمونه ببینیم:
app directory/forms.py
full_name = forms.CharField(max_length=250, widget=forms.TextInput(attrs={'class': 'form-name', 'placeholder': 'Full Name'}))
خب بریم برای فیلدهای فرم تیکت، اتریبیوت class اضافه کنیم.
اسم برای اتریبیوت class دلخواه است.
app directory/forms.py
class TicketForm(forms.Form): SUBJECT_CHOICES = ( ('پیشنهاد', 'پیشنهاد'), ('گزارش', 'گزارش'), ('انتقاد', 'انتقاد'), ) subject = forms.ChoiceField(choices=SUBJECT_CHOICES, widget=forms.Select(attrs={'class': 'form-subject'})) full_name = forms.CharField(max_length=250, widget=forms.TextInput(attrs={'class': 'form-fName', 'placeholder': 'Full Name'})) message = forms.CharField(required=True, widget=forms.Textarea(attrs={'class': 'form-message'})) email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-email'})) phone = forms.CharField(max_length=11, widget=forms.TextInput(attrs={'class': 'form-phone'}))
فرم ایجاد شده از ModelForm
در ساختار ModelForm یکم داستان فرق میکنه.
در کلاس Meta، از «اتریبیوت کلاس widgets» استفاده میکنیم؛ مقداری که برایش مشخص میکنیم یک دیکشنری است.
کلیدهای این دیکشنری اسم فیلدهای فرم هستند؛ حالا برای اینکه برای هر فیلد اتریبیوتی که میخواهیم اضافه کنیم، به عنوان value برای آن فیلد از ساختار forms.<type-of-widget>() استفاده کرده و حالا مثل حالت قبلی از آرگومان attrs استفاده کرده و برایش یک دیکشنری مشخص میکنیم حالا اتریبیوت های مدنظر را اضافه میکنیم.
خب بریم برای فیلدهای فرم کامنت، اتریبیوت class اضافه کنیم
app directory/forms.py
class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ['name', 'letter'] widgets = { 'name': forms.TextInput(attrs={'class': 'form-name'}), 'letter': forms.TextInput(attrs={'class': 'form-letter'}), }
تمرینات فصل چهارم (مهم)
T1- نوشتن فرم برای ایجاد پست:
بریم ساختار فرم خود را در forms.py ایجاد کنیم. | از هر دو روش استفاده میکنیم.
در فرم ها PositiveIntegerField نداریم پس از IntegerField استفاده کرده و برایش min_value مشخص میکنیم تا مثل PositiveIntegerField عمل کند.
app directory/forms.py
# first way class CreatePostForm(forms.Form): title = forms.CharField(max_length=250) description = forms.CharField(widget=forms.Textarea) reading_time = forms.IntegerField(min_value=0)
app directory/forms.py
# second way class CreatePostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'description', 'reading_time']
بریم برای صفحه ایجاد پست یک URL بنویسیم:
app directory/urls.py
path('create-post/', views.create_post, name='create_post'),
وقتشه بریم و یک view برای ایجاد پست جدید بنویسیم:
دستور request.user کاربری که لاگین کرده است را برمیگرداند.
ساختار view برای فرمی که از forms.Form ارث بری کرده است:
app diretory/views.py
def create_post(request): if request.method == 'POST': form = CreatePostForm(request.POST) if form.is_valid(): cd = form.cleaned_data Post.objects.create( author=request.user, title=cd['title'], description=cd['description'], reading_time=cd['reading_time'], ) return redirect('Blog:index') else: form = CreatePostForm() return render(request, 'forms/create_post.html', {'form': form})
ساختار view برای فرمی که از forms.ModelForm ارث بری کرده است:
app diretory/views.py
def create_post(request): if request.method == 'POST': form = CreatePostForm(request.POST) if form.is_valid(): new_post = form.save(commit=False) new_post.author = request.user new_post.save() return redirect('Blog:index') else: form = CreatePostForm() return render(request, 'forms/create_post.html', {'form': form})
بریم تمپلیت create_post.html را ایجاد کنیم:
فرم در تمپلیت را به دو روش اتوماتیک و دستی ایجاد میکنیم. | فرقی نمیکند از کدام فرم(Form, ModelForm) استفاده کرده باشید.
templates/forms/create_post.html
{% extends 'parent/base.html' %} {% block title %} post detail {% endblock %} {% block content %} <form method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="send"> </form> {% endblock %}
templates/forms/create_post.html
{% extends 'parent/base.html' %} {% block title %} post detail {% endblock %} {% block content %} <form method="post"> {% csrf_token %} <input type="text" name="title" placeholder="Title"> <br> <textarea name="description">Description...</textarea> <br> <input type="number" name="reading_time" min="0"> <br> <input type="submit" value="send"> </form> {% endblock %}
T2- اعتبارسنجی فیلدهای فرم ایجاد پست:
شیوه اعتبارسنجی در هردو فرم یکسان میباشد بنابراین فقط برای یکی اعتبارسنجی را انجام میدهیم.
app directory/forms.py
class CreatePostForm(forms.Form): title = forms.CharField(max_length=250) description = forms.CharField(widget=forms.Textarea) reading_time = forms.IntegerField(min_value=0) def clean_title(self): title = self.cleaned_data['title'] if len(title) < 3: raise forms.ValidationError('تعداد کاراکترهای عنوان کمتر از حد مجاز میباشد') elif len(title) > 45: raise forms.ValidationError('عنوان نمیتواند بیشتر از 45 کاراکتر باشد') else: return title # --------------------------------------------------------------- def clean_reading_time(self): try: reading_time = int(self.cleaned_data['reading_time']) except ValueError: raise forms.ValidationError('زمان مطالعه باید یک عدد باشد.') if reading_time < 0: raise forms.ValidationError('زمان مطالعه نمیتواند منفی باشد') else: return reading_time
T3- نمایش خطاهای فرم ایجاد پست:
خب بریم خطاهای فرم را در تمپلیت نمایش دهیم:
templates/forms/create_post.html
{% extends 'parent/base.html' %} {% block title %} post detail {% endblock %} {% block content %} <form method="post"> {% csrf_token %} <input type="text" name="title" placeholder="Title"> <br> <textarea name="description">Description...</textarea> <br> <input type="number" name="reading_time" min="0"> <br> <input type="submit" value="send"> </form> <div class="errors"> {% if form.non_field_errors %} {{ form.non_field_errors }} {% endif %} {% if form.errors %} {% for field in form %} {% if field.errors %} {% for error in field.errors %} {{ field.name }}: {{ error }} {% endfor %} {% endif %} {% endfor %} {% endif %} </div> {% endblock %}
T4- ایجاد تمپلیت تگ هایی که بیشترترین و کمترین تایم مطالعه را دارند:
این کار را با هردو حالت ایجاد تمپلیت تگ انجام میدهیم.
"simple tag"
نمایش پست با بیشترین تایم مطالعه با simple_tag:
app directory/templatetags/blog_tags.py
from django import template from ..models import * register = template.Library() @register.simple_tag() def max_reading_time(): return Post.published.order_by('-reading_time').first()
نمایش پست با کمترین تایم مطالعه با simple_tag:
app directory/templatetags/blog_tags.py
from django import template from ..models import * register = template.Library() @register.simple_tag() def max_reading_time(): return Post.published.order_by('-reading_time').last()
"inclusion tag"
نمایش پست با بیشترین تایم مطالعه با inclusion_tag:
app directory/templatetags/blog_tags.py
from django import template from ..models import * register = template.Library() @register.inclusion_tag('partials/max_reading_time.html') def max_reading_time(): max_time = Post.published.order_by('-reading_time').first() context = {'max_time': max_time} return context
templates/partials/max_reading_time.html
longest post: {{ max_time }}
نمایش پست با کمترین تایم مطالعه با inclusion_tag:
app directory/templatetags/blog_tags.py
from django import template from ..models import * register = template.Library() @register.inclusion_tag('partials/min_reading_time.html') def min_reading_time(): min_time = Post.published.order_by('-reading_time').last() context = {'min_time': min_time} return context
templates/partials/min_reading_time.html
shortest post: {{ min_time }}
T5- تمپلیت تگی که فعال ترین کاربر را نشان میدهد.(کاربری که بیشترین تعداد پست را دارد):
app directory/templatetags/blog_tags.py
from django import template from ..models import * from django.db.models import Count register = template.Library() @register.simple_tag() def most_active_user(): return User.objects.annotate(total_posts=Count('user_posts')).order_by('-total_posts').first()
T6- تمپلیت فیلتر که متن را سانسور میکنه:
app directory/templatetags/blog_tags.py
from django import template from ..models import * register = template.Library() @register.filter() def text_censor(text): deleted_words = ['donkey', 'monkey', 'dog'] for d_word in deleted_words: if d_word in text: text = text.replace(d_word, "...") return text