Django1.7开发博客


开篇

笔者用过django一段时间了,是时候做点笔记了。不过官网文档稍微有点复杂,对新手而言很困难, 而网上的一些教程很多都过时了。 最近看到一个外文的教程非常不错, 网址是:http://tutorial.djangogirls.org/, 这个是基于最新的django1.7写的,通俗易懂,非常适合新手入门。 那么我自己参考这个整理了一下这个教程,同时还将源码上传到GitHub上去了。 希望对于大家有帮助。教程中如果有不足之处希望大家不吝赐教 ^_^

参考教程:http://tutorial.djangogirls.org/

GitHub项目地址:https://github.com/yidao620c/simpleblog

Heroku演示地址:https://yidaoblog.herokuapp.com/

非常期待有人合作一起完成正式版1.0。目前有35个人fork,但暂时还木有收到任何的pull requests。→_→

Django是神马?

Django是一个开源免费的Web框架,使用Python编写。能够让你快速写出一个Web应用, 因为它包含了绝大部分的组件,比如认证,表单,ORM,Session,安全,文件上传,页面模板等,避免了重复造轮子。

官方网站:https://www.djangoproject.com/

笔者写这篇教程的时候,最新版本是1.7.

安装Django1.7

安装python虚拟环境:

为了开发应用的时候使用单独的环境,最好是安装virtual environment, 这样有很好的独立性,可以在里面乱搞而不会影响到其他的应用开发。

下面我以cetnos6.5测试环境为例子介绍怎样去安装python的virtual environment, 该测试机的IP地址是192.168.203.95。

1,先安装python3,最新版本是3.4.2

centos6.5上面默认没有安装python3,那么需要先安装python3。 注意不能简单的使用yum去安装。关于这个教程,可以去网上搜索下。

笔者给出一个参考:install-python3-on-centos6

2, 安装virtualenv

1
pip3 install virtualenv

关于virtualenv的详细说明,请参考文档:virtualenv

3,创建一个文件夹叫djangogirls

1
2
mkdir djangogirls
cd djangogirls

4,创建虚拟环境myenv

1
python3 -m venv myvenv

5,激活虚拟环境

1
source myvenv/bin/activate

如果看到下面这个提示,说明成功进入了虚拟环境:(myvenv) ~/djangogirls$

这时候可以使用python来代替python3了。

6,在虚拟环境中安装django1.7

1
2
3
4
5
(myvenv) ~$ pip install django==1.7
Downloading/unpacking django==1.7
Installing collected packages: django
Successfully installed django
Cleaning up...

OK,到此为止,django1.7环境已经搞定了。

生成项目骨架

接下来一步是生成项目骨架,Django为我们提供了很多有用的脚本让我们可以很方便的使用简单的命令即可生成基本的目录和文件。

对于生产的文件和目录名称请不要随意去修改,也不要随意去移动文件的位置, 因为这些都是约定好的。Django会根据特定的结构去查找对应的文件。

假设你已经在刚刚的djangogirls目录中了,那么执行下面的命令:

1
(myvenv) [mango@centos00 djangogirls]$ django-admin.py startproject mysite

会自动在djangogirls目录中生成一个mysite目录,进入mysite目录,会是下面的结构:

myste
├───manage.py
└───mysite
        settings.py
        urls.py
        wsgi.py
        __init__.py

manage.py是管理网站的脚本,可以使用它来启动一个简单的web服务器,这个对于开发调试非常有用。

setting.py是工程的核心配置文件。

urls.py是路径配置文件,可以配置URL到实际Controller的映射关系。

修改默认配置

我们可以试着去修改下setting.py配置文件中的时区配置,改为你所在的地区的时区。 关于时区可以参考:http://en.wikipedia.org/wiki/List_of_tz_database_time_zones 因为我现在在广州地区,所以把它改成了这样:

1
2
LANGUAGE_CODE = 'zh-cn'
TIME_ZONE = 'Asia/Shanghai'

配置数据库

目前使用默认的sqlite3即可,最简单,什么依赖都没有。

1
2
3
4
5
6
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

为我们的博客系统生成数据库,我们需要运行下面的命令:

1
(myvenv) [mango@centos00 mysite]$ python manage.py migrate

出现如下的信息表示成功了:

Operations to perform:
  Apply all migrations: sessions, contenttypes, admin, auth
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying sessions.0001_initial... OK

运行服务器

接下来我们通过manage.py来运行服务器

1
(myvenv) [mango@centos mysite]$ python manage.py runserver 192.168.203.95:8000

然后在浏览器中打开这个地址:http://192.168.203.95:8000/

按CTRL+C可以停止服务器

