由于之前在Comodo申请的SSl证书马上就要到期,刚好看到现在很多网站已经用上了Let's Encrypt的证书,了解了下配置和更新都十分简单,因此决定本站也转向Let's Encrypt。简单记录一下配置过程(环境:CentOS 7 + nginx)。

1.开启EPEL

使用Certbot配置证书,首先要开启EPEL(Extra Packages for Enterprise Linux)。

sudo yum install epel-release

2.安装Certbot

sudo yum install certbot

3.生成及配置证书

预先工作

在证书生成过程中,Certbot会在网站根目录生成一个随机文件(.well-known/acme-challenge/some_random.html),Certbot的服务器会访问地址imliyan.com/.well-known/acme-challenge/some_random.html用于验证,但由于网站是使用 Django 开发并用 uwsgi + nginx 部署的,除静态文件外的 url 都被转到 Django 处理,因此这一步会返回 404,解决办法可以在 Django 的 urls.py 中进行配置或配置 nginx,此处选择配置 nginx。

server {
    location /.well-known/acme-challenge {
        alias /path/to/imliyan.com/.well-known/acme-challenge;
    }
}

生成证书

certbot certonly --webroot -w /path/to/imliyan.com -d imliyan.com -d www.imliyan.com

上述命令会为imliyan.comwww.imliyan.com生成一个单独的证书。证书存储于 /etc/letsencrypt/live/imliyan.com/fullchain.pem

nginx配置

server {
    listen 443 ssl;
    server_name www.imliyan.com imliyan.com;

    ssl_certificate /etc/letsencrypt/live/imliyan.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/imliyan.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/imliyan.com/chain.pem;
}

重启 nginx

nginx -s reload

到此配置完成。整个过程相比 Comodo 手工生成证书签名请求(CSR)、生成证书、上传证书、配置要简单不少,关键是到期更新的话也十分简单。

Django使用模型(model)来定义数据,模型包含数据的字段以及行为信息,通常可以认为每个模型对应于一个数据库表。Django的模型系统实现了数据库的常见关系(一对一、一对多、多对多),本文主要介绍这三种关系模型的使用。

一对一关系

Django使用OneToOneField处理一对一关系。我们大学和地点来进行一对一关系的演示。一个地点可以是一所大学。

首先定义模型:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class University (models.Model):
    name = models.CharField(max_length=50)
    place = models.OneToOneField(
        Place,
        primary_key = True
    )

    def __str__(self):
        return self.name

之后就可以用Django的API来进行模型操作了:

> python manage.py shell
>>> from myapp.models import Place, University

# 创建地点
>>> place1 = Place(name="Peking University",
        address="No.5 Yiheyuan Road Haidian District, Beijing")
>>> place2 = Place(name="Tsinghua University",
        address="No.30 Shuangqing Road Haidian District, Beijing")
>>> place3 = Place(name="Somewhere",
        address="Someroad Somecity")
>>> place1.save()
>>> place2.save()
>>> place3.save()

# 创建大学
>>> university1 = University(name="Peking University", place=place1)
>>> university2 = University(name="Tsinghua University", place=place2)
>>> university1.save()
>>> university2.save()

# 地点和大学模型可以相互访问
>>> place1.university
<University: Peking University>
>>> university1.place
<Place: Peking University>
>>> place2.university.name
'Tsinghua University'
>>> university2.place.address
'No.30 Shuangqing Road Haidian District, Beijing'

# 地点3目前还没有大学
>>> hasattr(place3, 'university')
False
# 使用赋值语句为清华大学设置新地点
>>> university2.place = place3
>>> university2.save()
>>> hasattr(place3, 'university')
True
>>> place3.university
<University: Tsinghua University>

# 注意place为University的主键,因此上述操作创建了一个新的大学对象
>>> University.objects.all()
<QuerySet [<University: Peking University>, <University: Tsinghua University>, <University: Tsinghua University>]>

一对多关系

Django使用ForeignKey来处理一对多关系,我们用大学和学生的关系来进行演示:一个学校可以有多个学生。

定义学生模型

class Student(models.Model):
    university = models.ForeignKey(University)
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

模型操作:

>>> from myapp.models import University, Student

# 创建学生
>>> pku = University.objects.get(name="Peking University")
>>> zhangsan = Student(name="Zhang San", university=pku)
>>> lisi = Student(name="Li Si", university=pku)
>>> zhangsan.save()
>>> lisi.save()

# 查看张三的学校信息
>>> zhangsan.university
<University: Peking University>
>>> lisi.university.place.address
'No.5 Yiheyuan Road Haidian District, Beijing'

