2019-08-05 第七章Django高级实战 开发企业级问答网站

  • 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' %}">赞 乎&nbsp;&nbsp;</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>&nbsp;&nbsp;

            <ul class="navbar-nav mr-auto">
                <li class="nav-item"><a class="nav-link" href="{% url 'news:list' %}">&nbsp;&nbsp;首页</a></li>&nbsp;&nbsp;
                <li class="nav-item"><a class="nav-link" href="{% url 'articles:list' %}">文章</a></li>&nbsp;&nbsp;
{#                <li class="nav-item"><a class="nav-link" href="{% url 'qa:unanswered_q' %}">问答</a></li>&nbsp;&nbsp;#}
{#                <li class="nav-item"><a class="nav-link" href="{% url 'messager:messages_list' %}">私信</a></li>&nbsp;&nbsp;#}
            </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">&times;</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 本章总结与课后作业.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容