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提供的引入版本控制功能

Django创建博客应用_第1张图片

输入你的用户名和密码就可以连接到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

 

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

Django创建博客应用_第2张图片

完成一部分,将代码推送到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的文件,如下

Django创建博客应用_第3张图片

{% 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)

 

运行测试,得

Django创建博客应用_第4张图片

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

Django创建博客应用_第5张图片

然后编写测试,自动化测试首页是否包含”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)

 

测试完没有问题之后,就可以使用上面写的测试搭建集成测试了,做集成测试需要安装Java的一款工具,Jenkins,由于我使用的是mac,所以可以直接使用brew安装,非常方便

brew cask install jenkins

 

安装完成之后会自动启动并打开

然后我们需要继承Github插件,然后将代码提交到Github之后就可以自动运行测试了

添加GitHub plugin和

你可能感兴趣的:(Django创建博客应用)