# 查看pku的学生信息
>>> pku.student_set.all()
<QuerySet [<Student: Zhang San>, <Student: Li Si>]>
>>> pku.student_set.get(name="Li Si")
<Student: Li Si>

# 创建一个没有学校的学生
>>> wangwu = Student(name="Wang Wu")
>>> wangwu.save()
>>> wangwu.university
Traceback (most recent call last):
  File "<console>", line 1, in <module>
 ...
django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist: Student has no university.

# 添加到pku
>>> pku.student_set.add(wangwu)
>>> wangwu.university
<University: Peking University>
>>> pku.student_set.all()
<QuerySet [<Student: Zhang San>, <Student: Li Si>, <Student: Wang Wu>]>

# 查看pku有几个学生
>>> pku.student_set.count()
3

# 查看pku名字以"Li"开头的学生
>>> pku.student_set.filter(name__startswith="Li")
<QuerySet [<Student: Li Si>]>

# 查看学校名字为"Peking University"的学生
>>> Student.objects.filter(university__name="Peking University")
<QuerySet [<Student: Zhang San>, <Student: Li Si>, <Student: Wang Wu>]>

多对多关系

Django使用ManyToManyField来处理多对多关系,我们用学生和课程的关系来进行演示:一个学生可以选择多个课程,每个课程可以有多个学生来上课。

定义课程模型

class Course(models.Model):
    student = models.ManyToManyField(Student)
    name = models.CharField(max_length=50)

    def __str_(self):
        return self.name

模型操作

>>> from myapp.models import University, Student, Course
>>> hm = Course(name="Higher Mathematics")
>>> ms = Course(name="Mathematical Statistics")
>>> hm.save()
>>> ms.save()
>>> pku = University.objects.get(name="Peking University")
>>> zhangsan = Student.objects.get(name="Zhang San")
>>> lisi = Student.objects.get(name="Li Si")
>>> wangwu = Student.objects.get(name="Wang Wu")

# lisi目前还没有选择课程
>>> lisi.course_set.all()
<QuerySet []>

# 高等数学目前还没有学生选择
>>> hm.student.all()
<QuerySet []>

# 我们给lisi选择两门课程
>>> lisi.course_set.add(hm, ms)
>>> lisi.course_set.all()
<QuerySet [<Course: Higher Mathematics>, <Course: Mathematical Statistics>]>
>>> ms.student.all()
<QuerySet [<Student: Li Si>]>

# 我们再给数理统计课程添加两名学生
>>> ms.student.add(zhangsan, wangwu)
>>> ms.student.all()
<QuerySet [<Student: Zhang San>, <Student: Li Si>, <Student: Wang Wu>]>

# 直接给高等数学再创建并添加一名学生
>>> zhaoliu = hm.student.create(name="Zhao Liu", university=pku)
>>> hm.student.all()
<QuerySet [<Student: Li Si>, <Student: Zhao Liu>]>

# 交叉查询
# 查询选择了高等数学的学生
>>> Student.objects.filter(course__name="Higher Mathematics")
<QuerySet [<Student: Li Si>, <Student: Zhao Liu>]>

# 查询有李四参与的课程
>>> Course.objects.filter(student__name="Li Si")
<QuerySet [<Course: Higher Mathematics>, <Course: Mathematical Statistics>]>

# 张三退掉数理统计
>>> zhangsan.course_set.remove(ms)
>>> zhangsan.course_set.all()
<QuerySet []>

# 从高等数学课程中删除赵六
>>> hm.student.remove(zhaoliu)
>>> hm.student.all()
<QuerySet [<Student: Li Si>]>

参考:

简述JavaScript对象与继承

JavaScript是基于原型的语言,每一个对象都有一个原型对象。通过构造器函数实例化的对象可以通过浏览器实现的__proto__接口访问其原型对象(ECMA官方实现为Object.getPrototypeOf(obj)),此对象的构造器函数同样有一个属性 prototype指向此原型对象。

当查找对象属性时,会首先在对象上查找,如果找到则返回,未找到则查找对象的原型对象、原型对象的原型对象……一直到查找到为止或者某对象的原型对象为null

如果要实现继承,可以指定子对象的原型是父对象的一个实例,也即:

SubType.prototype = new SuperType()

对象与继承的基础知识本文不再详述,接下来对比构造函数与class两种语法格式创建对象的特点。

使用构造器函数

function Dog(name) {
    this.name = name
    this.bark = function() {
        console.log(this.name + ' Wang!')
    }
}
Dog.prototype.walk = function() {
    console.log(this.name + ' Walking!')
}

