最近在看一篇全栈增长工程师实战,然后学习里面的项目,结果发现作者用的技术太过老旧,好多东西都已经被抛弃了,所以结合着官方文档和自己的一些理解将错误的信息替换一下,边写边学习
准备工作和工具
作者说需要一些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)
测试完没有问题之后,就可以使用上面写的测试搭建集成测试了,做集成测试需要安装Java的一款工具,Jenkins,由于我使用的是mac,所以可以直接使用brew安装,非常方便
brew cask install jenkins
安装完成之后会自动启动并打开
然后我们需要继承Github插件,然后将代码提交到Github之后就可以自动运行测试了
添加GitHub plugin和