Chapter II Contents:
Using django to send mail
Create forms and process them in views
Create form from model
Consolidate third-party applications
Building a complex QuerySet
2.1 sharing posts by email
For the mail sending function of a post, you need to do the following:
- Create user form, fill in name, email, recipient, optional note function.
- Create views in views.py file, process data and send mail
- In the urls.py file of the blog application, add the url path for the new view.
- ’Create a template and display the form.
django's own base class: Form,ModelForm
Create forms.py in the blog directory
from django import forms class EmailPostForm(forms.Form): name = forms.CharField(max_length=25) email = forms.EmailField() to = forms.EmailField() comments = forms.CharField(required=False, widget=forms.Textarea)
Configure mail parameters in setting:
# Send mail configuration EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # smpt service address EMAIL_HOST = 'smtp.qq.com' EMAIL_PORT = 25 # Port default is 25, no need to modify # The mailbox for sending mail needs to be configured to enable SMTP EMAIL_HOST_USER = 'xxx@qq.com' # Client authorization password set in mailbox EMAIL_HOST_PASSWORD = 'xxx' # Sender seen by recipient EMAIL_FROM = 'Signed<xxx@qq.com>'
To process a form in views.py:
def post_share(request, post_id): post = get_object_or_404(Post, id=post_id, status='published') sent = False if request.method == 'POST': form = EmailPostForm(request.POST) if form.is_valid(): cd = form.cleaned_data post_url = request.build_absolute_uri(post.get_absolute_url()) subject = '{} ({}) recommends you reading"{}"'.format(cd['name'], cd['email'], post.title) message = 'Read "{}" at {}\n\n{}\'s comments:{}'.format(post.title, post_url, cd['name'], cd['comments']) send_mail(subject, message, 'xxx@qq.com', [cd['to']]) sent = True else: form = EmailPostForm() return render(request, 'blog/post/share.html', {"post": post, "form": form, 'sent': sent})
Configure blog/urls.py:
path('<int:post_id>/share/', views.post_share, name='post_share'),
Create a new file, share.html, in blog/templates/blog/post, and add the following code:
{% extends "blog/base.html" %} {% block title %}share a post {% endblock %} {% block content %} {% if sent %} <h1>E-mail successfully sent</h1> <p> "{{ post.title }}" was successfully sent to {{ form.cleaned_data.to }}. </p> {% else %} <h1>Share"{{ post.title }}"by e-mail</h1> <form action="." method="post"> {{ form.as_p }} {% csrf_token %} <input type="submit" value="Send e-mail"> </form> {% endif %} {% endblock %}
Then run the program to send email for sharing. Note that the email address xxx@qq.com in setting and view should be the same.
I feel a little tired, but come on, we just started... keep trying.
2.2 build comment system
Step 1: create the model. Write the code in the models.py file in the blog:
class Comment(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') name = models.CharField(max_length=80) email = models.EmailField() body = models.TextField() created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) active = models.BooleanField(default=True) class Meta: ordering = ('created',) def __str__(self): return 'Comment by {} on {}'.format(self.name, self.post)
Run the command line for migration
python manage.py makemigrations blog python manage.py migrate
To import the comment model in blog/admin.py:
@admin.register(Comment) class CommentAdmin(admin.ModelAdmin): list_display = ('name', 'email', 'post', 'created', 'active') list_filter = ('active', 'created', 'updated') search_fields = ('name', 'email', 'body')
Create the form in the model. Use ModelForm here:
from blog.models import Comment class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ('name', 'email', 'body')
Modify the post detail function in blog/views.py:
def post_detail(request, year, month, day, post): # Get independent posts post = get_object_or_404(Post, slug=post, status='published', publish__year=year, publish__month=month, publish__day=day) comments = post.comments.filter(active=True) new_comment = None if request.method == 'POST': comment_form = CommentForm(data=request.POST) if comment_form.is_valid(): new_comment = comment_form.save(commit=False) new_comment.post = post new_comment.save() else: comment_form = CommentForm() return render(request, 'blog/post/detail.html', {"post": post, 'comments': comments, 'new_comment': new_comment, 'comment_form': comment_form})
Modify post/detail.html
- Show all comments
- Show comment list
- Show users a form and add new comments.
{% extends "blog/base.html" %} {% block title %}{{ post.title }} {% endblock %} {% block content %} <h1>{{ post.title }}</h1> <p class="date"> Published {{ post.publish }} by {{ post.author }} </p> {{ post.body|linebreaks }} <p> <a href="{% url 'blog:post_share' post.id %}"> Share this post</a> </p> {% with comments.count as total_comments %} <h2> {{ total_comments}} comment{{ total_comments|pluralize }} </h2> {% endwith %} {% for comment in comments %} <div class="comment"> <p class="info"> Comment{{ forloop.counter }} by {{ comment.name }} {{ comment.created }} </p> {{ comment.body|linebreaks}} </div> {% empty %} <p>There are no comments yet.</p> {% endfor %} {% if new_comment %} <h2>Your comment has been added.</h2> {% else %} <h2>Add a new comment</h2> <form action="." method="post"> {{ comment_form.as_p }} {% csrf_token %} <p><input type="submit" value="Add comment"></p> </form> {% endif %} {% endblock %}
Visit the website, you can see the following content, submit comments and test successfully.
2.3 add label function
The third-party plug-in Django taggit module is 0.22.2 in the book, and I installed 1.2.0.
pip install django_taggit==0.22.2
Registered in setting.py
INSTALLED_APPS = [ ... 'taggit', ]
Instantiate in the Post function in models.py.
class Post(models.Model): # Inherit models.Model ... tags = TaggableManager() # Add tags
Then database migration is a two-step operation.
python manage.py makemigrations blog python manage.py migrate
Running projects, opening http://127.0.0.1:8000/admin/taggit/tag/
Add labels. Tag each article.
In blog/post/list.html, add p tag under post title h2, and use comma join:
<p class="tags">Tags:{{ post.tags.all|join:"," }}</p>
Modify blog/views.py and use function view here. The complete code is as follows:
def post_list(request, tag_slug=None): # Get post list object_list = Post.published.all() tag = None if tag_slug: tag = get_object_or_404(Tag, slug=tag_slug) object_list = object_list.filter(tags__in=[tag]) paginator = Paginator(object_list, 3) # Three per page page = request.GET.get("page") try: posts = paginator.page(page) except PageNotAnInteger: # Not an integer, return to the first page posts = paginator.page(1) except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request, 'blog/post/list.html', {'page': page, 'posts': posts, 'tag': tag})
Pay attention to modifying the url. I will post the whole blog/urls.py at this time:
from django.urls import path from . import views urlpatterns = [ path('', views.post_list, name='post_list'), # path('', views.PostListView.as_view(), name='post_list'), path('<int:year>/<int:month>/<int:day>/<slug:post>/', views.post_detail, name='post_detail'), path('<int:post_id>/share/', views.post_share, name='post_share'), path('tag/<slug:tag_slug>/', views.post_list, name='post_list_by_tag'), ]
The two paths here point to the same view, but use different name s. The first one does not contain parameters. The second one uses the tag ﹣ slug parameter and the slug path converter.
Modify blog/post/list.html, and I will post all the codes:
{% extends "blog/base.html" %} {% block title %}{% endblock %} {% block content %} <h1>My blog</h1> {% if tag %} <h2>Posts tagged with "{{ tag.name }}"</h2> {% endif %} {% for post in posts %} <h2> <a href="{{ post.get_absolute_url }}"> {{ post.title }} </a> </h2> <!--Change how labels are displayed--> <p class="tags">Tags: <!--{{ post.tags.all|join:"," }}--> {% for tag in post.tags.all %} <a href="{% url 'blog:post_list_by_tag' tag.slug %}"> {{ tag.name }} </a> {% if not forloop.last %}, {% endif %} {% endfor %} </p> <p> Published {{ post.publish }} by {{ post.author }} </p> {{ post.body|truncatewords:30|linebreaks }} {% endfor %} {% include "pagination.html" with page=posts %} <!--{% include "pagination.html" with page=page_obj %}--> {% endblock %}
Visit the link, test the view, and need to tag the article in admin in advance.
http://127.0.0.1:8000/blog/
Click tag to access.
2.4 retrieve posts based on similarity.
Display similar posts by common tag number. In this way, when users visit a post, they can recommend reading related posts.
Steps:
- Retrieve all tags for the current post,
- Get all posts with specific tags,
- Exclude the current post from the results list to avoid recommending the same post,
- Sort the results by the tag number of the current post,
- If there are two or more posts with the same tag number, the most recent post is recommended,
- Limit queries to the number of posts you want to recommend
In view function, add:
from taggit.models import Tag def post_detail(request, year, month, day, post): ... post_tags_ids = Post.tags.values_list('id', flat=True) similar_posts = Post.published.filter(tags__in=post_tags_ids).exclude(id=post.id) similar_posts = similar_posts.annotate(same_tags=Count('tags')).order_by('-same_tags', '-publish')[:4] return render(request, 'blog/post/detail.html', {... 'similar_posts': similar_posts})
In blog/post/detail.html, add:
<!--Add similar recommendation--> <h2>Similar posts</h2> {% for post in similar_posts %} <p> <a href="{{ post.get_absolute_url }}">{{ post.title }}</a> </p> {% empty %} There are no similar posts yet. {% endfor %}