var d = new Dog('Tom')

// name, bark 是对象 d 的属性
d.hasOwnProperty('name')
// true
d.hasOwnProperty('bark')
// true

// walk 不是对象 d 的属性
d.hasOwnProperty('walk')
// false

d.__proto__.hasOwnProperty('walk')
// true
d.__proto__ === Dog.prototype
// true
d.walk()
// 'Tom Walking!'

实现继承

function Doggy(name, age) {
    Dog.call(this, name)
    this.age = age
}
var dg = new Doggy('Doggy', 3)

dg.hasOwnProperty('name')
// true
dg.hasOwnProperty('bark')
// true

// 我们虽然在 Doggy 的上下文调用了 Dog 构造函数,
// 但这只是一个新的构造函数, 我们还未实现继承
// 因为 Doggy 的 prototype 未定义,Doggy 未继承 Dog
dg.walk()
// TypeError: dg.walk is not a function

// 指定原型实现继承
Doggy.prototype = Object.create(Dog.prototype)
var dg = new Doggy('Doggy', 3)
dg.walk()
// 'Doggy Walking!'

// dg原型的原型是 d 的原型
// dg 的原型是 Dog 的实例
dg.__proto__.__proto__ === d.__proto__
// true
dg.__proto__ instanceof Dog
// true

ECMAScript 6 之 class

class 在ECMAScript 6中引进,但这只是一个语法糖,JavaScript仍是一个基于原型而不是基于类的语言。

class Dog {
    constructor(name) {
        this.name = name
    }
    bark() {
        console.log(this.name + ' Wang!')
    }
}

class Doggy extends Dog {}

// Dog 其实是一个 function 实例
typeof Dog
// 'function'

var d = new Dog('Tom')
var dg = new Doggy('Doggy')

d.hasOwnProperty('name')
// true

// d 自身没有 bark 属性,是在其原型上查找到的
d.hasOwnProperty('bark')
// false
d.__proto__.hasOwnProperty('bark')
// true
d.__proto__ === Dog.prototype
// true

dg.hasOwnProperty('name')
// true
dg.hasOwnProperty('bark')
// false
dg.__proto__.hasOwnProperty('bark')
// false

// ***********************
//
// dg 对象的原型是 Dog 的实例
// dg 原型的原型就是d的原型
//
// ***********************
dg.__proto__ instanceof Dog
// true
dg.__proto__.__proto__ === d.__proto__
// true

两种构造方法对比可见,其内部实现是一致的, 但是class声明更加清晰明确,特别是对于熟悉其他基于类的语言的同学来说更容易理解。但必须要明白的是,这只是一个语法糖,JavaScript 仍是一个基于原型的语言。

参考


There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
——the Zen of Python

Python提倡用一种,而且最好是只有一种方法来完成一件事。可是在字符串格式化方面Python好像没有做到这一点。除了基本的%-formatting格式,还有str.format()格式、以及string.Template。根据对Python标准库的统计,目前string.Template的使用屈指可数,str.format()获得广泛使用,但是我们来与其他几种语言的字符串格式化对比一下:

已知name = 'Tom',我们如何打印出字符串'My name is Tom.'

Ruby:

puts 'My name is #{name}.'

JavaScript(ECMAScript 2015):

console.log(`My name is ${name}.`)

Python:

print('My name is {name}.'.format(name = name))

# 即便是简化的版本
print('My name is {}.'.format(name))

可以看出Python明显还不够简洁,于是,随着Python3.6版本在上周正式发布,Python提供了一种字符串格式化语法——'f-strings'。

f-strings

要使用f-strings,只需在字符串前加上f,语法格式如下:

f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... '

基本用法

>>> name = "Tom"
>>> age = 3
>>> f"His name is {name}, he's {age} years old."
>>> "His name is Tom, he's 3 years old."

支持表达式

# 数学运算
>>> f'He will be { age+1 } years old next year.'
>>> 'He will be 4 years old next year.'

# 对象操作
>>> spurs = {"Guard": "Parker", "Forward": "Duncan"}
>>> f"The {len(spurs)} players are: {spurs['Guard']} the guard, and {spurs['Forward']} the forward."
>>> 'The 2 players are: Parker the guard, and Duncan the forward.'

>>> f'Numbers from 1-10 are {[_ for _ in range(1, 11)]}'
>>> 'Numbers from 1-10 are [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]'

排版格式

>>> def show_players():
    print(f"{'Position':^10}{'Name':^10}")
    for player in spurs:
        print(f"{player:^10}{spurs[player]:^10}")