如果你看到下面这个页面,那么恭喜你,成功入门。

    什么是模型?

    django的模型就是用于在数据库中存储的某种类型的对象。在我们的博客系统中, 发表的文章就是一个模型,需要存储在数据库中。 这里我们使用django默认的sqlite3库,对于我们的这个小系统而言已经足够了。

    创建一个应用

    在django中有两个概念需要弄清楚。一个是工程(project)的概念,一个是应用(application)的概念。 它们的关系是:一个工程中包含多个应用。每个应用都是独立的,应用通过setting.py注册到工程中来就可以使用了。 这样可以解耦合,并且好的应用也可以复用。很好的模块化设计!

    在manage.py文件所在目录,执行下面命令:

    1
    
    (myvenv) [mango@centos00 mysite]$ python manage.py startapp blog

    你会看到一个新的blog文件夹被创建,并且下面多了许多文件,目前结构如下:

    mysite
    ├── mysite
    |       __init__.py
    |       settings.py
    |       urls.py
    |       wsgi.py
    ├── manage.py
    └── blog
        ├── migrations
        |       __init__.py
        ├── __init__.py
        ├── admin.py
        ├── models.py
        ├── tests.py
        └── views.py
    

    然后在setting.py中注册这个应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'blog',
    )
    

    创建一个博客文章的模型

    在blog/models.py中定义所有的模型,用vim打开后添加下面的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    from django.db import models
    from django.utils import timezone
    from django.contrib.auth.models import User
    
    class Post(models.Model):
        author = models.ForeignKey(User)
        title = models.CharField(max_length=200)
        text = models.TextField()
        created_date = models.DateTimeField(default=timezone.now)
        published_date = models.DateTimeField(blank=True, null=True)
    
        def publish(self):
            self.published_date = timezone.now()
            self.save()
    
        def __str__(self):
            return self.title
    

    我们定义了作者,标题,文章内容,创建时间,发布时间等。还添加了一个publish方法用于保存发布。 每个字段都需要定义它的类型,这里的几个类型解释如下:

    CharField:普通的文本
    TextField:长文本
    DateTimeField:日期时间类型
    ForeignKey:外键类型
    

    详细的字段类型说明请参考官方文档:field-types

    在数据库中为模型生成表结构:

    每次我们新建了一个模型后,需要在数据库中添加对应的表。

    第一步是先让django感知到我们刚刚已经创建了一个新的模型:

    1
    
    (myvenv) [mango@centos00 mysite]$ python manage.py makemigrations blog
    

    输出如下:

    Migrations for 'blog':
      0001_initial.py:
        - Create model Post
    

    这时候django已经为我们准备好了数据库更新的sql文件。

    第二步是让django帮我们执行这些文件:

    1
    
    (myvenv) [mango@centos00 mysite]$ python manage.py migrate blog
    

    输出如下:

    Operations to perform:
      Apply all migrations: blog
    Running migrations:
      Applying blog.0001_initial... OK
    

    OK,这时候数据库中已经有post这张表了。

    对象关系映射ORM

    下面我们看看django的ORM是怎样和数据库打交道的。

    首先解释下QuerySet,它是某个模型的对象列表,用来从数据库中读取数据,过滤和排序等等。

    Django控制台Django Shell

    执行以下命令可以打开django的控制台

    1
    
    (myvenv) [mango@centos00 mysite]$ python manage.py shell
    

    查询所有的博客文章

    1
    2
    3
    
    >>> from blog.models import Post
    >>> Post.objects.all()
    []
    

    这时候肯定是空的,因为我们还没有任何数据。

    下面我们去新建几篇文章

    先要创建一个用户,因为Post里面有User外键

    1
    2
    3
    4
    5
    
    >>> from django.contrib.auth.models import User
    >>> User.objects.create(username='ola')
    <User: ola>
    >>> User.objects.all()
    [<User: ola>]
    

    然后添加文章:

    1
    2
    3
    4
    5
    
    >>> user = User.objects.get(username='ola')
    >>> Post.objects.create(author = user, title = 'Sample title', text = 'Test')
    <Post: Sample title>
    >>> Post.objects.all()
    [<Post: Sample title>]
    

    文章过滤

    先像前面那样再添加几篇文章,此处省略七十二个字….

    1
    2
    3
    4
    5
    6
    
    >>> Post.objects.filter(author=user)
    [<Post: Sample title>, <Post: Cool day Aha>, <Post: LiLei and Hanmeimei>, <Post: Happy boy>]
    >>> Post.objects.filter(title__contains='LiLei')
    [<Post: LiLei and Hanmeimei>]
    >>> Post.objects.filter(published_date__isnull=False)
    []
    

    最后的输出表示所有的文章published_date都是空的。我想改变这个情况。可以这样做

    1
    2
    3
    4
    
    >>> post = Post.objects.get(id=1)
    >>> post.publish()
    >>> Post.objects.filter(published_date__isnull=False)
    [<Post: Sample title>]
    

    结果排序

    还可以根据某个或某几个字段来排序检索结果

    1
    2
    3
    4
    
    >>> Post.objects.order_by('created_date')
    [<Post: Sample title>, <Post: Cool day Aha>, <Post: LiLei and Hanmeimei>, <Post: Happy boy>]
    >>> Post.objects.order_by('-created_date')
    [<Post: Happy boy>, <Post: LiLei and Hanmeimei>, <Post: Cool day Aha>, <Post: Sample title>]
    

    OK,目前为止你应该对django的ORM有了大体的了解了。

    请用exit()退出django的控制台

    1
    
    >>> exit()
    

    利用django admin修改模型

    在上面我们已经创建了Post模型并且通过django控制台来添加修改模型。 然后我们使用django自带的web管理界面admin来在页面上修改模型数据。

    模型注册

    首先我们需要在admin中注册对应的模型,打开blog/admin.py文件,修改如下

    1
    2
    3
    4
    
    from django.contrib import admin
    from .models import Post
    
    admin.site.register(Post)
    

    注意上面的from .models import Post,使用到了python3的相对导入。

    运行服务器

    一切都这么简单,接下来我们启动服务器

    1
    
    (myvenv) [mango@centos mysite]$ python manage.py runserver 192.168.203.95:8000
    

    打开链接:http://192.168.203.95:8000/admin/

    看到下面的界面:

    添加管理员

    不过你需要一个管理员才能登录。运行python manage.py createsuperuser可以创建管理员账号。

    1
    2
    3
    4
    5
    6
    
    (myvenv) [mango@centos00 mysite]$ python manage.py createsuperuser
    Username (leave blank to use 'mango'): admin
    Email address: admin@gmail.com
    Password:
    Password (again):
    Superuser created successfully.
    

    我创建了一个admin/admin的账户。这时候登录

    点击Posts修改或者增加等等,确保里面至少2个又published_date,这个后面会用到。

    更多关于django admin的内容,参考官方文档:admin

    接下来呢?

    先别急嘛,坐下来喝杯咖啡先。到现在为止你已经前进了很远了。



    到目前为止,你的网站只能在你自己的电脑上访问到。你需要将它发布到公网上去让地球上的人都能看到,那么要怎么做呢?

    Heroku简介

    Heroku是一个主机托管平台,对于访问量不是很大的小应用是免费的,正好适用于我们的这个例子。

    Heroku官网上有一篇django的教程: getting started with django

    这里我把它复制到这里来详细讲解一下。

    requirements.txt文件

    我们需要创建一个requirements.txt文件来告知Heroku需要在服务器上创建哪些python包。

    不过首先,Heroku需要我们在本地按照一些包。在python虚拟环境virtualenv下面执行:

    1
    
    (myvenv) [mango@centos mysite]$ pip install dj-database-url gunicorn whitenoise

    然后,在mysite目录,也就是manage.py所在目录执行:

    1
    
    (myvenv) [mango@centos00 mysite]$ pip freeze > requirements.txt

    这条命令会新建一个requirements.txt文件,里面包含了工程的所有依赖包。

    打开这个文件,添加下面这句话:

    1
    
    psycopg2==2.5.4

    要想在Heroku上面运行,必须加上这句。

    Procfile文件

    另一个需要创建的文件就是Procfile,它告知Heroku在启动网站时需要执行的命令。 同样在manage.py所在目录,新建一个文件名为Procfile,添加如下内容:

    1
    
    web: gunicorn mysite.wsgi

    这句话的意思就是我们将要部署一个web应用程序,并且我们通过执行 gunicorn mysite.wsgi命令来完成。

    gunicorn是一个类似django的runserver的程序,但是功能更强大。

    runtime.txt文件

    我们需要告知Heroku运行时的python版本,这里我们指定为3.4.2。新建runtime.txt,加入

    1
    
    python-3.4.2

    mysite/local_settings.py文件

    由于我们本地开发使用的数据库跟服务器上部署时使用的数据库是不一样的。 那么我们需要创建一个local_settings.py来指定本地用的数据库。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    import os
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }
    }
    
    DEBUG = True
    

    mysite/settings.py文件

    另外我们还需要修改settings.py文件,在文件结尾添加如下几行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    import dj_database_url
    DATABASES['default'] =  dj_database_url.config()
    
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
    
    ALLOWED_HOSTS = ['*']
    
    STATIC_ROOT = 'staticfiles'
    
    DEBUG = False
    

    然后再导入本地开发配置,也是在最后添加

    1
    2
    3
    4
    
    try:
        from .local_settings import *
    except ImportError:
        pass
    

    mysite/wsgi.py文件

    打开mysite/wsgi.py文件,添加如下几行到最后:

    1
    2
    
    from whitenoise.django import DjangoWhiteNoise
    application = DjangoWhiteNoise(application)
    

    OK,工程配置修改做完了。

    Heroku账号

    首先你需要安装Heroku toolbelt,教程地址:https://toolbelt.heroku.com/

    两句话搞定:

    1
    2
    
    wget -qO- https://toolbelt.heroku.com/install.sh | sh
    echo 'PATH="/usr/local/heroku/bin:$PATH"' >> ~/.profile
    

    然后申请一个账号:https://id.heroku.com/signup/www-home-top

    登录:

    1
    
    heroku login
    

    如果报bad URI错误的话,那么代理设置必须是http://192.168.203.91:3128/这样的完整形式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    [mango@centos00 work]$ heroku login
    Enter your Heroku credentials.
    Email: yidao620@gmail.com
    Password (typing will be hidden):
    Your Heroku account does not have a public ssh key uploaded.
    Found an SSH public key at /home/mango/.ssh/id_rsa.pub
    Would you like to upload it to Heroku? [Yn] Y
    Uploading SSH public key /home/mango/.ssh/id_rsa.pub... done
    Authentication successful.
    

    .gitignore文件

    我们并不想将所有文件都上传到服务器上, 比如我们本地设置local_setttings.py,数据库文件等。 在我们工程主目录下面创建一个.gitignore文件,内容如下

    myvenv
    __pycache__
    *.pyc
    staticfiles
    local_settings.py
    db.sqlite3
    

    部署到Heroku

    第一步先创建一个工程

    1
    
    (myvenv) [mango@centos00 mysite]$ heroku create yidaoblog
    

    返回结果

    Creating yidaoblog... done, stack is cedar
    https://yidaoblog.herokuapp.com/ | git@heroku.com:yidaoblog.git
    

    请记住上面的地址git@heroku.com:yidaoblog.git

    第二步初始化git本地仓库提交,并添加上面的远程仓库地址

    1
    2
    3
    4
    5
    
    [mango@centos00 mysite]$ git init
    初始化空的 Git 版本库于 /home/mango/work/djangogirls/mysite/.git/
    [mango@centos00 mysite]$ git add .
    [mango@centos00 mysite]$ git commit -m 'yidao blog by django.'
    [mango@centos00 mysite]$ git remote add origin git@heroku.com:yidaoblog.git
    

    第三步push工程

    1
    
    [mango@centos00 mysite]$ git push origin master
    

    最后得到类似下面的输出:

    Successfully installed Django dj-database-url gunicorn whitenoise psycopg2
         Cleaning up...
    
    -----> Preparing static assets
           Running collectstatic...
           61 static files copied to '/app/staticfiles'.
    
    -----> Discovering process types
           Procfile declares types -> web
    
    -----> Compressing... done, 39.6MB
    -----> Launching... done, v5
           https://yidaoblog.herokuapp.com/ deployed to Heroku
    
    To git@heroku.com:yidaoblog.git
     * [new branch]      master -> master
    

    大功告成。

    访问应用

    让Heroku启动你的应用

    1
    
    $ heroku ps:scale web=1 --app yidaoblog
    

    启动一个实例即可

    同步数据库(这一步是必须要做的,否则会报500错误):

    1
    
    $ heroku run:detached python manage.py migrate --app yidaoblog
    

    不过我运行后,只是表结构同步了,本地数据并没有同步,所以还要进行数据同步。 试了好多教程都不尽如人意。最后一个最好的方案是本地直接使用postgresql开发,然后使用import技术。

    参考教程: https://devcenter.heroku.com/articles/heroku-postgres-import-export

    如果本地要使用postgresql开发的话,local_settings.py文件需要修改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    import os
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': 'django',
            'USER': 'postgres',
            'PASSWORD': 'postgres',
            'HOST': '192.168.203.95',
            'PORT': '5432',
        }
    }
    
    DEBUG = True
    

    在CentOS6上面安装postgresql9.3

    1
    2
    3
    
    sudo yum install python-devel
    export PATH=/usr/pgsql-9.3/bin/:$PATH
    pip install psycopg2
    

    出现pg_config命令找不到的时候可以先find:

    1
    
    sudo find /usr -name pg_config
    

    然后添加到PATH中去:

    1
    
    export PATH=/usr/pgsql-9.3/bin/:$PATH
    

    使用postgres用户登录后备份数据库:

    1
    2
    
    su - postgres(postgres)
    pg_dump dbname > /tmp/mydb.txt
    

    将输出的mydb.txt放到某个静态文件服务器上,必须在公网上可访问,比如七牛存储。 为什么我没有选择AWS S3呢?原因你肯定懂得啦,就算我有了梯子还是不行, 弄了一下午还是没成功,快气晕了。哎…

    使用如下命令进行数据同步:

    1
    
    heroku pgbackups:restore DATABASE_URL 'http://yidaospace.qiniudn.com/mydb.txt'
    

    期间需要你输入你的app名字来确认,并且还需要给你的app添加一个免费的addon:

    1
    
    heroku addons:add pgbackups
    

    查看日志:

    1
    
    (myvenv) [mango@centos00 mysite]$ heroku logs
    

    查看启动的实例

    1
    2
    3
    
    [mango@centos00 mysite]$ heroku ps
    === web (1X): `gunicorn mysite.wsgi`
    web.1: up 2014/11/04 09:40:43 (~ 7m ago)
    

    打开浏览器访问吧! Enjoy it…

    输入网址 https://yidaoblog.herokuapp.com/admin 后的界面如下:

    停止应用的命令



    什么是三部曲?

    其实在django中实现一个功能只需要三个步骤即可,这里我姑且叫它三部曲。

    这三部曲就是:

    1. 定义urls映射
    2. 定义views
    3. 定义templates

    什么是URL?

    URL就算一个WEB地址,你在浏览器输入这个地址,然后浏览器返回相应的网页给你。 比如http://djangogirls.com是一个URL,而127.0.0.1:8000同样也是个URL,默认就是http协议的。

    Django中的URL工作原理

    我们打开mysite/urls.py文件,会发现类似下面这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        # Examples:
        # url(r'^$', 'mysite.views.home', name='home'),
        # url(r'^blog/', include('blog.urls')),
    
        url(r'^admin/', include(admin.site.urls)),
    )
    

    上面的两行注释先不要管,这个以后再用到。 django默认已经为我们添加了admin的URL配置。 当django碰到以admin/开头的URL的时候会去admin.site.urls里面去寻找对应的匹配。 所有和admin相关的urls配置都写在一个文件中,这样就便于管理了。

    正则表达式

    你可以看到上面的url用到了正则表达式,比如’^admin/’、’^$’等等, django是通过正则式来匹配URL的。关于正则式这里不想展开太多。可以参考相关数据和教程。

    第一个django url配置

    现在我们要将http://127.0.0.1:8000/这个首页地址映射到一个显示最新文章列表的页面上面去。一般的博客首页基本都是这样的。

    为了保持mysite/urls.py配置文件的简介,我们最好将博客的url配置放到单独的文件中。在mysite/urls.py中去将它引进来即可。

    那么你的mysite/urls.py文件现在类似于这样了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        url(r'^admin/', include(admin.site.urls)),
        url(r'', include('blog.urls')),
    )
    

    blog.urls

    创建文件blog/urls.py,然后加入下列内容

    1
    2
    3
    4
    5
    6
    
    from django.conf.urls import patterns, include, url
    from . import views
    
    urlpatterns = patterns('',
        url(r'^$', views.post_list),
    )
    

    现在我们将r’^$’的url映射到视图views.post_list。

    不过你要是现在就访问首页http://127.0.0.1:8000/的话会报错的。

    为啥,因为你的视图views.post_list现在没有实现啊,找不到这个方法!

    那么接下来我们就来讲解view的实现了。

    什么是view?

    view也叫视图,在django中它存放了实际的业务逻辑。这个跟我们通常所说的MVC中的view是不一样的。

    django的MTV模式

    这里我稍微解释下django的结构,一般我们称之为MTV模式:

    1. M 代表模型(Model),即数据存取层。该层处理与数据相关的所有事务:如何存取、如何确认有效性、包含哪些行为以及数据之间的关系等。
    2. T 代表模板(Template),即表现层。该层处理与表现相关的决定:如何在页面或其他类型文档中进行显示。
    3. V 代表视图(View),即业务逻辑层。该层包含存取模型及调取恰当模板的相关逻辑。你可以把它看作模型与模板之间的桥梁。

    那么通常意义的控制器Controller去哪里了呢,细心的童鞋应该会猜到了,那就是我们上一节所讲的urls.py配置文件。

    一句话总结:URLconf+MTV构成了django的总体架构。

    blog/views.py

    这个文件初始内容是这样的:

    1
    2
    3
    
    from django.shortcuts import render
    
    # Create your views here.
    

    添加一个最简单的视图:

    1
    2
    3
    
    def post_list(request):
    
        return render(request, 'blog/post_list.html', {})
    

    我们定义了一个方法post_list,它的参数是request,使用render函数返回一个html模板blog/post_list.html。

    接下来我们访问下首页,OMG,又出错了:

    这次报的错是模板blog/post_list.html找不到。这个是显而易见的,因为我们根本还没有定义这个html模板。

    别着急,继续沿着教程往下看就行…

    什么是模板?

    一个模板就是一个使用固定格式呈现动态内容的可重用的文件。 比如你可以使用一个模板来写邮件,每封邮件可能有不同的内容,寄给不同的人,但是它们的格式是一样的。

    Django中的模板使用HTML文件,至于神马是HTML,这个去参考下W3C或者自行google下, 不过如果做web开发的人不懂HTML,请不要告诉别人我认识你。^_^

    ** 第一个模板 **

    创建一个模板就是创建一个HTML文件。模板文件存储在blog/templates/blog目录下面, 首先在blog目录下创建templates目录,然后再在templates目录下创建blog目录,至于为啥要这么做, 先不用管,django里面很多目录都是约定好的,这个就跟maven是一样的,约定高于配置。 所以你先照着做就是了。目录结构如下:

    blog
    └───templates
        └───blog
    

    然后在blog/templates/blog目录下创建一个post_list.html文件,现在里面还没有内容。

    这时候再次访问首页,效果如下:

    一片空白,但没有报错了。

    在post_list.html中添加点东西:

    1
    2
    3
    4
    
    <html>
        <p>Hi there!</p>
        <p>It works!</p>
    </html>
    

    再次访问http://192.168.203.95:8000/:

    动态模板

    不过目前为止我们还只能显示静态的网页。怎样将文章列表在首页显示出来呢?

    我们已经有了模型Post,有了模板post_list.html,怎样使得模型数据在模板中显示出来呢, 这个就是视图的功能了,实际上,django中的视图的作用就是连接模型和模板的桥梁。 在视图中,通过QuerySet将数据库中的数据检索出来,然后传递给模板,模板负责显示出来。

    首先打开blog/views.py,它目前的内容是这样的:

    1
    2
    3
    4
    
    from django.shortcuts import render
    
    def post_list(request):
        return render(request, 'blog/post_list.html', {})
    

    这时候我们将Post模型导入进来

    1
    2
    
    from django.shortcuts import render
    from .models import Post
    

    注意我们还是用到了相对导入,这是python3的强大功能。

    QuerySet

    是时候请出QuerySet了,在模型和ORM小节我们已经介绍过。

    现在我们想要将数据库中的文章都检索出来并且按照发布日期逆序排序,使得最新的文章放前面。

    1
    2
    3
    4
    5
    6
    
    from django.shortcuts import render
    from .models import Post
    
    def post_list(request):
        posts = Post.objects.filter(published_date__isnull=False).order_by('-published_date')
        return render(request, 'blog/post_list.html', {'posts': posts})
    

    注意render函数中最后一个参数{‘posts’: posts},这个就是用来给模板传递数据的。

    模板标签

    HTML页面只识别HTML标签,那么怎样让生成动态的内容呢?答案就是使用django自带的模板标签, 包括了判断、循环、管道等语法。我们已经获取了文章的列表了,那么可以使用for循环来生成相应的HTML页面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <html>
    <head>
        <title>Django Girls Blog</title>
    </head>
    <body>
        <div>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        
    </body>
    </html>
    

    {% for %} 和{% endfor %}之间会循环每个post,然后每次生成一段

    现在再次访问首页,效果如下:

    别忘了一件事

    别忘了把它push到heroku上面去。

    1
    2
    3
    
    git add .
    git commit -m '动态文章列表首页'
    git push origin master
    

    恭喜你,目前为止基本的全程已经贯通了。打开admin后添加几篇文章, 记得填上发布日期,再刷新下首页,看会不会显示出来。

    好了,这时候你可以出门左拐去小卖部给自己买点棒棒糖奖励下自己了!

    不过我们的教程还没完,怎样让页面变得漂亮呢?请看下一节。




    什么是css?

    css是一种用来描述某种标记语言写的web站点的样式语言。 这里我们并不想展开讨论,关于CSS我在这里推荐一个很不错的资源: Codeacademy HTML & CSS course

    不想从头开始写,因为我们有现成的css框架,没必要重复造轮子。

    使用Bootstrap

    目前最流行的css框架非bootstrap莫属了,官网地址:http://getbootstrap.com/

    只需要在你的html模板页面的开始部分添加下面几句就行了

    1
    2
    3
    
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    

    你的工程里面不需要引入任何的文件,因为这里直接引用了bootstrap公共的css和js文件。

    再次打开模板文件,效果如下:

    是不是感觉变美观了。^_^

    django静态文件

    这里我还将讲解下django中的静态文件。静态文件就是css、js、图片、视频等等那些内容不会改变的文件,不管任何时候,对于任何用户都是一样的。

    css就是一种静态文件,为了自定义css,我们必须先再django中配置,你只需要配置一次就可以了。那让我们马上开始吧!

    django中配置静态文件

    首先我们需要创建一个目录来存储静态文件,在manage.py的同级目录中创建一个static文件夹

    mysite
    ├─── static
    └─── manage.py
    

    打开配置文件mysite/settings.py,在最后面添加如下配置:

    1
    2
    3
    
    STATICFILES_DIRS = (
        os.path.join(BASE_DIR, "static"),
    )
    

    它告知django应该在哪个位置去查找静态文件。

    第一个CSS文件

    现在我们开始创建自己的css文件了,首先在static目录中新建一个css目录,然后在里面创建一个blog.css文件。目录结构如下

    static
    └─── css
         └───blog.css
    

    打开文件static/css/blog.css后,添加如下内容

    1
    2
    3
    
    h1 a {
        color: #FCA205;
    }
    

    h1 a是CSS选择器,上面的意思是在h1标签下的链接a的文字颜色会是#FCA205,其实就是橘黄色,颜色都是用十六进制表示的。

    接下来我们要让模板加载静态css文件,打开blog/templates/blog/post_list.html,在最开始部分加入:

    1
    
    {% load staticfiles %}
    

    然后在bootstrap引用的后面添加下面这句

    1
    
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    

    最后,整个模板文件类似这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    {% load staticfiles %}
    <html>
        <head>
            <title>Django Girls blog</title>
            <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
            <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
            <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
            <link rel="stylesheet" href="{% static 'css/blog.css' %}">
        </head>
        <body>
            <div>
                <h1><a href="/">Django Girls Blog</a></h1>
            </div>
    
            {% for post in posts %}
                <div>
                    <p>published: {{ post.published_date }}</p>
                    <h1><a href="">{{ post.title }}</a></h1>
                    <p>{{ post.text|linebreaks }}</p>
                </div>
            {% endfor %}
        </body>
    </html>
    

    OK,保存并刷新后看看效果

    我想要文字左边的边距大一点,这样会好看些。那么在blog.css中添加如下内容:

    1
    2
    3
    
    body {
        padding-left: 15px;
    }
    

    刷新页面后效果:

    我还想自定义文字标题的字体,在post_list.html模板的中添加如下一句

    1
    
    <link href="http://fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">
    

    这句会引入Google的一个字体Lobster,然后修改blog.css中的h1 a的样式如下:

    1
    2
    3
    4
    
    h1 a {
        color: #FCA205;
        font-family: 'Lobster';
    }
    

    刷新后的效果:

    CSS中的class

    在CSS中有一个class的概念,它可以让你只改变HTML中某一部分的样式而不会影响到其他部分。

    这里我们将区别标题头和文章本身的样式。

    在post_list.html中添加如下的标题段:

    1
    2
    3
    
    <div class="page-header">
        <h1><a href="/">Django Girls Blog</a></h1>
    </div>
    

    文章列表段修改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    <div class="content">
        <div class="row">
            <div class="col-md-8">
                {% for post in posts %}
                    <div class="post">
                        <h1><a href="">{{ post.title }}</a></h1>
                        <p>published: {{ post.published_date }}</p>
                        <p>{{ post.text|linebreaks }}</p>
                    </div>
                {% endfor %}
            </div>
        </div>
    </div>
    

    blog.css样式修改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    .page-header {
        background-color: #ff9400;
        margin-top: 0;
        padding: 20px 20px 20px 40px;
    }
    .page-header h1, .page-header h1 a, .page-header h1 a:visited, .page-header h1 a:active {
        color: #ffffff;
        font-size: 36pt;
        text-decoration: none;
    }
    .content {
        margin-left: 40px;
    }
    h1, h2, h3, h4 {
        font-family: 'Lobster', cursive;
    }
    .date {
        float: right;
        color: #828282;
    }
    .save {
        float: right;
    }
    .post-form textarea, .post-form input {
        width: 100%;
    }
    .top-menu, .top-menu:hover, .top-menu:visited {
        color: #ffffff;
        float: right;
        font-size: 26pt;
        margin-right: 20px;
    }
    .post {
        margin-bottom: 70px;
    }
    .post h1 a, .post h1 a:visited {
        color: #000000;
    }
    

    保存这些文件后,刷新页面,看看,是不是很酷了:

    已经比较美观了。上面的css应该看起来不会那么难,可以自己试着去修改它,没关系的,反正出错了可以撤销。最好多练习才行。

    下一节我将介绍django中模板的继承机制!



    什么是模板继承?

    就是网站的多个页面可以共享同一个页面布局或者是页面的某几个部分的内容。 通过这种方式你就需要在每个页面复制粘贴同样的代码了。 如果你想改变页面某个公共部分,你不需要每个页面的去修改,只需要修改一个模板就行了, 这样最大化复用,减少了冗余,也减少了出错的几率,而且你敲的代码也少了。

    创建一个base模板

    一个base模板就是你全站所有页面都会继承的最基本的网站框架模板。我们在blog/templates/blog/中创建一个base.html模板:

    blog
    └───templates
        └───blog
                base.html
                post_list.html
    

    打开base.html,然后将post_list.html的所有内容都复制过来,现在它的内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    {% load staticfiles %}
    <html>
    <head>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <link href="http://fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    <title>Django Girls Blog</title>
    </head>
    <body>
        <div class="page-header">
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        <div class="content">
            <div class="row">
                <div class="col-md-8">
                    {% for post in posts %}
                        <div class="post">
                            <h1><a href="">{{ post.title }}</a></h1>
                            <p>published: {{ post.published_date }}</p>
                            <p>{{ post.text|linebreaks }}</p>
                        </div>
                    {% endfor %}
                </div>
            </div>
        </div>
    </body>
    </html>
    

    在base.html中,将…块替换成下面的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    <body>
        <div class="page-header">
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        <div class="content">
            <div class="row">
                <div class="col-md-8">
                {% block content %}
                {% endblock %}
                </div>
            </div>
        </div>
    </body>
    

    我们其实就是将{% for post in posts %}{% endfor %} 替换成了{% block content %}{% endblock %}。 在base.html中我们创建了一个名字为content的block,其他页面可以通过继承base.html, 替换这个content块来生成新的页面,页面其他内容保持不变。

    保存后,再修改post_list.html页面,只保留的内容:

    1
    2
    3
    4
    5
    6
    7
    
    {% for post in posts %}
        <div class="post">
            <h1><a href="">{{ post.title }}</a></h1>
            <p>published: {{ post.published_date }}</p>
            <p>{{ post.text|linebreaks }}</p>
        </div>
    {% endfor %}
    

    然后添加这句到post_list.html页面的最开始部分:

    1
    
    {% extends 'blog/base.html' %}
    

    这句话的意思就是该模板继承自blog/base.html模板

    还有一步就是要将刚刚的内容放到{% block content %}和 {% endblock content %}之间,这时候整个页面是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    {% extends 'blog/base.html' %}
    {% block content %}
    {% for post in posts %}
        <div class="post">
            <h1><a href="">{{ post.title }}</a></h1>
            <p>published: {{ post.published_date }}</p>
            <p>{{ post.text|linebreaks }}</p>
        </div>
    {% endfor %}
    {% endblock content %}
    

    保存后刷新页面,看下是不是能正常工作:



    到目前为止我们已经完成了一个django应用的所有基础部分。 包括url配置、视图、模型和模板。接下来开始继续完善我们的博客系统了。

    首先我们需要一个显示每篇文章的详细页面,对不?

    文章详情

    对于首页每一篇文章,我们希望点击标题后可以进入该文章的阅读页面。修改post_list.html中的标题href如下:

    1
    
    <h1><a href="{% url 'blog.views.post_detail' pk=post.pk %}">{{ post.title }}</a></h1>
    

    我来详细解释下这个{% url ‘blog.views.post_detail’ pk=post.pk %},{% %} 表示使用django模板标签而不是普通的HTML文字,这里我们使用了url标签来生成真正的url链接。 blog.views.post_detail是视图的全路径。

    url配置

    我们希望文章详细页面的链接类似这样:http://127.0.0.1:8000/post/1/

    修改blog/urls.py为下面的这样:

    1
    2
    3
    4
    5
    6
    7
    
    from django.conf.urls import patterns, include, url
    from . import views
    
    urlpatterns = patterns('',
        url(r'^$', views.post_list),
        url(r'^post/(?P<pk>[0-9]+)/$', views.post_detail),
    )
    

    这个看起来有点复杂,我们来解释下:

    1. ^ 代表的是开始
    2. post/表示URL开始必须是post/
    3. (?P[0-9]+) – 这部分比较复杂。它表示一个命名参数pk, 它会捕获url中的这部分然后将它赋值给pk参数传递给视图。 [0-9]表示这部分必须是数字,+表示至少1个数字,也可以多个数字。
    4. / – 然后后面接/
    5. $ – URL的结尾

    post_detail视图

    现在去访问还会报错,因为我们还没有post_detail这个视图。现在我们开始定义它。

    修改文件blog/views.py如下:

    1
    2
    3
    4
    5
    
    from django.shortcuts import render, get_object_or_404
    
    def post_detail(request, pk):
        post = get_object_or_404(Post, pk=pk)
        return render(request, 'blog/post_detail.html', {'post': post})
    

    post_detail模板

    然后再增加模板blog/templates/blog/post_detail.html:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    {% extends 'blog/base.html' %}
    
    {% block content %}
        <div class="date">
            {% if post.published_date %}
                {{ post.published_date }}
            {% endif %}
        </div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaks }}</p>
    {% endblock %}
    

    这次我们还是采用模板继承方式,这里我们还用到了模板标签if,这是一个条件判断的标签。

    OK,一切都已准备就绪,现在打开首页,然后点击任意一篇文章标题看下结果:

    搞定!!

    创建文章

    最后一件事就是要实现文章创建和更新操作,这是博客系统最核心的功能。django自带的admin很酷,但是确很难定制和美化。 forms非常强大和自由,我们可以好好的利用它来实现我们需要的功能。

    forms一个很好的特性就是它既能从头定义一个表单,也能创建一个ModelForm来将表单结果保存为一个模型。 而这正是我们想要的功能,我们可以创建一个ModelForm来将表单转换为一个Post模型。

    表单对象的定义放在forms.py文件中。我们需要在blog文件夹中创建forms.py文件,结构如下:

    blog
       └── forms.py
    

    在里面写入如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    
    from django import forms
    from .models import Post
    
    class PostForm(forms.ModelForm):
    
        class Meta:
            model = Post
            fields = ('title', 'text',)
    

    PostForm需要继承自forms.ModelForm,这样django就能实现某些神奇的效果。 在里面我们定义了元类Meta,然后指定model为Post,还有字段为title和text。 因为我们只需要对外暴露标题和内容,至于作者就是登陆用户了,而发布日期和创建日期就是提交时间。

    下面我们要做的就是在view中使用我们的form,并在template中显示它。

    我们继续走四个步骤:页面上添加链接, 外加“三部曲”。基本上每个新功能的增加时只需要增加这四个东东就行了。

    增加链接

    打开blog/templates/blog/base.html,在名字为page-header的div中添加一个新增文章的链接:

    1
    
    <a href="{% url 'blog.views.post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
    

    这时候你的base.html应该是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    {% load staticfiles %}
    <html>
    <head>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <link href="http://fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    <title>Django Girls Blog</title>
    </head>
    <body>
        <div class="page-header">
            <a href="{% url 'blog.views.post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        <div class="content">
            <div class="row">
                <div class="col-md-8">
                    {% block content %}
                    {% endblock %}
                </div>
            </div>
        </div>
    </body>
    </html>
    

    URL

    打开blog/urls.py文件,添加一条配置:

    1
    
    url(r'^post/new/$', views.post_new, name='post_new'),
    

    现在它的内容应该是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    
    from django.conf.urls import patterns, include, url
    from . import views
    
    urlpatterns = patterns('',
        url(r'^$', views.post_list),
        url(r'^post/(?P<pk>[0-9]+)/$', views.post_detail),
        url(r'^post/new/$', views.post_new, name='post_new'),
    )
    

    post_new视图

    打开文件blog/views.py,先引入PostForm

    1
    
    from .forms import PostForm
    

    然后增加视图:

    1
    2
    3
    
    def post_new(request):
        form = PostForm()
        return render(request, 'blog/post_edit.html', {'form': form})
    

    post_edit.html模板

    在blog/templates/blog目录新建一个post_edit.html页面,然后写入下列内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    {% extends 'blog/base.html' %}
    
    {% block content %}
        <h1>New post</h1>
        <form method="POST" class="post-form">{% csrf_token %}
            {{ form.as_p }}
            <button type="submit" class="save btn btn-default">Save</button>
        </form>
    {% endblock %}
    

    保存后,刷新首页,点击加号那个链接可以看到如下页面:

    但是当你写了文字后点击保存后会发现又跑到这个新建页面来了。 因为这个是POST提交,但是URL还是一样的,又会跑到post_new那个视图中去, 这个视图只做了页面跳转来到了这个新建页面。

    那么我们需要修改下post_new视图逻辑了:

    在头部先引入下面的依赖:

    1
    2
    
    from django.core.urlresolvers import reverse
    from django.shortcuts import redirect
    

    然后修改post_new视图如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    def post_new(request):
        if request.method == "POST":
            form = PostForm(request.POST)
            if form.is_valid():
                post = form.save(commit=False)
                post.author = request.user
                post.save()
                return redirect('blog.views.post_detail', pk=post.pk)
        else:
            form = PostForm()
        return render(request, 'blog/post_edit.html', {'form': form})
    

    上面表示我添加完一篇文章后自动跳转到文章详情页面去,保存后效果:

    表单验证

    由于我们在Post模型中已经定义了title和text是必需的,django会自动帮我们做验证。 看下如果我们不输入title和text直接提交会怎样:

    django已经自动帮我们做了验证,是不是很酷呢?

    编辑文章

    我们刚刚已经实现了新建文章的功能,那么如果是编辑修改文章呢。接下来我会快速的讲解这个流程,现在你应该是可以看得懂的了。

    首先打开blog/templates/blog/post_detail.html,添加一行:

    1
    2
    
    <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}">
    <span class="glyphicon glyphicon-pencil"></span></a>
    

    现在它的内容是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    {% extends 'blog/base.html' %}
    
    {% block content %}
        <div class="date">
        {% if post.published_date %}
            {{ post.published_date }}
        {% endif %}
        <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
        </div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaks }}</p>
    {% endblock %}
    

    然后修改blog/urls.py文件,添加一条:

    1
    
    url(r'^post/(?P<pk>[0-9]+)/edit/$', views.post_edit, name='post_edit'),
    

    我们会重用模板blog/templates/blog/post_edit.html, 因此只需要修改下view就可以了,打开文件blog/views.py,将下面的内容添加到最后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    def post_edit(request, pk):
        post = get_object_or_404(Post, pk=pk)
        if request.method == "POST":
            form = PostForm(request.POST, instance=post)
            if form.is_valid():
                post = form.save(commit=False)
                post.author = request.user
                post.save()
                return redirect('blog.views.post_detail', pk=post.pk)
        else:
            form = PostForm(instance=post)
        return render(request, 'blog/post_edit.html', {'form': form})
    

    这个跟post_new几乎一模一样,先通过主键查到文章:post = get_object_or_404(Post, pk=pk), 然后我们在提交和进入编辑页面的时候都将post作为instance参数传递给PostForm。

    OK,现在让我们来测试下效果:

    完美,恭喜你!你的应用已经变得越来越酷了。

    如果你想对django的表单有更深入的了解, 请查阅官方文档:https://docs.djangoproject.com/en/1.7/topics/forms/





    到现在为止我们已经完成的差不多了,并且基本的东西都已经学到了,是时候用起来了。 我们的博客还有很多功能需要完善,下面抛砖引玉新增几个功能,还有其他功能等你自己去发现和实现。

    草稿箱

    之前我们新建文章的时候只是是保存到数据库,也就是仅仅保存了草稿,还没有对外发布, 在博客首页上面是看不到的,因为published_date字段为空。这里我们需要添加一个草稿箱的链接。还是四部曲。

    第一步,添加一个链接:

    打开mysite/templates/mysite/base.html文件,在

    1
    
    <h1><a href="/">Django Girls Blog</a></h1>
    

    的上面一行添加如下链接:

    1
    2
    
    <a href="{% url 'post_draft_list' %}" class="top-menu">
    <span class="glyphicon glyphicon-edit"></span></a>
    

    第二步就是配置urls,在blog/urls.py中添加:

    1
    
    url(r'^drafts/$', views.post_draft_list, name='post_draft_list'),
    

    第三步在blog/views.py中添加一个view:

    1
    2
    3
    
    def post_draft_list(request):
        posts = Post.objects.filter(published_date__isnull=True).order_by('-created_date')
        return render(request, 'blog/post_draft_list.html', {'posts': posts})
    

    第四步添加一个template,新建blog/templates/blog/post_draft_list.html,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    {% extends 'blog/base.html' %}
    {% block content %}
        {% for post in posts %}
            <div class="post">
                <p class="date">created: {{ post.created_date|date:'d-m-Y' }}</p>
                <h1><a href="{% url 'blog.views.post_detail' pk=post.pk %}">{{ post.title }}</a></h1>
                <p>{{ post.text|truncatechars:200 }}</p>
            </div>
        {% endfor %}
    {% endblock %}
    

    这个模板跟我们的post_list.html非常相似。

    刷新首页,点击那个草稿箱链接,看看效果。

    发布功能

    在文章详情页面添加一个发布的按钮,如果觉得合适的时候就能发布文章了。 每个新功能都是四部曲,你照着这四步做就行,你会发现越来越简单。

    第一步在页面上添加一个链接或Form表单,这里我们添加一个链接。

    打开blog/template/blog/post_detail.html,将下面这段

    1
    2
    3
    
    {% if post.published_date %}
        {{ post.published_date }}
    {% endif %}
    

    换成下面这段:

    1
    2
    3
    4
    5
    
    {% if post.published_date %}
        {{ post.published_date }}
    {% else %}
        <a class="btn btn-default" href="{% url 'blog.views.post_publish' pk=post.pk %}">Publish</a>
    {% endif %}
    

    这里增加了一个else语句,意思是如果没有发布日期的话就增加一个发布按钮。

    第二步添加urls配置,打开blog/urls.py:

    1
    
    url(r'^post/(?P<pk>[0-9]+)/publish/$', views.post_publish, name='post_publish'),
    

    第三步视图,打开blog/views.py:

    1
    2
    3
    4
    
    def post_publish(request, pk):
        post = get_object_or_404(Post, pk=pk)
        post.publish()
        return redirect('blog.views.post_detail', pk=pk)
    

    第四步模板,由于这次没有引入新的模板,所以这步省略。

    刷新后看效果:

    发布之后的效果:

    注意观察发布前和发布后文章的发布日期那个位置的变化。并且发布后再去首页看看,文章已经可以正常显示了。

    删除功能

    最后当然需要一个删除功能了,还是四部曲!

    第一步是在页面上添加链接,打开blog/templates/blog/post_detail.html,在编辑按钮下面一行添加如下:

    1
    2
    
    <a class="btn btn-default" href="{% url 'post_remove' pk=post.pk %}">
    <span class="glyphicon glyphicon-remove"></span></a>
    

    第二步配置urls映射,打开blog/urls.py,添加如下一行:

    1
    
    url(r'^post/(?P<pk>[0-9]+)/remove/$', views.post_remove, name='post_remove'),
    

    第三步添加视图view,打开blog/views.py,添加一个视图函数:

    1
    2
    3
    4
    
    def post_remove(request, pk):
        post = get_object_or_404(Post, pk=pk)
        post.delete()
        return redirect('blog.views.post_list')
    

    第四步模板,由于这次又没有新的模板,所有这步省略。

    OK,刷新页面看效果:

    删除后再去首页看,已经没有这篇文章了。

    分页功能

    在首页显示文章列表时候需要分页显示,这时候可以使用django的一个插件叫pagination

    设置非常简单,简直是简单到变态。

    settings.py文件中

    1. INSTALLED_APPS增加’pagination’项
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    # Application definition
    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'blog',
        'pagination',
    )
    
    1. MIDDLEWARE_CLASSES增加’pagination.middleware.PaginationMiddleware’
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    MIDDLEWARE_CLASSES = (
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'pagination.middleware.PaginationMiddleware',
    )
    
    1. TEMPLATE_CONTEXT_PROCESSORS增加’django.core.context_processors.request’
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    TEMPLATE_CONTEXT_PROCESSORS = (
        "django.contrib.auth.context_processors.auth",
        "django.core.context_processors.debug",
        "django.core.context_processors.i18n",
        "django.core.context_processors.media",
        "django.core.context_processors.static",
        "django.core.context_processors.tz",
        "django.contrib.messages.context_processors.messages",
        "blog.commons.context_processors.custom_proc",
        "django.core.context_processors.request",
    )
    
    1. requirements.txt中增加依赖:
    1
    
    django-pagination-py3==1.1.1
    
    1. 修改post_list.html页面,增加autopaginate标签
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
    {% extends 'mysite/base.html' %}
    {% load blog_tags %}
    {% load pagination_tags %}
    {% block content %}
        {% if list_header %}
            <div class="box">{{ list_header }}</div>
        {% endif %}
        {% autopaginate posts 10 %}
        {% for post in posts %}
            <div class="post">
                <h2><a href="{% url 'blog.views.post_detail' pk=post.pk %}">{{ post.title }}</a></h2>
                <div class="info">
                    <span class="date">{{ post.published_date|date:'Y年m月d日' }}</span>
                    <span class="comments">
                        {% if post.num_comment > 0 %}
                            {{ post.num_comment }} 条评论
                        {% else %}
                            没有评论
                        {% endif %}
                    </span>
                    <span class="comments">
                        {{ post.click }} 人阅读&nbsp;&nbsp;
                    </span>
                    <div class="fixed"></div>
                </div>
                <div class="content">
                    <p>
                        {% autoescape off %}
                            {{ post.text|more:post.id }}
                        {% endautoescape %}
                    </p>
                </div>
                <div class="under">
                    <span class="categories">分类: </span>
                    <span>
                        <a href="{% url 'blog.views.post_list_by_category' cg=post.category.name %}"
                           rel="category tag">{{ post.category.name }}
                        </a>
                    </span>
                    <span class="tags">标签: </span>
                    <span>
                        {% for tg in post.tags.all %}
                            <a href="{% url 'blog.views.post_list_by_tag' tag=tg.name %}"
                               rel="tag">{{ tg.name }}</a>&nbsp;
                        {% endfor %}
                    </span>
                </div>
            </div>
        {% endfor %}
        {% paginate %}
    {% endblock %}
    

    刷新下列表首页,看看分页效果。

    恭喜你,你可以大声对自己喊:我太棒了。^_^



    安全问题

    你应该注意到了一点,当你去新建、修改和删除文章的时候并不需要登录, 这样的话任何浏览网站的用户都能随时修改和删除我的文章。这个可不是我想要的!

    编辑和删除的认证

    我们需要保护post_new, post_edit和post_publish这三个视图,只有登录用户才有权去执行。 django为我们提供了很好的帮助类,其实就是利用了python中的decorators技术。 django中认证的装饰器位于模块django.contrib.auth.decorators中,名称叫login_required。

    编辑blog/views.py文件,在import部分添加如下的导入语句:

    1
    
    from django.contrib.auth.decorators import login_required
    

    然后在post_new, post_edit和post_publish这三个函数上添加@login_required, 类似下面

    1
    2
    3
    
    @login_required
    def post_new(request):
        [...]
    

    好的,现在你再去访问下http://localhost:8000/post/new/,看看有啥变化。

    注:如果你仍然能正常进入新建页面,那可能是你之前在admin界面登陆过。 那么你需要先退出,访问http://localhost:8000/admin/logout/可以退出,然后你再看下效果。

    我刚刚添加的@login_required装饰器检测到你尚未登陆的时候会重定向到login页面, 但是我现在还没有定义login的模板页面,所以这时候会是404错误页面。

    用户登录

    django在用户认证方面做得很好了,我们只需要去使用它就行。

    在mysite/urls.py文件中,添加下面一行

    1
    
    url(r'^accounts/login/$', 'django.contrib.auth.views.login')
    

    现在这个文件内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        url(r'^admin/', include(admin.site.urls)),
        url(r'^accounts/login/$', 'django.contrib.auth.views.login'),
        url(r'', include('blog.urls')),
    )
    

    然后我们再定义一个登陆页面,创建目录mysite/templates/registration, 并在里面新建模板文件login.html,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    {% extends "mysite/base.html" %}
    
    {% block content %}
    
        {% if form.errors %}
        <p>Your username and password didn't match. Please try again.</p>
        {% endif %}
    
        <form method="post" action="{% url 'django.contrib.auth.views.login' %}">
        {% csrf_token %}
        <table>
        <tr>
            <td>{{ form.username.label_tag }}</td>
            <td>{{ form.username }}</td>
        </tr>
        <tr>
            <td>{{ form.password.label_tag }}</td>
            <td>{{ form.password }}</td>
        </tr>
        </table>
    
        <input type="submit" value="login" />
        <input type="hidden" name="next" value="{{ next }}" />
        </form>
    {% endblock %}
    

    你可以看到我们仍然使用到了模板继承。这个时候可以定义一个mysite/templates/mysite/base.html, 把blog/templates/blog/base.html的内容复制给它即可。

    不过我们需要在mysite/settings.py中再添加一个urls配置:

    1
    
    LOGIN_REDIRECT_URL = '/'
    

    这样的话当用户直接访问login页面后登录成功会重定向到文章列表页面去。

    改进显示

    现在的确只有登录用户才能修改和删除文章,但是未登录用户却能看到这些按钮, 这个是很不好的体验。现在如果是未登录用户的话就把这些按钮给隐藏掉。

    因此我们修改mysite/templates/mysite/base.html如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    <body>
        <div class="page-header">
            {% if user.is_authenticated %}
            <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
            <a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a>
            {% else %}
            <a href="{% url 'django.contrib.auth.views.login' %}" class="top-menu"><span class="glyphicon glyphicon-lock"></span></a>
            {% endif %}
            <h1><a href="{% url 'blog.views.post_list' %}">Django Girls</a></h1>
        </div>
        <div class="content">
            <div class="row">
                <div class="col-md-8">
                {% block content %}
                {% endblock %}
                </div>
            </div>
        </div>
    </body>
    

    然后修改blog/templates/blog/post_detail.html如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    {% extends 'blog/base.html' %}
    
    {% block content %}
        <div class="date">
            {% if post.published_date %}
                {{ post.published_date }}
            {% else %}
                {% if user.is_authenticated %}
                    <a class="btn btn-default" href="{% url 'blog.views.post_publish' pk=post.pk %}">Publish</a>
                {% endif %}
            {% endif %}
            {% if user.is_authenticated %}
                <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
                <a class="btn btn-default" href="{% url 'post_remove' pk=post.pk %}"><span class="glyphicon glyphicon-remove"></span></a>
            {% endif %}
        </div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaks }}</p>
    {% endblock %}
    

    用户注销

    当用户登录后显示欢迎语句,Hello ,然后后面跟一个logout链接。还是依靠django帮我们处理logout动作。

    修改mysite/templates/mysite/base.html文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    <div class="page-header">
        {% if user.is_authenticated %}
        <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
        <a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a>
        <p class="top-menu">Hello {{ user.username }}<small>&nbsp;<a href="{% url 'django.contrib.auth.views.logout' %}">Log out</a></p>
        {% else %}
        <a href="{% url 'django.contrib.auth.views.login' %}" class="top-menu"><span class="glyphicon glyphicon-lock"></span></a>
        {% endif %}
        <h1><a href="{% url 'blog.views.post_list' %}">Django Girls</a></h1>
    </div>
    

    很显然这时候logout肯定会报错。我们还得做些事情。

    对于这方面的详细文档请参考:https://docs.djangoproject.com/en/1.7/topics/auth/default/

    打开mysite/urls.py文件,添加一个logout配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        url(r'^admin/', include(admin.site.urls)),
        url(r'^accounts/login/$', 'django.contrib.auth.views.login'),
        url(r'^accounts/logout/$', 'django.contrib.auth.views.logout', {'next_page': '/'}),
        url(r'', include('blog.urls')),
    )
    

    如果访问网站时出现模板找不到错误,那么你就在mysite/settings.py中添加如下配置:

    1
    2
    3
    4
    5
    
    # TEMPLATE_DIRS
    TEMPLATE_DIRS = (
        os.path.join(BASE_DIR, 'mysite/templates'),
        os.path.join(BASE_DIR, 'blog/templates'),
    )
    

    好的,现在你已经可以达成如下的效果了:

    1. 需要一个用户名和密码登录系统
    2. 在添加/编辑/删除/发布文章的时候需要登录
    3. 也能注销

    这么说的话,这个博客系统算功能比较完善了!朋友,祝福你。

































































    • 0
      点赞
    • 2
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值