Django创建博客应用

Posted 血之君殇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django创建博客应用相关的知识,希望对你有一定的参考价值。

最近在看一篇全栈增长工程师实战,然后学习里面的项目,结果发现作者用的技术太过老旧,好多东西都已经被抛弃了,所以结合着官方文档和自己的一些理解将错误的信息替换一下,边写边学习

准备工作和工具

作者说需要一些python基础,但是中国程序员是最好的程序员,没有基础照样看,大不了遇到不懂的现学就是喽

需要在计算机上安装一些工具

  • Python环境及包管理工具pip
  • 一款浏览器,推荐Chrome,当然,用自己喜欢的浏览器也可以
  • 版本控制,推荐用Git,但是很多培训机构出来的只会SVN,所以这个没有什么重要的
  • 一款IDE,我用的是pycharm,个人感觉还挺好用的

 Django简介

用来充字数的段落而已,估计读技术书籍的没人关心,值得一提的是Django是一个MTV架构,以前或许会有面试官问问MVC之类的代表什么含义,但现在各种框架,各种标准,已经没法记了,但大体意思是将视图层分为两层,一层Template模板,一层View视图层,感觉有点多此一举

Django应用架构

Django每一个模块在内部都称之为APP,每个APP都有自己的三层架构

安装Django

这里有一个新东西,是类似于php的XAMPP或者MAMP的一个集成环境,可以避免机器被污染,还是挺有用的,叫做virtualenv,安装它的话需要使用python的包管理工具pip,如果没有安装pip的,按照下面的命令安装

curl https://bootstrap.pypa.io/get-pip.py | python

 

作者在这里使用的是pip3,也就是python3,但是据我的了解,现在市场,尤其是中国,python3还是没有使用的,python2.7才是王道,所以老老实实的用2.7安装吧

$ pip install virtualenv

 

然后就是要用这个工具创建一个工作区间了

$ mkdir somewhere/virtualenvs
$ virtualenv somewhere/virtualenvs/<project-name> --no-site-packages

 

工作区间名随便起,虽然写着是项目名,但项目名是在后面指定的,然后到相应的目录,启动集成环境

$ cd somewhere/virtualenvs/<project-name>/bin
$ source activate

 

要关闭环境需要使用

$ deactivate

 

虚拟环境和工作区间安装好之后,开始安装Django

$ pip install django

 

下载完成之后,会自己安装,然后Django给我们提供了一个管理工具,可以用它来控制一些东西,和Laravel中的Artisan是一样的效果

创建项目

创建的项目名可以随便起,我这里起名为blog,执行下面代码

$ django-admin startproject blog

 

执行完之后会创建一个blog的文件夹,进入文件夹之后就看到了生成的东西

blogpost,.gitignore,db.sqlite3都是后来生成的,至于里边每个文件都是干什么不适合在一起讲述,后边遇到哪个再解释是干什么的,都则字太多了谁都没有兴趣看。接下来我们就可以运行期一个服务器,来看看第一个成果,执行下面命令

python manage.py runserver

 

如果没有报错,打开浏览器,输入网址http://127.0.0.1:8000,应该就可以看到如下图那样的页面了,一些简单的英文阅读问题应该不大

然后我们需要创建一个管理员可以登录的后台,Django已经自己提供了这个功能,我们先需要运行数据迁移创建数据库,数据迁移是比较新的技术都带着的一项功能,为了项目切换数据库或者部署的时候方便一点,迁移的时候往哪儿迁移就看配置文件了,Django的配置文件是settings.py,在本项目由中应该是位于根目录下的blog文件夹里,打开可以看到如下所示的默认配置

执行下面代码,就会在根目录看到新创建的数据库db.sqlite3了

$ python manage.py migrate

 

然后创建一个超级管理员账号,注意此处密码最少要8位,再也不是当年的一个1可以解决的了

$ python manage.py createsuperuser

 

创建完成之后就可以打开浏览器看一看了

 

写到这儿差不多该出去遛个弯吃个饭,打个炉石啥的了,但就怕走开的这段时间你的电脑突然起火什么的,为了防止代码丢失,所以我们还需要做相应的版本控制,就是我们刚开始说的准备的工具git。作者用的是命令行的git,但我觉的不够直观,所以我直接用IDE里的git,就是Pycharm。打开最下面的Terminal,会看到一个命令行之类的东西,在这里执行命令更直观一点。刚开始使用git的时候需要先初始化一个仓库

git init

 

创建成功之后就可以将所有的文件提交到版本控制里了

git add .

 