>>> show_players()
 Position    Name   
  Guard     Parker  
 Forward    Duncan

数字操作

# 小数精度
>>> PI = 3.141592653
>>> f"Pi is {PI:.2f}"
>>> 'Pi is 3.14'

# 进制转换
>>> f'int: 31, hex: {31:x}, oct: {31:o}'
'int: 31, hex: 1f, oct: 37'

与原始字符串联合使用

>>> fr'hello\nworld'
'hello\\nworld'

注意事项

{}内不能包含反斜杠\

f'His name is {\'Tom\'}'
SyntaxError: f-string expression part cannot include a backslash

# 而应该使用不同的引号,或使用三引号。
>>> f"His name is {'Tom'}"
'His name is Tom'

不能与'u'联合使用

'u'是为了与Python2.7兼容的,而Python2.7不会支持f-strings,因此与'u'联合使用不会有任何效果。

如何插入大括号?

>>> f"{{ {10 * 8} }}"
'{ 80 }'
>>> f"{{ 10 * 8 }}"
'{ 10 * 8 }'

str.format()的一点不同

使用str.format(),非数字索引将自动转化为字符串,而f-strings则不会。

>>> "Guard is {spurs[Guard]}".format(spurs=spurs)
'Guard is Parker'

>>> f"Guard is {spurs[Guard]}"
Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    f"Guard is {spurs[Guard]}"
NameError: name 'Guard' is not defined

>>> f"Guard is {spurs['Guard']}"
'Guard is Parker'

参考:

在不同的语境中,this代表不同的对象。

全局上下文

在全局上下文中(任何函数体外), this 指代全局对象。

console.log(this === window);
// true

函数上下文

在函数内部, this的值取决于函数是如何被调用的。

直接调用

function say() {
    return this
}
say() === window
// true

而在严格模式下,this在进入运行环境时设置,将是undefined。

function say2() {
    "use strict";
    return this
}
say2() === undefined
// true

作为方法

如果函数作为一个对象的方法,则this指代调用此方法的对象。注意箭头函数在{}定义对象 中的不同表现。

var dog = {
    name: "Tom",
    bark: function() {
        return this
    },
    walk: () => {
        return this
    },
    head: {
        name: "Tom's head",
        shake: function() {
            return this.name + " shaking"
        }
    }
}
dog.bark() === dog
// true
dog.walk() === window
// true

即使方法是之后才指定为对象的属性。

dog.run = function() {
    return this.name + " running"
}
dog.run()
// Tom running

this指向其最近的对象。

dog.head.shake()
// Tom's head shaking

另外构造函数中的函数(包括箭头函数)则指向产生的对象本身。

function Dog() {
    this.walk = () => {
        return this
    }
    this.bark = function() {
        return this
    }
}
var d = new Dog()
d.bark() === d
// true

d.walk === d
// true

call 与 apply、bind

call() 方法可用于指定函数体内的this对象。 apply() 指定this,并且第二个数组参数的各项作为函数的参数。

function greet(greeting) {
    return greeting + this.fname + " " + this.lname
}
var tom = {fname: "Tom", lname: "Jerry"}
greet.call(tom, "Hello ")
// Hello Tom Jerry
greet.apply(tom, ["Hi~ "])
// Hi~ Tom Jerry

bind()方法返回一个新的函数,函数体与函数相同,但this被永久地绑定。

greet = greet.bind(tom)
greet("Hello ")
// Hello Tom Jerry

箭头函数

对于箭头函数,this值一旦确定,就会一直保持不变。

var name = "Jerry",
say = (() => this.name),
tom = {
    name: "Tom",
    say: say,
    walk: function() {
        var inwalk = () => this.name + " walking"
        return inwalk
    }
}

say() === "Jerry"
// true

tom.say() === "Jerry"
// true

say.call(tom) === "Jerry"
// true

say.bind(tom)() === "Jerry"
// true

var globalwalk = tom.walk()
globalwalk()
// Tom walking

globalwalk.bind(window)
globalwalk()
// Tom walking

DOM事件处理函数中的this

当函数作为DOM时间处理函数时,this指向触发事件的元素。

var element = document.getElementById('id1')
element.onclick = function(e) {
    console.log(this === e.currentTarget) // 总是 true
    console.log(this === e.target) // 当currenrTarget === target
}

行内事件处理函数中的this

<button onclick="alert(this)">Click</buttom>

上述代码的this指向<button> DOM对象。 注意只有外层的this被如此设置。

<button onclick="alert((function(){return this})())"

上述代码的this未指定所以其值默认为全局对象(window / global)。