- 2019-05-19 开始学习第六章,现在才开始编辑第七章.我真的好拖延啊.
7-1 文章模块models.py设计 - python manage.py startapp articles
- zanhu/artivles/models.py
from django.db import models
from six import python_2_unicode_compatible
@python_2_unicode_compatible
class Article(models.Model):
STATUS = (("D", "Draft"), ("P", "Published"))
title = models.CharField(max_length=255, null=False, unique=True, verbose_name='标题')
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, related_name="author", on_delete=models.SET_NULL, verbose_name='作者')
image = models.ImageField(upload_to='articles_pictures/%Y/%m/%d/', verbose_name='文章图片')
slug = models.SlugField(max_length=80, null=True, blank=True, verbose_name='(URL)别名')
status = models.CharField(max_length=1, choices=STATUS, default='D', verbose_name='动态') # 默认存入草稿箱
content = models.TextField(verbose_name='内容')
edited = models.BooleanField(default=False, verbose_name='是否可编辑')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '文章'
verbose_name_plural = verbose_name
ordering = ("created_at",)
def __str__(self):
return self.title
7-2 使用python-slugify和django-taggit
- python-slugify 网址显示题目的拼音.
7-3 models.py中自定义QuerySet
from django.db import models
from six import python_2_unicode_compatible
from slugify import slugify
from taggit.managers import TaggableManager
from django.conf import settings
@python_2_unicode_compatible
class ArticleQuerySet(models.query.QuerySet):
"""自定义QuerySet,提高模型类的可用性"""
def get_published(self):
"""返回已发表的文章"""
return self.filter(status="P")
def get_drafts(self):
"""返回草稿箱的文章"""
return self.filter(status="D")
def get_counted_tags(self):
"""统计所有已发布的文章中,每一个标签的数量(大于0的)"""
tag_dict = {}
query = self.filter(status='P').annotate(tagged=Count('tags')).filter(tags__gt=0)
for obj in query:
for tag in obj.tags.names():
if tag not in tag_dict:
tag_dict[tag] = 1
else:
tag_dict[tag] += 1
return tag_dict.items()
@python_2_unicode_compatible
class Article(models.Model):
STATUS = (("D", "Draft"), ("P", "Published"))
title = models.CharField(max_length=255, null=False, unique=True, verbose_name='标题')
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, related_name="author", on_delete=models.SET_NULL, verbose_name='作者')
image = models.ImageField(upload_to='articles_pictures/%Y/%m/%d/', verbose_name='文章图片')
slug = models.SlugField(max_length=80, null=True, blank=True, verbose_name='(URL)别名')
status = models.CharField(max_length=1, choices=STATUS, default='D', verbose_name='动态') # 默认存入草稿箱
content = models.TextField(verbose_name='内容')
edited = models.BooleanField(default=False, verbose_name='是否可编辑')
tags = TaggableManager(help_text='多个标签使用,(英文)隔开', verbose_name='标签')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
objects = ArticleQuerySet.as_manager()
class Meta:
verbose_name = '文章'
verbose_name_plural = verbose_name
ordering = ("created_at",)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
# 根据作者和标题生成文章在URL中的别名
self.slug = slugify(self.title)
super(Article, self).save(*args, **kwargs)
- zhanhu/config/setting/base.py
THIRD_PARTY_APPS = [
"crispy_forms",
"allauth",
"allauth.account",
"allauth.socialaccount",
"rest_framework",
"sorl.thumbnail",
"taggit",
]
LOCAL_APPS = [
'users.apps.UsersConfig',
'news.apps.NewsConfig',
'articles.apps.ArticlesConfig',
# Your stuff: custom apps go here
]
7-4 完成文章列表页开发
- articles/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
# Create your views here.
from django.views.generic import ListView
from dacall.articles.models import Article
class ArticlesListView(LoginRequiredMixin, ListView):
"""已发布的文章列表"""
model = Article
paginate_by = 10
context_object_name = "articles"
template_name = "articles/article_list.html" # 可省略
def get_context_data(self, *args, **kwargs):
context = super(ArticlesListView, self).get_context_data(*args, **kwargs)
context['popular_tags'] = Article.objects.get_counted_tags()
return context
def get_queryset(self, **kwargs):
return Article.objects.get_published()
- /articles/urls.py
#!/usr/bin/python3
# -*- coding:utf-8 -*-
# __author__ = '__Jack__'
from django.urls import path
from zanhu.articles import views
app_name = 'articles' # Django > 2.0,这样模板中可以使用"{% url 'articles:list' %}","articles"是总urls.py中定义的namespace
urlpatterns = [
path('', views.ArticlesListView.as_view(), name='list'),
path('write-new-article/', views.CreateArticleView.as_view(), name='write_new'),
path('drafts/', views.DraftsListView.as_view(), name='drafts'),
path('edit/<int:pk>/', views.EditArticleView.as_view(), name='edit_article'),
path('<slug>/', views.DetailArticleView.as_view(), name='article'),
]
- config/urls.py
from django.conf import settings
from django.urls import include, path
from django.conf.urls.static import static
from django.views.generic import TemplateView
from django.views import defaults as default_views
from dacall.news.views import NewsListView
urlpatterns = [
path('', NewsListView.as_view(), name='home'),
# User management
path("users/", include("dacall.users.urls", namespace="users")),
path("accounts/", include("allauth.urls")),
path('news/', include('dacall.news.urls', namespace='news')),
path('articles/', include('dacall.articles.urls', namespace='articles')),
# Your stuff: custom urls includes go here
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
# This allows the error pages to be debugged during development, just visit
# these url in browser to see how these error pages look like.
urlpatterns += [
path(
"400/",
default_views.bad_request,
kwargs={"exception": Exception("Bad Request!")},
),
path(
"403/",
default_views.permission_denied,
kwargs={"exception": Exception("Permission Denied")},
),
path(
"404/",
default_views.page_not_found,
kwargs={"exception": Exception("Page not Found")},
),
path("500/", default_views.server_error),
]
if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns
- /templates/base.html
{% load static compress thumbnail %}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}赞乎{% endblock title %}</title>
<link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}">
<meta name="description" content="赞乎问答社区">
<meta name="author" content="__Jack__">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
<![endif]-->
{% compress css %}
<!-- Latest compiled and minified Bootstrap 4 beta CSS -->
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<!-- Your stuff: Third-party CSS libraries go here -->
<link rel="stylesheet" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}">
<!-- This file stores project-specific CSS -->
<link rel="stylesheet" href="{% static 'css/zanhu.css' %}">
{% block css %}{% endblock css %}
{% endcompress %}
</head>
<body>
<nav class="navbar fixed-top navbar-expand-sm bg-light">
<div class="container">
<a class="navbar-brand" href="{% url 'news:list' %}">赞 乎 </a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainMenu" aria-controls="mainMenu"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainMenu">
<ul class="navbar-nav">
<li class="nav-item">
<a class="btn-sm" href="#" id="notifications" data-toggle="popover" data-title="通知">
<i class="fa fa-bell-o" aria-hidden="true"></i>
</a>
</li>
</ul>
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a class="nav-link" href="{% url 'news:list' %}"> 首页</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'articles:list' %}">文章</a></li>
{# <li class="nav-item"><a class="nav-link" href="{% url 'qa:unanswered_q' %}">问答</a></li> #}
{# <li class="nav-item"><a class="nav-link" href="{% url 'messager:messages_list' %}">私信</a></li> #}
</ul>
<form role="search" action="#}">
<div class="input-group">
<input name="q" type="search" id="searchInput" class="form-control" placeholder="搜索" aria-label="Search">
<div class="input-group-append">
<button class="input-group-text"><i class="fa fa-search" aria-hidden="true"></i></button>
</div>
</div>
</form>
{% if request.user.is_authenticated %}
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
{% thumbnail request.user.picture "x40" as im %}
<img src="{{ im.url }}" style="border-radius: 50%;" alt="用户头像" class="user-image">
{% empty %}
<img src="{% static 'img/user.png' %}" height="40px" alt="没有头像"/>
{% endthumbnail %}
{{ request.user.username }}
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{% url 'users:detail' request.user.username %}">
<i class="fa fa-cogs fa-fw" aria-hidden="true"></i> 设置</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'account_logout' %}">
<i class="fa fa-sign-out fa-fw" aria-hidden="true"></i> 退出</a>
</div>
</li>
</ul>
{% endif %}
</div>
</div>
</nav>
<div class="mb-3"></div>
<div class="container">
{% if messages %}
{% for message in messages %}
<div id="messages" class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock content %}
</div>
<!-- /container -->
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<!-- Required by Bootstrap v4 -->
{% compress js %}
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/popper.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/bootstrap.min.js' %}" type="text/javascript"></script>
<!-- Your stuff: Third-party javascript libraries go here -->
<script src="{% static 'js/jquery-ui.min.js' %}" type="text/javascript"></script>
<!-- place project specific Javascript in this file -->
<script src="{% static 'js/zanhu.js' %}" type="text/javascript"></script>
<script src="{% static 'js/websocketbridge.js' %}" type="text/javascript"></script>
<script type="text/javascript">
const currentUser = "{{ request.user.username }}";
</script>
{% block js %}{% endblock js %}
{% endcompress %}
</body>
<!-- /.container -->
</html>
7-5 用户发表文章与保存草稿
- forms.py
from django import forms
from dacall.articles.models import Article
class ArticleForm(forms.ModelForm):
# status = forms.CharField(widget=forms.HiddenInput()) # 隐藏
# edited = forms.BooleanField(widget=forms.HiddenInput(), required=False, initial=False) # 隐藏
# content = MarkdownxFormField()
class Meta:
model = Article
fields = ["title", "content", "image"]
- url
path('write-new-article/', views.CreateArticleView.as_view(), name='write_new'),
path('drafts/', views.DraftsListView.as_view(), name='drafts'),
path('edit/<int:pk>/', views.EditArticleView.as_view(), name='edit_article'),
- views.py
class DraftsListView(ArticlesListView):
"""草稿箱文章列表"""
def get_queryset(self, **kwargs):
# 当前用户的草稿
return Article.objects.filter(user=self.request.user).get_drafts()
class CreateArticleView(LoginRequiredMixin, CreateView):
"""创建文章"""
model = Article
message = "您的文章已创建成功!" # Django框架中的消息机制
form_class = ArticleForm
template_name = 'articles/article_create.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super(CreateArticleView, self).form_valid(form)
def get_success_url(self):
"""创建成功后跳转"""
messages.success(self.request, self.message) # 消息传递给下一次请求
return reverse_lazy('articles:list')
7-6 实现Markdown编辑与实时预览
- 安装markdownx
- settting/base.py
THIRD_PARTY_APPS = [
"crispy_forms",
"allauth",
"allauth.account",
"allauth.socialaccount",
"rest_framework",
"sorl.thumbnail",
"taggit",
"markdownx",
]
- 总url.py
# 第三方应用
# path('comments/', include('django_comments.urls')),
path('markdownx/', include('markdownx.urls')),
- forms
from django import forms
from markdownx.fields import MarkdownxFormField
from dacall.articles.models import Article
class ArticleForm(forms.ModelForm):
status = forms.CharField(widget=forms.HiddenInput()) # 隐藏
edited = forms.BooleanField(widget=forms.HiddenInput(), required=False, initial=False) # 隐藏
content = MarkdownxFormField()
class Meta:
model = Article
fields = ["title", "content", "image", "tags", "status", "edited"]
pipenv install django-markdownx 没有成功
后来是 pip install django-markdownx成功的.
python manage.py makemigrations
python manage.py migrate
python manage.py collectstatic
- python manage.py collectstatic 这步骤
7-7 通用类视图CreateView源码详解
7-8 用户浏览文章内容
class DetailArticleView(LoginRequiredMixin, DetailView):
"""文章详情"""
model = Article
template_name = 'articles/article_detail.html'
path('<slug>/', views.DetailArticleView.as_view(), name='article'),
7-9 django-contrib-comments实现评论文章
7-10 用户编辑文章
7-11 通用类视图UpdateView源码详解
7-12 Django Template Language语法精讲
7-13 Django Template Language语法精讲
7-14 模型类和视图的测试用例
7-15 本章总结与课后作业.