.代表所有的文件,但是数据库不能上传到版本控制里,一个是因为太大,另一个是因为如果里边有重要数据,并且你将你的代码在一些开源平台上托管的话,别人就能轻而易举的获得你的数据了,所以使用reset命令来重置数据库的状态

git reset db.sqlite3

 

但是每次这样操作的话会很麻烦,所以需要添加一个忽略文件.gitignore来忽略数据库的修改,为了方便起见直接用vim创建一个文件,并输入相应的信息

vim .gitignore

 

然后将刚刚创建的忽略文件添加到版本控制中去

git add .gitignore

 

然后提交代码到本地

git commit -m "init project"

 

引号中的内容就是提交信息,如果你想把代码存储到一些远程仓库里去的话就需要将代码push上去,但如果你没有事先配置的话世界使用push会报错,所以我直接使用IDE提供的引入版本控制功能

输入你的用户名和密码就可以连接到github然后将代码push到github上从而让更多的人看到了。

然后我们需要创建一个博文模块,名字是blogpost

django-admin startapp blogpost

 

然后创建博文的model,打开models.py,然后输入下面代码

from __future__ import unicode_literals

from django.db import models
from django.db.models import permalink


# Create your models here.
class Blogpost(models.Model):
    title = models.CharField(max_length=100, unique=True)
    author = models.CharField(max_length=100, unique=True)
    slug = models.CharField(max_length=100, unique=True)
    body = models.TextField()
    posted = models.DateField(db_index=True, auto_now_add=True)

    def __unicode__(self):
        return \'%s\' % self.title

    @permalink
    def get_absolute_url(self):
        return (\'view_blog_post\', None, {\'slug\': self.slug})

 

__unicode__函数是用来实现unicode功能的,当对Blogpost对象使用unicode的时候,就会返回它的title.db_index是讲posted设置为索引,auto_now_add是设置时间为添加时的时间,修改后时间不会动。然后作者在这里注册出了问题,害的我辛苦了好久不见成功。注册的时候是需要在blog/settings.py文件中注册,而不是在作者所谓的admin中注册。打开settings.py,将blogpost写入INSTALLER_APPS中,如下所示

# Application definition

INSTALLED_APPS = [
    \'django.contrib.admin\',
    \'django.contrib.auth\',
    \'django.contrib.contenttypes\',
    \'django.contrib.sessions\',
    \'django.contrib.messages\',
    \'django.contrib.staticfiles\',
    \'blogpost\'
]

 

然后需要在管理员管理界面能看到博文模块,所以需要在admin.py中注册blogpost模块,打开blogpost/admin.py,把它编辑成这样

from django.contrib import admin

# Register your models here.
from .models import Blogpost


class BlogpostAdmin(admin.ModelAdmin):
    exclude = [\'posted\']
    prepopulated_fields = {\'slug\': (\'title\',)}


admin.site.register(Blogpost, BlogpostAdmin)

 

 exclude用来排除掉posted字段,prepopulated_fields指定博文的slug和title是一样的。接着我们需要做数据库迁移,好将生成的模型迁移到数据库中

python manage.py migrate

 

打开浏览器就能看到如下结果

完成一部分,将代码推送到Github上,提交信息写了“创建博文模块”

现在需要修改相应的路由来访问博客,Django的路由在blog/urls.py中,但是一路过来感觉作者在这儿的顺序有点乱,官方文档和序贯都是先写出了view再创建路由,而作者直接创建了路由,让我在阅读的时候很是苦恼,作者这儿为什么要这么写。所以我决定先创建视图,再去修改路由。

首先创建博客列表页,打开blog/views.py,添加index视图,显示博客页的列表

from django.shortcuts import render, render_to_response, get_object_or_404
from blogpost.models import Blogpost


# Create your views here.
def index(request):
    return render_to_response(\'index.html\', {\'posts\': Blogpost.objects.all()[:5]})

 

然而这还是不够的,这页是这个框架比较糟糕的地方,还需要再写一个模板才能显示出来,首先在blogpost文件夹下创建一个templates的文件夹,用来存放相应的模板文件,Django将会在这里查找模板文件,Django不会自己创建也是醉了,官方的建议是在这个文件夹中再创建一个名字为blogpost的文件夹,怕命名污染,感觉这里有点违背python的设计理念,可能是我技术不够,还无法体会这个框架的优点。所以按照官方的方法来,创建好对应的文件夹,然后在里面创建一个index.html的文件,如下

{% extends \'base.html\' %}
{% block title %}
    Welcome to my blog
{% endblock %}

{% block content %}
    <h1>Posts</h1>
    {% for post in posts %}
        <h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
        <p>{{ post.posted }} - By {{ post.author }}</p>
        <p>{{ post.body }}</p>
    {% endfor %}

{% endblock %}

 

在这段代码里显然作者用到了一个叫base.html的页面,然后作者很不负责的依然没有给出来,我去他的源代码中扒出了这个页面,现在将它放在templates目录下 ,代码如下

{% load staticfiles %}
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>{% block head_title %}Welcome to my blog{% endblock %}</title>
    <link rel="stylesheet" type="text/css" href="{% static \'css/bootstrap.min.css\' %}">
    <link rel="stylesheet" type="text/css" href="{% static \'css/styles.css\' %}">
</head>
<body data-twttr-rendered="true" class="bs-docs-home">
<header class="navbar navbar-static-top bs-docs-nav" id="top" role="banner">
    <div class="container">
        <div class="navbar-header">
            <button class="navbar-toggle collapsed" type="button" data-toggle="collapse"
                    data-target=".bs-navbar-collapse">
                <span class="sr-only">切换视图</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a href="/" class="navbar-brand">Growth博客</a>
        </div>
        <nav class="collapse navbar-collapse bs-navbar-collapse" role="navigation">
            <ul class="nav navbar-nav">
                <li>
                    <a href="/pages/about/">关于我</a>
                </li>
                <li>
                    <a href="/pages/resume/">简历</a>
                </li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a href="/admin" id="loginLink">登入</a></li>
            </ul>
            <div class="col-sm-3 col-md-3 pull-right">
                <form class="navbar-form" role="search">
                    <div class="input-group">
                        <input type="text" id="typeahead-input" class="form-control" placeholder="Search" name="search" data-provide="typeahead">
                        <div class="input-group-btn">
                            <button class="btn btn-default search-button" type="submit"><i class="glyphicon glyphicon-search"></i></button>
                        </div>
                    </div>
                </form>
            </div>
        </nav>
    </div>
</header>
<main class="bs-docs-masthead" id="content" role="main">
    <div class="container">
        <div id="carbonads-container">
            THE ONLY FAIR IS NOT FAIR <br>
            ENJOY CREATE & SHARE
        </div>
    </div>
</main>
<div class="container" id="container">
    {% block content %}

    {% endblock %}
</div>
<footer class="footer">
    <div class="container">
        <p class="text-muted">@Copyright Phodal.com</p>
    </div>
</footer>
<script src="{% static \'js/jquery.min.js\' %}"></script>
<script src="{% static \'js/bootstrap.min.js\' %}"></script>
<script src="{% static \'js/bootstrap3-typeahead.min.js\' %}"></script>
<script src="{% static \'js/main.js\' %}"></script>
</body>
</html>

 

然后,我们还需要一个现实详情的视图,编辑views.py

from django.shortcuts import render, render_to_response, get_object_or_404
from blogpost.models import Blogpost


# Create your views here.
def index(request):
    return render_to_response(\'index.html\', {\'posts\': Blogpost.objects.all()[:5]})


def view_post(request, slug):
    return render_to_response(\'blogpost_detail.html\', {
        \'post\': get_object_or_404(Blogpost, slug=slug)
    })

 

然后就可以编写url使其可以访问了,访问之后发现样式全是错的,去作者的Github上找到了样式文件夹static,放到根目录下,最后得到的效果图如下

提交代码,准备编写单元测试。

先来一个简单的测试,测试首页,在blogpost目录下编辑tests.py文件

from django.core.urlresolvers import resolve
from django.test import TestCase
from blogpost.views import index


# Create your tests here.
class HomePageTest(TestCase):
    def test_root_url_resolves_to_home_page_view(self):
        found = resolve(\'/blog/\')
        self.assertEqual(found.func, index)

 

运行测试

python manage.py test

 

结果显示OK

进行下一个测试,测试页面标题是不是我们想要的结果

from django.core.urlresolvers import resolve
from django.http import HttpRequest
from django.test import TestCase
from blogpost.views import index, view_post


# Create your tests here.
class HomePageTest(TestCase):
    def test_root_url_resolves_to_home_page_view(self):
        found = resolve(\'/blog/\')
        self.assertEqual(found.func, index)

    def test_home_page_returns_correct_html(self):
        request = HttpRequest
        response = index(request)
        self.assertIn(b\'<title>Welcome to my blog</title>\', response.content)

 

再添加一个测试测试详情页

from datetime import datetime

from django.core.urlresolvers import resolve
from django.http import HttpRequest
from django.test import TestCase

from blogpost.models import Blogpost
from blogpost.views import index, view_post


# Create your tests here.
class HomePageTest(TestCase):
    def test_root_url_resolves_to_home_page_view(self):
        found = resolve(\'/blog/\')
        self.assertEqual(found.func, index)

    def test_home_page_returns_correct_html(self):
        request = HttpRequest
        response = index(request)
        self.assertIn(b\'<title>Welcome to my blog</title>\', response.content)


class BlogpostTest(TestCase):
    def test_blogpost_url_resolves_to_blog_post_view(self):
        found = resolve(\'/blog/this_is_a_test.html\')
        self.assertEqual(found.func, view_post)

    def test_blogpost_create_with_view(self):
        Blogpost.objects.create(title=\'hello\', author=\'admin\', slug=\'this_is_a_test\', body=\'This is a blog\',
                                posted=datetime.now())
        response = self.client.get(\'/blog/this_is_a_test.html\')
        self.assertIn(b\'This is a blog\', response.content)

 

运行测试,得

写完了单元测试,还需要写一些集成测试,这里使用的是一款叫做Selenium的软件,原本就想用这款软件做一些测试,但是不怎么会用,现在正好学习一下。使用之前要先安装Selenium,直接使用pip安装即可

然后编写测试,自动化测试首页是否包含”Welcome to my blog“

from datetime import datetime

from django.core.urlresolvers import resolve
from django.http import HttpRequest
from django.test import TestCase

from blogpost.models import Blogpost
from blogpost.views import index, view_post
from django.test import LiveServerTestCase
from selenium import webdriver


# Create your tests here.
class HomePageTest(TestCase):
    def test_root_url_resolves_to_home_page_view(self):
        found = resolve(\'/blog/\')
        self.assertEqual(found.func, index)

    def test_home_page_returns_correct_html(self):
        request = HttpRequest
        response = index(request)
        self.assertIn(b\'<title>Welcome to my blog</title>\', response.content)


class BlogpostTest(TestCase):
    def test_blogpost_url_resolves_to_blog_post_view(self):
        found = resolve(\'/blog/this_is_a_test.html\')
        self.assertEqual(found.func, view_post)

    def test_blogpost_create_with_view(self):
        Blogpost.objects.create(title=\'hello\', author=\'admin\', slug=\'this_is_a_test\', body=\'This is a blog\',
                                posted=datetime.now())
        response = self.client.get(\'/blog/this_is_a_test.html\')
        self.assertIn(b\'This is a blog\', response.content)


class HomepageTestCase(LiveServerTestCase):
    def setUp(self):
        self.selenium = webdriver.Firefox()
        self.selenium.maximize_window()
        super(HomepageTestCase, self).setUp()

    def tearDown(self):
        self.selenium.quit()
        super(HomepageTestCase, self).tearDown()

    def test_visit_homepage(self):
        self.selenium.get(\'%s%s\' % (self.live_server_url, "/blog"))
        self.assertIn("Welcome to my blog", self.selenium.title)

 

运行测试,ffirefox快速的一闪而过,程序正确运行,出现OK,然后继续测试博客详情页

class BlogpostDetailCase(LiveServerTestCase):
    def setUp(self):
        Blogpost.objects.create(
            title=\'hello\',
            author=\'admin\',
            slug=\'this_is_a_test\',
            body=\'This is a blog\',
            posted=datetime.now
        )
        self.selenium = webdriver.Firefox()
        self.selenium.maximize_window()
        super(BlogpostDetailCase, self).setUp()

    def tearDown(self):
        self.selenium.quit()
        super(BlogpostDetailCase, self).tearDown()

    def test_vist_blog_post(self):
        self.selenium.get(\'%s%s\' % (self.live_server_url, "/blog/this_is_a_test.html"))
        self.assertIn("hello", self.selenium.title)

 

然后测试用户首页点击博客标题是否能调到对应的博客

class BlogpostFromHomepageCase(LiveServerTestCase):
    def setUp(self):
        Blogpost.objects.create(
            title=\'hello\',
            author=\'admin\',
            slug=\'this_is_a_test\',
            body=\'This is a blog\',
            posted=datetime.now
        )
        self.selenium = webdriver.Firefox()
        self.selenium.maximize_window()
        super(BlogpostDetailCase, self).setUp()

    def tearDown(self):
        self.selenium.quit()
        super(BlogpostDetailCase, self).tearDown()

    def test_visit_blog_post(self):
        self.selenium.get(\'%s%s\' % (self.live_server_url, "/blog"))
        self.selenium.find_element_by_link_text("hello").click()
        self.assertIn("hello", self.selenium.title)

 

测试完没有问题之后,就可以使用上面写的测试搭建集成测试了,作者在这里犯了一个想当然的错误,需要github的推送,就需要将代码部署到服务器上去,在本地的话github的post是收不到的,我在自己的服务器上搭建了jenkins服务器,具体安装没遇到问题,所以就不在这里说了,百度上能找到很多的教程。安装好之后访问服务器的网址加8080端口,就能看到安装页面了,安装的时候会提示一串字符串密码,需要记录下来输入刚开始的页面里。我下载的是2,千万不要下载1的稳定版。

我选择安装所有建议的插件,安装过程确实相当缓慢,可以站起来活动一下去接个水之类的,反正我是这么干的

安装好之后,就是要创建用户名和密码了,然后到了主界面,创建一个任务

源码管理选择git,然后输入git的仓库地址,构建触发器选择Github的那个,然后点击增加构建步骤,选择execute shell,这里又卡了我两天,刚开始是各种命令找不到,如果是按照我说的在linux下的话就不用担心了,下面是我的shell脚本

virtualenv  python
source python/bin/activate
pip install -r requirements.txt
python manage.py test

 

然后想要提交后就出发构建的话,还需要去github里配置钩子

然后当代码提交到github上的时候就可以自动构建了,然后,事情的进展往往会往你最不想看到的结果发展的,结果可视化测试果然出错了,报的错说浏览器没有打开,不过想想linux连桌面系统都没装,自然没法打开。然后安装了一款虚拟桌面软件xfvb,然后再安装python的pyvirtualdisplay的模块,将测试代码修改为如下这样,就可以正常测试了

from datetime import datetime

from django.core.urlresolvers import resolve
from django.http import HttpRequest
from django.test import TestCase

from blogpost.models import Blogpost
from blogpost.views import index, view_post
from django.test import LiveServerTestCase
from selenium import webdriver
from pyvirtualdisplay import Display

#test
class HomePageTest(TestCase):
    def test_root_url_resolves_to_home_page_view(self):
        found = resolve(\'/blog/\')
        self.assertEqual(found.func, index)

    def test_home_page_returns_correct_html(self):
        request = HttpRequest
        response = index(request)
        self.assertIn(b\'<title>Welcome to my blog</title>\', response.content)


class BlogpostTest(TestCase):
    def test_blogpost_url_resolves_to_blog_post_view(self):
        found = resolve(\'/blog/this_is_a_test.html\')
        self.assertEqual(found.func, view_post)

    def test_blogpost_create_with_view(self):
        Blogpost.objects.create(title=\'hello\', author=\'admin\', slug=\'this_is_a_test\', body=\'This is a blog\',
                                posted=datetime.now())
        response = self.client.get(\'/blog/this_is_a_test.html\')
        self.assertIn(b\'This is a blog\', response.content)


class HomepageTestCase(LiveServerTestCase):
    def setUp(self):
        self.display = Display(visible=0, size=(1024, 768))
        self.display.start()
        self.selenium = webdriver.Firefox()
        self.selenium.maximize_window()
        super(HomepageTestCase, self).setUp()

    def tearDown(self):
        self.selenium.quit()
        self.display.stop()
        super(HomepageTestCase, self).tearDown()

    def test_visit_homepage(self):
        self.selenium.get(\'%s%s\' % (self.live_server_url, "/blog"))
        self.assertIn("Welcome to my blog", self.selenium.title)


class BlogpostDetailCase(LiveServerTestCase):
    def setUp(self):
        Blogpost.objects.create(
            title=\'hello\',
            author=\'admin\',
            slug=\'this_is_a_test\',
            body=\'This is a blog\',
            posted=datetime.now
        )
        self.display = Display(visible=0, size=(1024, 768))
        self.display.start()
        self.selenium = webdriver.Firefox()
        self.selenium.maximize_window()
        super(BlogpostDetailCase, self).setUp()

    def tearDown(self):
        self.selenium.quit()
        self.display.stop()
        super(BlogpostDetailCase, self).tearDown()

    def以上是关于Django创建博客应用的主要内容,如果未能解决你的问题,请参考以下文章

纯django开发博客系统

博客基础_django_python从入门到实践_小结

简易博客开发----django1.9 博客部署到pythonanywhere上

Django博客教程之一:创建网站项目

使用django搭建个人博客

个人博客系统!!!