码农之家
这篇文章主要知识点是关于python编程,从入门到实践,心得笔记,python编程:从入门到实践,《Python编程:从入门到实践》第五章:if语句 实例详解Python编程实现生成特定范围内不重复多个随机数的2种方法 python编程写代码时几个坏习惯总结 《Python编程:入门到实践》第七章:用户输入和while循环 python编程学习np.float 被删除的问题解析 的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子书
python的安装和环境变量的配置通过百度查询即可解决,这里不作赘述。
IDE的选择:在前几章学习语法这个阶段,我选择使用EditPlus打造一个Python IDE这个。
PS:如果需要在 Win10下同时安装python3和python2并解决pip共存问题可以参考这个。
第一次运行hello world!
首先创建第一个python脚本文件first.py,代码如下:
#coding=utf8 print('hello world!')
EditPlusIDE运行(ctrl+1):
hello world!
终端命令行运行:
>C:\Users\Desktop\学习笔记>python3 first.py hello world!
变量
和C\C++一样,值可以变化,可以用其存储任何东西。(相当于一个容器) 变量的命名:
首字符必须是字母(大写或小写)或者一下下划线('_')
名称的其他部分可以由字母(大写或小写)、下划线(‘ _ ’)或数字(0-9)组成
对大小写敏感
有效变量名称的例子有 k、__my_num、name_1 和 a4b5_c6
无效标识符名称的例子有 2board、this is me和my-num
变量的赋值: 变量名=值,比如变量名为me,值为god,赋值方法如下:
me = 'god' print(me) me = 'home' print(me) 运行结果: god home
其是由数字、字母、下划线组成的一串字符。
PS:字符串是不可变的,一旦创造了一个字符串,就不能再改变它。
字符串的修改 + title() 将字符串的首字母大写 + upper() 将字符串全部大写 + lower() 将字符串全部小写
me = 'goD' print(me.title()) print(me.upper()) print(me.lower()) 运行结果: GoD GOD god
one = 'im' two = 'god' print(one + ' ' + two) 运行结果: im god
one = ' god ' one.rstrip() one.lstrip() one.strip() 运行结果: ' god' 'god ' 'god'
4种类型的数:整数、长整数、浮点数和负数。主要运算有加(+)、减(-)、乘(*)、除(/)。另外,乘方使用两个乘号表示,比如 3**2 ,即3的平方。
PS:使用 str()可以将非字符串值表示为字符串。
注释用井号#。井号后面的都会被python忽略,通常用来标注解释代码。
列表是是处理一组有序项目的数据结构,即可以在一个列表中存储一个序列的项目。列表中的元素包括在方括号([])中,每个元素之间用逗号分割。列表是可变的数据类型,可以添加、删除或是搜索列表中的元素。
访问列表元素可以通过索引+方括号的形式,记住,索引从0而不是1开始!。
shoplist = ['apple', 'mango', 'carrot', 'banana'] print(shoplist) print(shoplist[0].title()) print('i want a' + ' ' + shoplist[0]) 运行结果: ['apple', 'mango', 'carrot', 'banana'] Apple i want a apple
修改元素可以通过直接赋值的方法。
shoplist = ['apple', 'mango', 'carrot', 'banana'] shoplist.append('duck') print(shoplist) shoplist.insert(0, 'chick') print(shoplist) del shoplist[1] print(shoplist) temp1 = shoplist.pop(0) print(temp1) print(shoplist) shoplist.remove('banana') print(shoplist) 运行结果: ['apple', 'mango', 'carrot', 'banana', 'duck'] [‘chick’, 'apple', 'mango', 'carrot', 'banana', 'duck'] [‘chick’, 'mango', 'carrot', 'banana', 'duck'] chick ['mango', 'carrot', 'banana', 'duck'] ['mango', 'carrot', 'duck']
shoplist = ['apple', 'mango', 'carrot', 'banana'] shoplist.sort(reverse=True) print(shoplist) shoplist = ['apple', 'mango', 'carrot', 'banana'] print(sorted(shoplist)) print(shoplist) shoplist.reverse() print(shoplist) len(shoplist) 运行结果: ['mango', 'carrot', 'banana', 'apple'] ['apple', 'banana', 'carrot', 'mango'] ['apple', 'mango', 'carrot', 'banana'] ['banana', 'carrot', 'mango', 'apple'] 4
利用for循环可以快速遍历列表,不要忘记它的冒号!
shoplist = ['apple', 'mango', 'carrot', 'banana'] for shop in shoplist: print('i want a ' + shop.title()) 运行结果: i want a Apple i want a Mango i want a Carrot i want a Banana
行首的空白(空格和制表符)决定行的缩进层次,同一层次的语句必须有相同的缩进。
不要混合使用制表符和空格来缩进,在每个缩进层次要么使用单个制表符或 两个或四个空格。
nums = list(range(1,5)) for num in nums: print(num) print(min(nums)) print(max(nums)) print(sum(nums)) 运行结果: 1 2 3 4 1 4 10
nums = list(range(0,11)) print(nums[0:5]) print(nums[:6]) Nums = nums[:] print(Nums) 运行结果: [0, 1, 2, 3, 4] [0, 1, 2, 3, 4, 5] [2, 3, 4, 5, 6, 7, 8, 9, 10] [8, 9, 10] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
元组不可以修改,使用圆括号标识。元组也可以用循环进行遍历。我们还能给元组的变量赋值。
nums = (1, 2) print(nums) nums = (4, 2) for num in nums: print(num) 运行结果: (1, 2) 4 2
if语句用来检验一个条件, 如果条件为真,运行一块语句(if-块),否则处理另外一块语句(else-块)。else从句是可选的。注意这里使用了缩进层次来告诉Python每个语句分别属于哪一个块。if语句判断条件无括号,在结尾处包含一个冒号!
下面是一个简单的示例
nums = ['one', 'two', 'three', 'four', 'five'] for num in nums: if num == 'two': print('yes') else: print('no') 运行结果: no yes no no no
python使用两个等号(==)来检查两个变量的值是否相等。注意,检查是否相等时区分大小写。如果相等返回True,否则返回False。
而惊叹号和等号(!=)用来判断两个值是否不相等(其中惊叹号表示“不”)。如果不相等返回True,否则返回False。
age = 18 print(age == 18) print(age != 18) 运行结果: True False
python还可以用小于<,大于>,小于等于<=,大于等于>=,来进行数学比较。
age = 18 print(age < 20) print(age > 20) print(age >= 18) print(age <= 17) 运行结果: True False True False
and :只有两边的条件都为真,整个表达式才为真。
or :至少有一边条件为真,整个表达式才为真。
age = 18 print(age < 20 and age > 17) print(age < 20 or age > 21) 运行结果: True True
in :判断特定值是否在列表
not in :判断特定值是否不在列表
nums = ['one', 'two', 'three', 'four', 'five'] num1 = 'one' num2 = 'six' print(num1 in nums) print(num2 not in nums) 运行结果: True True
条件测试的别名,布尔表达式结果要么为True,要么为False。布尔值通常用于记录条件,如游戏是否正在运行或者用户是否为超级用户。
if语句除了单独一个if语句,if-else语句,还有if-elif-else语句和if-elif语句。下面分别给出例子。
#单个if语句: if num in nums: print('yes') #if-else语句: if num in nums: print('yes') else: print('no') #if-elif-else语句: if num in nums: print('yes') elif num in cars: print('???') else: print('no')
python还可以根据需要使用任意个elif代码块、测试多个条件。比如:
money = 20 if money < 10: print('10') elif money < 20: print('20') elif money < 30: print('30') else: print('inf') money = 20 if money < 10: print('10') if money < 20: print('20') if money < 30: print('30')
使用if语句处理列表,我们可以判断特定值是否在列表中、判断列表是否为空等等,从而做出相应的动作。
习题5-11
num_list = range(1, 11) for i in num_list: if i == 1: print(str(i) + 'st') elif i == 2: print(str(i) + 'nd') elif i == 3: print(str(i) + 'rd') else: print(str(i) + 'th')
字典类似于通过联系人名字查找联系人电话号码的电话本,即把键(名字)和值(电话号码)联系在一起。注意,键必须是唯一的。并且python只能使用不可变的对象(比如字符串)来作为字典的键,但是可以将不可变或可变的对象作为字典的值。举一个简单的字典例子。
alien = {'color': 'green', 'points': 5}
键值对在字典中的标记为:d = {key1 : value1, key2 : value2 }。注意键/值对用冒号分割,而各个对用逗号分割,所有这些都包括在花括号中。字典中的键/值对是没有顺序的。如果想要一个指定的顺序,那么在使用前自行排序。
访问字典中的值
依次指定字典名和放在方括号内的键,如下所示:
alien = {'color': 'green', 'points': 5} print(alien_0['color']) 运行结果: green
创建空字典时使用一对空的花括号定义一个字典,再分行添加各个键值对。
修改字典的值可以指定字典名、键以及新的赋值。
alien = {} alien['x_position'] = 0 alien['y_position'] = 25 print(alien) alien['x_position'] = 25 print(alien) 运行结果: {'x_position': 0, 'y_position': 25} {'x_position': 25, 'y_position': 25}
字典是一种动态结构。
添加键值对时,依次指定字典名、方括号和键、所赋的值。
删除键值对时,可使用del语句,指定字典名和要删除的键。
alien = {'color': 'green', 'points': 5} print(alien) alien['x_position'] = 0 alien['y_position'] = 25 print(alien) del alien['color'] print(alien) 运行结果: {'color': 'green', 'points': 5} {'color': 'green', 'points': 5, 'y_position': 25, 'x_position': 0} {'points': 5, 'y_position': 25, 'x_position': 0}
遍历所有的键值对
user = { 'username': 'efermi',#前面有四个空格的缩进,下同 'first': 'enrico', 'last': 'fermi', } for key, value in user.items() print("\nKey: " + key) print("Value: " + value) 运行结果: Key: last Value: fermi Key: first Value: enrico Key: username Value: efermi
由上可知,在字典的遍历中,可以声明两个变量分别存储键值对中的键和值。字典的方法item()返回一个键值对列表。注意,在遍历字典的时候,键值对的返回顺序与存储顺序不同。python只关系键和值之间的对应关系。
使用方法keys()可以遍历字典中的所有键。或者不使用方法,默认遍历字典中的键。
使用方法values()可以遍历字典中的所有值。返回一个值列表。
favorite_languages = { 'jen': 'python', 'sarah': 'c', 'edward': 'ruby', 'phil': 'python', } for name in favorite_languages.keys(): print(name.title()) for language in favorite_languages.values(): print(language.title()) 运行结果: Jen Sarah Edward Phil Python C Ruby Python
或者不使用方法,默认遍历字典中的键。即for name in favorite_languages.keys():效果等同for name in favorite_languages:。
若需按顺序遍历,只需使用sorted()。
将一系列字典存储在列表中,或者将列表作为值存储在字典中,称为嵌套。
将一系列字典存储在列表中。
alien_0 = {'color': 'green', 'points': 5} alien_1 = {'color': 'yellow', 'points': 10} alien_2 = {'color': 'red', 'points': 15} aliens = [alien_0, alien_1, alien_2] for alien in aliens: print(alien) 运行结果: {'color': 'green', 'points': 5} {'color': 'yellow', 'points': 10} {'color': 'red', 'points': 15}
列表字典
将字典存储在列表中。
lili = { 'name': 'lili', 'phonenum': ['123', '456'], } print("lili's name is " + lili['name'] + " and her phonenum is ") for num in lili['phonenum']: print("\t" + num) 运行结果: lili's name is lili and her phonenum is 123 456
存储网站用户信息可以在字典中嵌套字典,例如:
users = { 'aeinstein': { 'first': 'albert', 'last': 'einstein', 'location': 'princeton', }, 'mcurie': { 'first': 'marie', 'last': 'curie', 'location': 'paris', }, } for username, user_info in users.items(): print("\nUsername: " + username) full_name = user_info['first'] + " " + user_info['last'] location = user_info['location'] print("\tFull name: " + full_name.title()) print("\tLocation: " + location.title()) 运行结果: Username: aeinstein Full name: Albert Einstein Location: Princeton Username: mcurie Full name: Marie Curie Location: Paris
第六章试题以及答案:http://www.xz577.com/j/129.html
第七章 用户输入和while循环
函数input()
input() :暂停程序,等待用户输入文本并把文本存储在指定变量中。并接受一个参数,即要向用户显示的提示或说明。
int() :使用input()函数时,输入的文本会被当成字符串,所以可以用这个函数将其化为int型。
% :求模运算符。讲两个数相除并返回余数。
下面举个例子:
message = input("Tell me your name:") print(message) message = input("Tell me your years:") if int(message) >= 18: print('wow') print(int(message) % 6) 运行结果: Tell me your name:mike mike Tell me your years:19 wow 1
循环简介
while循环不断运行,直到指定的条件不满足位置。例如:
current_number = 1 while current_number <= 5: print(current_number) current_number += 1 运行结果: 1 2 3 4 5
还可以让用户选择何时退出,只要把循环的条件由current_number <= 5改为message != 'quit',然后在循环语句里面添加message = input(prompt),即:
current_number = 1 while message != 'quit': message = input('Enter 'quit' to end the program or go on:') print(current_number) current_number += 1 运行结果: Enter 'quit' to end the program or go on: 1 Enter 'quit' to end the program or go on: 2 Enter 'quit' to end the program or go on:quit
这时候我们提示了一条消息:要么输入quit来结束程序,要么继续运行。当然我们还可以设置标志来决定循环的条件,比如利用布尔值True或者False。
break :不管条件测试结果如何,立即退出循环,不在运行循环中余下代码。
prompt = "\nPlease enter a city you have visited:" prompt += "\n(Enter 'quit' when you are finished.) " while True: city = input(prompt) if city == 'quit': break 运行结果: Please enter a city you have visited: (Enter 'quit' when you are finished.) China I'd love to go to China! Please enter a city you have visited: (Enter 'quit' when you are finished.) San Francisco I'd love to go to San Francisco! Please enter a city you have visited: (Enter 'quit' when you are finished.) quit
continue :跳出此次循环,即返回到循环开头,并判断循环条件是否满足来决定是否执行循环。
current_number = 0 while current_number < 10: current_number += 1 if current_number % 2 == 0: continue print(current_number) 运行结果: 1 3 5 7 9
for循环中不应修改列表,因为for循环中修改列表之后,python的遍历会出现问题。而要想遍历列表并修改,可以用while循环。下面举几个例子。
在列表之间移动元素
#创建待验证用户列表和一个已验证用户列表 unconfirmed_ids = ['mike', 'lili', 'ace'] confirmed_ids = [] #验证每个用户并把其移动到已验证用户列表 while unconfirmed_ids: current_id = unconfirmed_ids.pop() print("Verifying id: " + current_id.title()) confirmed_ids.append(current_id) #显示所有已验证id print("\nThe following ids have been confirmed:") for confirmed_id in confirmed_ids: print(confirmed_id.title()) 运行结果: Ace Lili Mike
删除包含特定值的所有列表元素
nums = ['1', '3', '5', '1', '6', '1', '7'] print(nums) while '1' in nums: pets.remove('1') print(nums) 运行结果: ['3', '5', '6', '7']
使用用户输入来填充字典
responses = {} #设置一个标志Lj指出调查是否继续 polling_active = True while polling_active: #提示输入被调查者的名字和回答 name = input("\nWhat is your name? ") response = input("Which mountain would you like to climb someday? ") #将答卷存储在字典中 responses[name] = response #看看是否有人要参与调查 repeat = input("Would you like to let another person respond? (yes/ no) ") if repeat == 'no': polling_active = False #调查结束显示结果 print("\n--- Poll Results ---") for name, response in responses.items(): print(name + " would like to climb " + response + ".") 运行结果: What is your name? Eric Which mountain would you like to climb someday? Denali Would you like to let another person respond? (yes/ no) yes What is your name? Lynn Which mountain would you like to climb someday? Devil's Thumb Would you like to let another person respond? (yes/ no) no --- Poll Results --- Lynn would like to climb Devil's Thumb. Eric would like to climb Denali.
第七章练习答案代码 :http://www.xz577.com/j/129.html
定义函数
举个简单的例子
def greet_user(username): """先是简单的问候语""" print("Hello! " + username.title() + "!") greet_user("mike") 运行结果: Hello! Mike!
由上所示,关键字def定义一个函数,后面跟着函数名以及用来输入参数的括号,定义以冒号结束,而print("Hello!")为其函数体。
调用函数时,则依次指定函数名以及用括号括起的必要信息,如参数等。
在函数greet_user(username)的定义中,变量username是一个形参。形参是一个函数完成其工作所需的一个参数。
在代码greet_user("mike")中,值"mike"是一个实参。实参是调用函数时传递给函数的参数。
调用greet_user("mike")函数时,我们将实参"mike"传递给了函数greet_user(),这个值被存储在形参username。
位置实参:调用函数时,必须将函数调用中的每个实参都采用基于实参顺序的方式关联到函数定义中的一个形参中。
关键字实参:调用函数时,直接传递给函数名称-值对。此时不用考虑实参顺序。
def printID(ID, sname): print(ID + ":" + sname) printID('14', 'mike') #位置实参 printID(sname='lili', ID='15') #关键字实参 运行结果: 14:mike 15:lili
默认值:给形参指定默认值。在调用函数中给形参提供了实参时,则用指定的实参值。如果没有提供则使用形参默认值。
PS:使用默认值时,在形参列表中必须Ian列出没有默认值的形参,再列出有默认值的实参。才能让python正确解读位置实参。
def printID(ID, sname = 'mike'): print(ID + ":" + sname) printID('14') #<-here printID(sname='lili', ID='15') 运行结果: 14:mike 15:lili
返回简单值
def get_formatted_name(first_name, last_name): full_name = first_name + ' ' + last_name return full_name.title() musician = get_formatted_name('jimi', 'hendrix') print(musician) 运行结果: Jimi Hendrix
我们可以使用return语句在函数中返回值。
让实参可选
def get_formatted_name(first_name, last_name, middle_name=''): if middle_name: full_name = first_name + ' ' + middle_name + ' ' + last_name else: full_name = first_name + ' ' + last_name return full_name.title() musician = get_formatted_name('jimi', 'hendrix') print(musician) musician = get_formatted_name('john', 'hooker', 'lee') print(musician) 运行结果: Jimi Hendrix John Lee Hooker
如上所示,使用if条件语句,并将实参作为判断条件即可让实参可选。
将列表传递给函数后,不仅可以遍历列表,还能修改列表,并且这种修改时永久性的。
如果要禁止函数修改列表,可以传递列表的副本,比如:function_name(list_name[:])。
def make_pizza(*toppings): print(toppings) make_pizza('pepperoni') make_pizza('mushrooms', 'green peppers', 'extra cheese') 运行结果: ('pepperoni',) ('mushrooms', 'green peppers', 'extra cheese')
形参名*toppings中的星号表示创建一个名为 toppings 的空元组,并把所有收到的值封装在这个元组中。我们还可以使用循环语句将所有值打印出来。
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量的实参的形参放在最后。这样,python会先匹配位置实参和关键字实参,并把余下的实参都收集到最后一个形参中。
def make_pizza(size, *toppings): print("\nMaking a " + str(size) + "-inch pizza with the following toppings:") for topping in toppings: print("- " + topping) make_pizza(16, 'pepperoni') make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese') 运行结果: Making a 16-inch pizza with the following toppings: - pepperoni Making a 12-inch pizza with the following toppings: - mushrooms - green peppers - extra cheese
def build_profile(first, last, **user_info): profile = {} profile['first_name'] = first profile['last_name'] = last for key, value in user_info.items(): profile[key] = value return profile user_profile = build_profile('albert', 'einstein',location='princeton',field='physics') print(user_profile)
形参**user_info中的两个星号表示创建一个名为user_info的空字典,并将收到的所有名称-值对都封装到这个字典中。
导入整个模块
模块时扩展名为.py的文件,包含要导入到程序中的代码。使用import语句可以将模块导入。
#pizza.py def make_pizza(size, *toppings): print("\nMaking a " + str(size) +"-inch pizza with the following toppings:") for topping in toppings: print("- " + topping) #making_pizzas.py import pizza pizza.make_pizza(16, 'pepperoni') pizza.make_pizza(12,'mushrooms', 'green peppers', 'extra cheese') 运行结果: Making a 16-inch pizza with the following toppings: - pepperoni Making a 12-inch pizza with the following toppings: - mushrooms - green peppers - extra cheese
如果导入的是整个模块,调用的时候就要指定模块名:module_name.function_name()。
导入特定的函数
导入模块中特定的函数,可以使用以下方法:from module_name import function_name
用逗号分隔函数名,可导入任意数量函数:from module_name import function_0, function_1, function_2
这时候调用函数,无需使用句点,直接指定函数名,因为我们在import语句中显示导入了函数。
使用as给函数指定别名
为了防止冲突,或者函数名太长,可指定一个独一无二的别名,函数的另外一个名称,通用语法为:from module_name import function_name as fn
导入模块中的所有函数
使用星号(*)运算符可以导入模块中的所有函数,此时不用使用句点来调用函数。不过最好不要这样。语法为:from module_name import *。
第八章练习答案代码:http://www.xz577.com/j/129.html
第九章
面向对象编程时,都会遇到一个概念,类,python也有这个概念,下面我们通过代码来深入了解下。
class Dog(): def __init__(self, name, age): self.name = name self.age = age def sit(self): print(self.name.title() + " is now sitting.") def roll_over(self): print(self.name.title() + " rolled over!") my_dog = Dog('willie', 6) print("My dog's name is " + my_dog.name.title() + ".") print("My dog is " + str(my_dog.age) + " years old.") 运行结果: My dog's name is Willie. My dog is 6 years old.
class关键字:来定义一个类。类名通常首字母为大写。
__init__方法:特殊方法,每当实例类的时候都会运行。其中的形参self必不可少,而且必须位于最前面。
self形参:类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称,但是在调用这个方法的时候用不为这个参数赋值,Python会提供这个值。这个特别的变量指对象本身。
实例的方法是指定类名以及需要传入的实参。
要访问实例的属性或者调用方法,可使用句点表示法。
class Car(): def __init__(self, make, model, year): self.make = make self.model = model self.year = year self.odometer_reading = 0 #<- here def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' + self.model return long_name.title() def read_odometer(self): print("This car has " + str(self.odometer_reading) + " miles on it.") my_new_car = Car('audi', 'a4', 2016) print(my_new_car.get_descriptive_name()) my_new_car.read_odometer() 运行结果: 2016 Audi A4
可以看到,我们给Car类的属性指定了默认值self.odometer_reading = 0。如果要修改它,我们可以通过实例直接访问它并修改,比如:my_new_car.odometer_reading = 23。或者通过方法修改属性的值。或者通过方法对属性的值进行递增。
class Car(): --snip-- #通过方法修改属性的值 def update_odometer(self, mileage): self.odometer_reading = mileage #通过方法对属性的值进行递增 def increment_odometer(self, miles): self.odometer_reading += miles
一个类继承另一个类时,他将自动获得另一个类的所有属性和方法;原有的类称为父类,新类称为子类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
现在我们有一个Car类如下
class Car(): def __init__(self, make, model, year): self.make = make self.model = model self.year = year self.odometer_reading = 0 #<- here def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' + self.model return long_name.title() def read_odometer(self): print("This car has " + str(self.odometer_reading) + " miles on it.") def update_odometer(self, mileage): self.odometer_reading = mileage def increment_odometer(self, miles): self.odometer_reading += miles
创建子类实例时,python首先给父类所有属性赋值。
接下来我们创建新类EleCar
class Car(): --snip-- class ElectricCar(Car): def __init__(self, make, model, year): super().__init__(make, model, year) my_tesla = ElectricCar('tesla', 'model s', 2016) print(my_tesla.get_descriptive_name()) 运行结果: 2016 Tesla Model S
首先父类必须位于子类前面。
super()是一个特殊函数,将父类和子类关联起来。使得可调用子类的父类的方法__init__(),让子类包父类的所有属性。
给子类定义属性和方法没有任何限制,比如:
class Car(): --snip-- class ElectricCar(Car): def __init__(self, make, model, year): super().__init__(make, model, year) self.battery_size = 70 def describe_battery(self): print("This car has a " + str(self.battery_size) + "-kWh battery.") my_tesla = ElectricCar('tesla', 'model s', 2016) print(my_tesla.get_descriptive_name()) my_tesla.describe_battery() 运行结果: 2016 Tesla Model S This car has a 70-kWh battery.
如果需要重写父类的方法,只需定义一个与重写的父类方法同名的方法即可。
python还可以使用其他类的实例作为自己类的属性。
类似函数,类也可以保存为.py文件形成模块从而进行导入。
导入单个类:
from model_name import class_name
导入一个模块的多个类:
from model_name import class_name1, class_name2
导入一个模块所有类:
from model_name import *
导入整个模块:
import model_name
还可以在一个模块中导入另一个模块
其是一组模块。模块collections中有一个类OrderedDict。字典的键值对没有顺序,但是这个类几乎和字典相同,区别只在于记录了键值对的顺序。
from collections import OrderedDict favorite_languages = OrderedDict() favorite_languages['jen'] = 'python' favorite_languages['sarah'] = 'c' favorite_languages['edward'] = 'ruby' favorite_languages['phil'] = 'python' for name, language in favorite_languages.items(): print(name.title() + "'s favorite language is " + language.title() + ".") 运行结果: Jen's favorite language is Python. Sarah's favorite language is C. Edward's favorite language is Ruby. Phil's favorite language is Python.
第九章练习答案代码:http://www.xz577.com/j/129.html
从文件中读取数据
读取文件、文件路径
pi_digits.txt 3.1415926535 8979323846 2643383279 file_reader.py fillename = 'pi_digits.txt' #读取整个文件 with open(filename) as file_object: contents = file_object.read() print(contents.rstrip()) #逐行读取 with open(filename) as file_object: for line in file_object: print(line.rstrip()) 运行结果: 3.1415926535 8979323846 2643383279 3.1415926535 8979323846 2643383279
函数open():接受要打开文件的文件名作为参数。如果没有指定路径,则在当前执行的文件所在的目录中查找指定的文件。此函数返回一个表示文件的对象。
关键字with:表示在不需要访问文件之后将其关闭。
函数read():用于从文件读取指定的字节数,如果未给定或为负则读取所有。到达文件末尾时返回一个空字符串(空行)。打印时可使用函数rstrip()删掉。
相对文件路径:相对于当前运行的程序所在目录。
绝对文件路径:完整的文件路径。
使用关键字with时,open()返回的文件对象只在with代码块内使用。如果要在with代码块外访问文件内容,可在里边将文件各行存储在一个列表中待后续使用。
filename = 'pi_30_digits.txt' with open(filename) as file_object: lines = file_object.readlines() pi_string = '' for line in lines: pi_string += line.strip() print(pi_string) print(len(pi_string)) 运行结果: 3.141592653589793238462643383279 32
注意:读取文本文件时,python将其中所有文本都解读稳字符串。
将文本写入文件时,调用open()时需要提供另外一个实参。其中,第一个实参时要打开的文件的名称。第二个实参是指定的模式:读取模式('r')、写入模式('w')、附加模式('a')、读取和写入模式('r+')。如果省略将以默认的只读模式打开文件。
如果写入的文件不存在,则open()会自动创建它。如果以写入模式打开文件且指定文件对象存在,则返回文件对象前会清空改文件。
filename = 'programming.txt' with open(filename, 'w') as file_object: file_object.write("I love programming.") file_object.write("I love programming.\n") file_object.write("I love creating new games.\n") 运行结果: programming.txt I love programming.I love programming. I love creating new games.
注意:python只能将字符串写入文本文件。函数write()不会在写入的文本末尾添加换行符。
如果要写入文件并不覆盖,可以使用附加模式。写入的行都会添加到文件末尾。指定文件不存在时,自动创建。
filename = 'programming.txt' with open(filename, 'a') as file_object: file_object.write("I also love finding meaning in large datasets.\n") file_object.write("I love creating apps that can run in a browser.\n") 运行结果: programming.txt I love programming.I love programming. I love creating new games. I also love finding meaning in large datasets. I love creating apps that can run in a browser.
异常时python用来管理程序执行期间发生错误的特殊对象。
异常是使用try-except代码块处理的。如果try代码块中的代码运行无误,则跳过except代码块;如果try代码块中的代码运行出现错误,则运行except代码块。
print(5/0) 运行结果: Traceback (most recent call last): File "division.py", line 1, in <module> print(5/0) ZeroDivisionError: division by zero
ZeroDivisionError是一个异常对象。
try: print(5/0) except ZeroDivisionError: print("You can't divide by zero!") 运行结果: You can't divide by zero!
使用try-except代码块也可以隐藏Traceback,防止恶意攻击等等。
除了try-except代码块还有try-except-else代码块。其中try代码块只包含可能导致错误的代码,而依赖于其成功执行的代码都放在else代码块中。
如果希望发生错误时继续运行,可以在except代码块中使用pass语句。pass语句还充当了占位符,用来提醒我们在程序的某个地方什么都没有做。
模块json可以让我们将简单的python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。并且还可以使用json在python程序中分享数据。
函数json.dump()接受两个实参:要存储的数据以及可用于存储数据的文件对象。
import json numbers = [2, 3, 5, 7, 11, 13] filename = 'numbers.json' with open(filename, 'w') as f_obj: json.dump(numbers, f_obj) 运行结果: numbers.json [2, 3, 5, 7, 11, 13]
函数json.load()加载存储在指定文件对象的信息。
import json filename = 'numbers.json' with open(filename) as f_obj: numbers = json.load(f_obj) print(numbers) 运行结果: [2, 3, 5, 7, 11, 13]
下面将存取数据和异常结合一下程序运行时,从文件获取用户名,若文件不存在,则提示输入用户名并存储:
import json # 如果以前存储了用户名就加载它 # 否则提示用户输入用户名并存储它 filename = 'username.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input("What is your name? ") with open(filename, 'w') as f_obj: json.dump(username, f_obj) print("We'll remember you when you come back, " + username + "!") else: print("Welcome back, " + username + "!") 运行结果: #如果文件不存在 What is your name? Eric We'll remember you when you come back, Eric! #如果文件存在 Welcome back, Eric!
第十章练习答案代码:http://www.xz577.com/j/129.html
第十一章 测试代码
单元测试和测试用例
Python标准库中的模块unittest提供了代码测试工具。单元测试用于核实函数的某个方面没有问题;测试用例时一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。。
测试示例
现在有这么一个函数以及其单元测试:
#name_function.py def get_formatted_name(first, last, middle=''): if middle: full_name = first + ' ' + middle + ' ' + last else: full_name = first + ' ' + last return full_name.title()
#test_name_ function.py import unittest from name_function import get_formatted_name class NamesTestCase(unittest.TestCase): def test_first_last_name(self): formatted_name = get_formatted_name('janis', 'joplin') self.assertEqual(formatted_name, 'Janis Joplin') unittest.main() 运行结果: . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
首先导入模块unittest和要测试的函数get_formatted_ name()。接着创建一个单元测试类NamesTestCase,记住,类名最好包含Test并且继承unittest.TestCase类,这样python才能识别为测试类。
接着编写好测试方法之后(在这里我们只写了一个方法:只有名和姓的姓名能否被正确地格式化),当我们运行 test_name_ function.py时,所有以test开头的方法都将自动运行。
最后,我们使用了unittest类的一个断言方法unittest.assertEqual(),用来核实得到的结果是否与期望的结果一致。代码行unittest.main()让python运行文件中的测试。
各种断言方法
python的unittest.TestCase类中提供了很多断言方法(检查应该满足的条件是否确实满足,返回布尔值)。
下面提供了六种常用的断言方法:
当测试方法多了起来的时候,每次都要创建一个实例对象很麻烦,而python的unittest.TestCase类中提供了方法setup(),让我们只需创建一次实例对象即可让所有方法使用该对象(运行测试时如果包含这个函数将会首先运行它)。
注意:测试通过时为一个句点,测试引发错误时为一个E,测试导致断言失败时为一个F。
第十一章练习答案代码:http://www.xz577.com/j/129.html
Django是一个Web框架——一套用于帮助开发交互式网站的工具,其能够响应网页的请求,还能轻松读写数据库,管理用户等。
建立项目
目标:编写一个名为“学习笔记”的Web应用程序,让用户能够记录学习笔记。其主页时对这个网站的描述,可邀请用户注册或登录。用户登录后可以创建新主题、添加新条目以及阅读既有条目。
建立虚拟环境
在win10下安装Virtualenv。在cmd中,执行:pip install virtualenv。接着开始创建第一个虚拟环境(参考http://www.jb51.net/article/85527.htm 使用Virtualenv来创建虚拟环境)并安装django。
在项目文件夹中执行:virtualenv venv
创建好虚拟环境之后。接着激活环境,执行:venv\Scripts\activate(退出venv环境,运行命令:deactivate)
如果看到命令行前面有(venv),就代表激活成功,如下图
创建并激活虚拟环境后,接着安装django,执行命令pip install django==1.11.7。
在Django创建项目和数据库
在处于活动的虚拟环境下,执行命令django-admin.py startproject learning_log .即创建一个名为learning_log的项目,命令末尾的句点让新项目使用合适的目录结构。
django除了创建一个名为learning_log的目录还创建了一个名为manage.py的文件。
目录learning_log包含4个文件,其中最重要的是settings.py、urls.py、wsgi.py。
django将大部分与项目相关的信息都存储在数据库中。创建django使用的数据库,执行命令python manage.py migrate。我们把修改数据库称为迁移数据库。
执行完之后django将创建一个文件db.sqlite3。SQLite是一个使用单个文件的俄数据库。
运行项目启动服务器,可执行命令runserver。接着在浏览器输入http://127.0.0.1:8000/即可查看运行中的项目。如果要关闭服务器,按ctrl+c。
创建应用程序
django项目由一系列应用程序组成,他们协同工作,让项目称谓一个整体。首先我们执行命令python manage.py startapp learning_logs。
定义模型
打开刚刚我们创建的文件夹,并修改models.py:
from django.db import models class Topic(models.Model): text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): return self.text
我们为模型创建了一个名为Topic的类,他继承了Model——django中一个定义模型基本功能的类。Topic类只有两个属性:text和date_added。
text是一个CharField——由字符或文本组成的数据,可存储少量文本。传入参数**max_length**为文本最大长度。
date_added是一个DateField——记录日期和时间的数据。传入的实参auto_add_now=True告诉django每当用户新建主题时,将这个属性自动设置为当前日期和时间。
打开learning_log中的setting.py并添加
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'learning_logs', #<-here ]
这是一个元组,包含了django项目中的所有应用程序。接着执行python manage.py makemigrations learning_logs让django确定如何修改数据库。最后应用迁移,执行python manage.py migrate。
1.创建超级用户,执行命令python manage.py createsuperuser。
2.向管理网站注册模型,打开 learning_logs下的admin.py,并修改为:
from django.contrib import admin from learning_logs.models import Topic admin.site.register(Topic)
这写代码导入了我们要注册的模型Topic,接着使用admin.site.register(Topic)让django通过管理网站管理我们的模型。
接着运行服务器,访问http://127.0.0.1:8000/admin并使用着急用户帐号密码登录。
3.添加主题,点击Topic之后进入主题网页,此时可以通过点击add进行主题的添加,添加完成之后点击save进行保存。
learning_logs\models.py from django.db import models class Topic(models.Model): text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): return self.text class Entry(models.Model): topic = models.ForeignKey(Topic) text = models.TextField() date_added = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = 'entries' def __str__(self): return self.text[:50] + "..."
ForeignKey是数据库的外键,引用数据库中的另一个记录。Entry类中的Meta类用于存储管理模型的额外信息,这里设置了一个特殊属性,让django在需要时使用 Entries 来表示多个条目。
由于我们添加了一个新模型,因此需要再次迁移数据库。执行命令python manage.py makemigrations learning_logs以及python manage.py migrate.
修改如下文件
learning_logs\admin.py from django.contrib import admin from learning_logs.models import Topic, Entry admin.site.register(Topic) admin.site.register(Entry)
返回管理网站可以发现多了一个板块
接着点击新板块的add添加以下主题和讨论,并点击保存给我们之后测试使用。
国际象棋:The opening is the first part of the game, roughly the first ten moves or so. In the opening, it’s agood idea to do three things— bring out your bishops and knights, try to control the center of theboard, and castle your king.
攀岩:One of the most important concepts in climbing is to keep your weight on your feet asmuch as possible. There’s a myth that climbers can hang all day on their arms. In reality, goodclimbers have practiced specific ways of keeping their weight over their feet whenever possible
在输入以上数据之后,可以通过交互式终端会话访问这些数据,在测试项目和排除故障的时候会用上。执行命令python manage.py shell可以打开shell会话。
使用django创建网页通常分三个阶段:定义URL、编写视图和编写模板。
首先必须定义URL模式,其描述了URL是如何设计的,让django知道如何将浏览器请求与网站URL匹配,以确定返回哪个网页。每个URL都被映射到特定的视图——视图函数获取并处理网页所需的数据。视图函数通常调用一个模板,后者生成浏览器能够理解的网页。
映射URL
#learning_log/urls.py from django.conf.urls import url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'', include('learning_logs.urls', namespace='learning_logs')), ]
我们添加了一行代码来包含模块learning_logs.urls。实参namespace能够将learning_logs的URL同项目中的其他URL区分开来。
默认的urls.py保存在learning_log文件夹中,我们需要在learning_logs中创建另外一个urls.py如下
#learning_logs/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), ]
此模块中,urlpatterns变量是一个列表,包含可在应用程序learning_logs中请求的网页。
URL模式是一个对函数url()的调用,其包含三个实参:
视图函数接受请求中的信息,准备生成网页所需的数据,并将这些数据发送给浏览器。
修改并添加:
#learning_logs/views.py from django.shortcuts import render def index(request): return render(request, 'learning_logs/index.html')
函数render()根据视图提供的数据渲染相应。
当URL请求与我们定义的模式匹配时,django将在文件views.py中查找函数index(),再将请求对象传递给这个视图函数。
而这里的函数render()提供了两个实参:原始请求对象以及一个可用于创建网页的模板。
模板定义了网页的结构。模板指定了网页是啥样的。而每当网页被请求时,django将填入相关的数据。
在文件夹learning_logs中创建文件夹templates,又在这个文件夹中创建文件夹learning_logs。接着在最里面的文件夹learning_logs中新建一个index.html。
<!-- index.html --> <p>Learning Log</p> <p>Learning Log helps you keep track of your learning, for any topic you're learning about.</p>
最后在虚拟环境中runserver,并打开http://127.0.0.1:8000/可以看到
创建其他网页
我们接下来扩充“学习笔记”项目,创建两个显示数据的网页,其中一个列出所有的主题,另一个显示特定主题的所有条目。
模板继承
编写一个包含通用元素的父模板,并让每个网页都继承这个模板,而不必在每个网页中重复定义这些通用元素。这样我们可以专注于开发每个网页的独特部分。
1.父模板
创建名为base.html的模板,和index.html放在同一个目录,这个文件包含所有页面都有的元素——顶端的标题。
<p> <a href="{% url 'learning_logs:index' %}">Learning Log</a> </p> {% block content %}{% endblock content %}
在第二行中使用了一个模板标签,模板标签使用大括号和百分号({% %})表示,其是一小段生成网页中显示信息的代码。
模板标签{% url 'learning_logs:index' %}生成一个URL,该URL与learning_logs/urls.py中定义的名为index的URL模式匹配。在这个示例中,learning_logs是一个命名空间,而index是该命名空间中的一个名称独特的URL模式。
在结尾中插入了一对块标签,这个块名为content,是一个占位符,其包含的信息将由子模板指定。
2.子模板
重新修改index.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Learning Log helps you keep track of your learning, for any topic you're learning about.</p> {% endblock content %}
子模板第一行{% extends %}表示继承了指定的父模板。这行代码会导入base.html的所有内容,让index.html的内容能够添加在父模板预留的content块中。
第二行,我们插入了content的{% content %}标签,以定义content块。不是从父模板继承的内容都包含在里边。
最后一行,{% endblock content %}表示定义结束。
1.URL模式
#learning_logs/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^topics/$', views.topics, name='topics'), ]
新添加的正则表达式表示匹配的URL为:基础URL后面跟着topics。接着匹配成功的话将请求都交给views.py中的函数topics()进行处理。
2.视图
函数topics()需要从数据库获取数据,发送给模板。
from django.shortcuts import render from .models import Topic def index(request): return render(request, 'learning_logs/index.html') def topics(request): topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)
首先导入与所需数据相关联的模型。接着函数topics()包含一个形参:从服务器收到的request对象。
函数的第二行表示查询数据库,请求Topic对象,并按属性date_added对数据进行排序。最后把返回的查询集春处在topics变量中。
函数的第三行定义了一个发给模板的上下文:是一个字典,其中的键是模板中用来访问数据的名称,值是发送给模板的数据。
函数返回的时候,即创建使用数据的网页时,我们把对象request和模板路径,还有变量context传递给render()。
3.模板
创建一个文件 topics.html 和 index.html 同一文件夹。
{% extends "learning_logs/base.html" %} {% block content %} <p>Topics</p> <ul> {% for topic in topics %} <li>{{ topic }}</li> {% empty %} <li>No topics have been added yet.</li> {% endfor %} </ul> {% endblock content %}
首先继承父模板,接着定义content块。接着使用for循环的模板标签遍历字典context的列表topics。在模板中,不能使用缩进来表示循环结束,应当使用标签{% endfor %}来指出循环的结束。双花括号({{ topic }})里的模板变量都会被替换为topic的当前值。
循环中间的模板标签{% empty %}告诉django在列表topics为空时该怎么办:这里是打印一条消息,告诉用户No topics have been added yet.。
最后修改父模板,使其包含显示所有主题的页面连接:
<p> <a href="{% url 'learning_logs:index' %}">Learning Log</a> - <a href="{% url 'learning_logs:topics' %}">Topics</a> </p> {% block content %}{% endblock content %}
运行本地服务器可以看到
成功了!
显示特定主题的网页
1.URL模式
#learning_logs/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^topics/$', views.topics, name='topics'), url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'), ]
显示特定主题的页面的URL模式与前面的不同,因为其将使用主题的id属性来指出请求的是哪个主题。
+ r'^topics/(?P<topic_id>\d+)/$'中的第二部分/(?P<topic_id>\d+)/表示与包含在两个斜杠内的整数匹配,并将这个整数存储在实参topic_id中。这部分表达式两边的括号捕获URL中的值;?P<topic_id>将匹配的值存储到topic_id中;\d+与包含在两个斜杠内的任何数字都匹配,不管其多少位。
这个模式匹配成功时,将会调用视图函数topic(),并把topic_id的值作为实参传递进去。
2.视图
from django.shortcuts import render from .models import Topic def index(request): return render(request, 'learning_logs/index.html') def topics(request): topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context) def topic(request, topic_id): topic = Topic.objects.get(id=topic_id) entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context)
函数get()获取指定的主题。data_added前面的减号指定按降序排列,即先显示最近的条目。
3.模板
创建一个文件 topic.html 和 index.html 同一文件夹。
{% extends 'learning_logs/base.html' %} {% block content %} <p>Topic: {{ topic }}</p> <p>Entries:</p> <ul> {% for entry in entries %} <li> <p>{{ entry.date_added|date:'M d, Y H:i' }}</p> <p>{{ entry.text|linebreaks }}</p> </li> {% empty %} <li> There are no entries for this topic yet. </li> {% endfor %} </ul> {% endblock content %}
因为我们变量topic包含在字典context中,所以我们可以使用他。
每个项目列表项都将列出两项信息:条目的时间戳和完整的文本。列出时间戳date_added的值,我们可以在模板中使用竖线(|)表示模板过滤器——对模板变量的值进行修改的函数。
过滤器date:'M d, Y H:i'表示以这样的格式显示时间戳:January 1, 2015 23:00。
过滤器linebreaks表示自动换行。
4.将显示所有主题的页面中的每个主题都设置为链接
修改topics.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Topics</p> <ul> {% for topic in topics %} <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a> <!-- here --> {% empty %} <li>No topics have been added yet.</li> {% endfor %} </ul> {% endblock content %}
运行本地服务器可以看到
在创建用户账户身份验证系统之前,先添加几个页面,让用户能偶输入数据。添加新主题、添加新条目以及编辑既有条目。
1.用于添加主题的表单
创建一个forms.py文件与models.py放在同一目录下。
from django import forms from .models import Topic class TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''}
TopicForm类继承了forms.ModelForm,其包含一个内嵌的Meta类。Meta类指定了根据哪个模型创建表单,以及在表单中包含哪些字段。
在这里,我们根据模型Topic创建了一个表单,该表单只包含字段text,且不用为字段text生成标签。
2.URL模式new_topic
# learning_logs/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^topics/$', views.topics, name='topics'), url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'), url(r'^new_topic/$', views.new_topic, name='new_topic'), ]
# learning_logs/views.py from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Topic from .forms import TopicForm --snip-- def new_topic(request): if request.method != 'POST': #未提交数据,创建一个新表单 form = TopicForm() else: #POST提交的数据,对数据进行处理 form = TopicForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context)
用户提交主题后使用HttpResponseRedirect类将用户重定向到网页topics。函数reverse()根据指定的URL模式确定URL,即django将在页面被请求时生成URL。
4.GET请求和POST请求
GET请求:从服务器读取数据;
POST请求:通过表单提交信息。
在上述代码的else部分中,我们将使用-存储在request.POST的用户输入数据-创建一个TopicForm实例并赋值给对象form。
将提交的信息保存到数据库之前,得检查其是否有效。函数is_valid()核实用户填写了所有必不可少的字段,且输入的数据与要求的字段类型一致。如果所有字段有效,调用函数save()将表单数据写入数据库。
最后使用函数reverse()获取页面topics的URL,并将其传递给HttpResponseRedirect(),后者将用户浏览器重定向到页面topics查看用户刚输入的主题。
5.模版new_topic
<!-- new_topic.html --> {% extends "learning_logs/base.html" %} {% block content %} <p>Add a new topic:</p> <form action="{% url 'learning_logs:new_topic' %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name="submit">add topic</button> </form> {% endblock content %}
定义表单使的实参action指定了服务器将提交的表单数据发送的目的地。在这里我们将它发回给视图函数new_topic()。实参method让浏览器以POST请求的方式提交数据。
模板标签{% csrf_token %}防止攻击者利用表单来获得对服务器未经授权的访问(跨站请求伪造)。
模板变量{{ form.as_p }}让django自动创建显示表单所需的全部字段。修饰符as_p表示以段落格式渲染所有表单元素。
6.链接到页面new_topic
在页面 topics中添加一个到页面new_topic的链接
{% extends "learning_logs/base.html" %} {% block content %} <p>Topics</p> <ul> --snip-- </ul> <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a> {% endblock content %}
运行服务器可以看到
1.用于添加新条目放入表单
定制一个与模型Entry相关联的表单
from django import forms from .models import Topic, Entry class TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''} class EntryForm(forms.ModelForm): class Meta: model = Entry fields = ['text'] labels = {'text': ''} widgets = {'text': forms.Textarea(attrs={'cols': 80})}
同样导入Entry,新类继承forms.ModelForm,新类包含的Meta类指出表单基于的模型以及表单中包含的字段。
在Meta类中我们定义了属性widgets。小部件(widget)是一个HTML表单元素,比如单行文本框、多行文本区域或者下拉列表。通过设置这个属性覆盖django默认的小部件,并把文本区域的宽度设置为80列,而不是默认的40列。
2.URL模式new_entry
修改urls.py
#learning_logs/urls.py from django.conf.urls import url from . import views urlpatterns = [ #--snip-- url(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'), ]
这个URL模式与形式为http://127.0.0.1:8000/new_entry/id/的URL匹配,其中id是一个与主题id匹配的数字。(?P<topic_id>\d+)捕获一个数值存储在变量topic_id中。模式匹配时django把请求和主题id发给函数new_entry()。
3.视图函数new_topic
修改views.py
from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Topic from .forms import TopicForm, EntryForm #--snip-- def new_entry(request, topic_id): topic = Topic.objects.get(id=topic_id) if request.method != 'POST': #未提交数据,创建一个空表单 form = EntryForm() else: #POST提交的数据,对数据进行处理 form = EntryForm(data=request.POST) if form.is_valid(): new_entry = form.save(commit=False) new_entry.topic = topic new_entry.save() return HttpResponseRedirect(reverse('learning_logs:topic',args=[topic_id])) context = {'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)
new_entry = form.save(commit=False)
表示创建一个新的条目对象并把它存储到new_entry中,但是不保存到数据库。接着把new_entry的topic设置为此函数开头从数据库中获取的主题,然后调用save()保存到数据库,这样才能确保与正确的主题相关联。
在调用reserve()时需要提供两个实参。第一个是根据它来生成URL的URL模式的名称。第二个是列表args,其中存储着要包含在URL中的所有实参。
4.模板new_entry
类似new_topic.html
#new_entry.html {% extends "learning_logs/base.html" %} {% block content %} <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p> <p>Add a new entry:</p> <form action="{% url 'learning_logs:new_entry' topic.id %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name='submit'>add entry</button> </form> {% endblock content %}
表单的实参action包含URL中的topic_id值,让视图函数能够把新条目关联到正确的主题。
5.链接到页面new_entry
在显示特定主题的页面中添加到页面new_entry的链接
#topic.html {% extends 'learning_logs/base.html' %} {% block content %} <p>Topic: {{ topic }}</p> <p>Entries:</p> <p> <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a> </p> <ul> #--snip-- </ul> {% endblock content %}
运行服务器可以看到
1.URL模式edit_entry
此页面的URL需要传递要编辑的条目的ID。
#learning_logs/urls.py from django.conf.urls import url from . import views urlpatterns = [ #--snip-- url(r'^edit_entry/(?P<entry_id>\d+)/$', views.edit_entry,name='edit_entry'), ]
在URLhttp://127.0.0.1:8000/edit_entry/id/中传递的id存储在形参entry_id中。模式匹配时把请求发送给视图函数edit_entry()。
2.视图函数edit_entry
页面edit_entry收到GET请求时,edit_entry()返回一个表单,让用户对条目进行编辑。该页面收到POST请求(条目文本经过修订)时,把修改后的文本保存到数据库中。
修改views.py
from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Topic, Entry from .forms import TopicForm, EntryForm #--snip-- def edit_entry(request, entry_id): entry = Entry.objects.get(id=entry_id) topic = entry.topic if request.method != 'POST': #初次请求,使用当前条目填充表单 form = EntryForm(instance=entry) else: # POST提交的数据,对数据进行处理 form = EntryForm(instance=entry, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topic',args=[topic.id])) context = {'entry': entry, 'topic': topic, 'form': form} return render(request, 'learning_logs/edit_entry.html', context)
首先获取需要修改的条目对象以及该条目相关联的主题。当请求方法为GET时执行if代码块,实参instance=entry表示使用既有条目对象中的信息填充创建的表单。
处理POST请求时,传递实参instance=entry和data=request.POST让django根据既有条目对象创建表单实例并根据request.POST中的相关数据对其进行修改。
3.模板edit_entry
类似new_entry.html
#edit_entry.html {% extends "learning_logs/base.html" %} {% block content %} <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p> <p>Edit entry:</p> <form action="{% url 'learning_logs:edit_entry' entry.id %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name="submit">save changes</button> </form> {% endblock content %}
在标签{% url %}中将条目id作为实参。
5.链接到页面edit_entry
在显示特定主题的页面中给每个条目添加到页面edit_entry的链接:
#topic.html #--snip-- {% for entry in entries %} <li> <p>{{ entry.date_added|date:'M d, Y H:i' }}</p> <p>{{ entry.text|linebreaks }}</p> <p> <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a> </p> </li> #--snip--
运行服务器可以看到
创建用户账户
这一部分我们来创建用户注册和身份验证系统。
应用程序users
首先使用命令python manage.py startapp users创建名为users的应用程序,现在你的目录应该和下面一样。
1.将应用程序users添加到settings.py
#--snip-- INSTALLED_APPS = ( #--snip-- 'learning_logs', 'users', ) #--snip--
2.创建users的URL模式
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', include('users.urls', namespace='users')), url(r'', include('learning_logs.urls', namespace='learning_logs')), ]
添加的代码与任何以单词users大头的URL(如http://localhost:8000/users/login/)都匹配。
在learning_log/users/中新建一个urls.py。
from django.conf.urls import url from django.contrib.auth.views import login from . import views urlpatterns = [ url(r'^login/$', login, {'template_name': 'users/login.html'},name='login'), ]
首先导入默认视图login,接着登录页面的URL模式与URLhttp://localhost:8000/
users/login/匹配。实参login(不是views.login)告诉django将请求发送给视图login。接着传递了一个字典指定要查找的模板。
1.模板login.html
同样,新建一个路径users/templates/users/,接着在这个路径下新建login.html。
{% extends "learning_logs/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 'users:login' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">log in</button> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> </form> {% endblock content %}
这个模板同样继承了base.html。一个应用程序中的模板可继承另一个应用程序中的模板。
表单errors属性可以在输入的用户名-密码错误时显示错误消息。
将实参action设置为登录页面的URL,让登录视图处理表单。表单元素next告诉django在用户成功登录后将其重定向到主页。
2.链接到登录页面
在base.html中添加到登录页面的链接,但当用户已登录时,为了不显示这个链接,我们把它嵌套在{% if %}标签中。
<p> <a href="{% url 'learning_logs:index' %}">Learning Log</a> - <a href="{% url 'learning_logs:topics' %}">Topics</a> - {% if user.is_authenticated %} Hello, {{ user.username }}. {% else %} <a href="{% url 'users:login' %}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
在django身份验证系统中,每个模板都可以使用变量user,变量的is_authenricated属性:如果用户已登录,该属性值为True,否则为Faule;变量的username属性为用户名。
3.使用登录页面
如果你之前登录过管理员,记得先在http://127.0.0.1:8000/admin注销登录。然后重新登录我们的学习笔记网页登录页面http://127.0.0.1:8000/users/login/,并使用管理员的帐号密码登录。
1.注销URL
#users/urls.py from django.conf.urls import url from django.contrib.auth.views import login from . import views urlpatterns = [ url(r'^login/$', login, {'template_name': 'users/login.html'},name='login'), url(r'^logout/$', views.logout_view, name='logout'), ]
2.视图函数logout_view()
django自带函数模块logout(),我们只需导入他,并调用他重定向到主页。
from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib.auth import logout def logout_view(request): logout(request) return HttpResponseRedirect(reverse('learning_logs:index'))
在这里,request对象作为实参传递给函数logout()。
3.链接到注销视图
在base.html中添加注销链接。为了使得仅当用户登录后才能看见他,我们将他放在标签{% if user.is_authenticated %}中。
#base.html <p> <a href="{% url 'learning_logs:index' %}">Learning Log</a> - <a href="{% url 'learning_logs:topics' %}">Topics</a> - {% if user.is_authenticated %} Hello, {{ user.username }}. <a href="{% url 'users:logout' %}">log out</a> {% else %} <a href="{% url 'users:login' %}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
运行服务器可以看到
1.注册页面的URL模式
from django.conf.urls import url from django.contrib.auth.views import login from . import views urlpatterns = [ url(r'^login/$', login, {'template_name': 'users/login.html'},name='login'), url(r'^logout/$', views.logout_view, name='logout'), url(r'^register/$', views.register, name='register'), ]
2.视图函数reigster()
当注册页面首次被请求时,视图函数register()显示一个空的注册表单,用户提交后注册成功并自动登录。
from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib.auth import login, logout, authenticate from django.contrib.auth.forms import UserCreationForm def logout_view(request): --snip-- def register(request): if request.method != 'POST': #显示空的注册表单 form = UserCreationForm(); else: #处理填写好的表单 form = UserCreationForm(data=request.POST) if form.is_valid(): new_user = form.save() #让用户自动登录并重定向到主页 authenticate_user = authenticate(username=new_user.username, password=request.POST['password1']) login(request, authenticate_user) return HttpResponseRedirect(reverse('learning_logs:index')) context = {'form': form} return render(request, 'users/register.html', context)
函数login()和函数 authenticate()可以让用户注册成功后自动登录。
在注册的时候,如果用户提交的注册表单数据有效,调用方法save()并返回新创建的用户对象,保存在new_user中。
自动登录过程中,调用authenticate()并把实参new_user.username和密码(因为表单有效,两个密码相同,所以我们使用第一个密码'password1')传给它。接着我们将authenticate()返回的通过身份验证的用户对象保存在authenticate_user中。最后调用函数login(),并把对象request和authenticate_user传给他。最后完成自动登录。
3.注册模板
创建一个register.html,和login.html放在一起。
{% extends "learning_logs/base.html" %} {% block content %} <form method="post" action="{% url 'users:register' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">register</button> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> </form> {% endblock content %}
4.链接到注册页面
<p> <a href="{% url 'learning_logs:index' %}">Learning Log</a> - <a href="{% url 'learning_logs:topics' %}">Topics</a> - {% if user.is_authenticated %} Hello, {{ user.username }}. <a href="{% url 'users:logout' %}">log out</a> {% else %} <a href="{% url 'users:register' %}">register</a> - <a href="{% url 'users:login' %}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
运行服务器可以看到
这部分我们将创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。
修改模型Topic,让每个主题都归属特定用户。
装饰器是放在函数定义前面的指令,python在函数运行前,根据它来修改函数代码的行为。
装饰器@login_required:让特定的页面只允许已登录的用户访问。
1.限制对topics页面的访问
修改learning_logs/views.py
#--snip-- from django.core.urlresolvers import reverse from django.contrib.auth.decorators import login_required from .models import Topic, Entry #--snip-- @login_required def topics(request): #--snip--
加上装饰器之后,python会在运行topics()的代码前先运行login_required()的代码。
login_required()会检查用户是否已登录,仅当用户已登录时,django才运行topics()。如果用户未登录,则重定向到登录页面。
为实现重定向,修改settings.py,在文件末尾加上 LOGIN_URL = '/users/login/' 。现在未登录的用户会重定向到 LOGIN_URL 指定的URL。
2.全面限制对项目“学习笔记”的访问
现在,我们不限制对主页、注册、注销页面的访问,而限制对其他所有页面的访问。
在learning_logs/views.py中,除了index()意外,每个视图都加上 @login_required。
--snip-- @login_required def topics(request): --snip-- @login_required def topic(request, topic_id): --snip-- @login_required def new_topic(request): --snip-- @login_required def new_entry(request, topic_id): --snip-- @login_required def edit_entry(request, entry_id): --snip--
修改模型Topic,添加一个关联到用户的外键。然后必须对数据库进行迁移。最后对部分视图进行修改,使其只显示与当前用户登录的相关联的数据。
1.修改模型Topic
修改learning_logs/models.py
from django.db import models from django.contrib.auth.models import User class Topic(models.Model): text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User) def __str__(self): return self.text class Entry(models.Model): --snip--
2.确定当前有哪些用户
为了简单,我们将既有主题都关联到超级用户上,先确定超级用户的id,打开shell。
确定当前用户只有admin,id为1。(或者还有其他你自己创建的用户)
3.迁移数据库
获取id之后,迁移数据库。
首先执行命令makemigrations后,django指出我们视图给既有模型Topic添加一个必不可少(不可为空)的字段(无默认值),并提供了两种选择:要么现在提供默认值,要么退出并在models.py中添加默认值。在这里我选了第一个选项,输入默认值,接着输入了用户id = 1。最后django使用这个值迁移数据库,在模型Topic中添加字段owner。
接下来执行迁移,执行命令python manage.py migrate。
当前不管哪个用户登录,都能看到所有主题,现在我们让用户只能看到自己的主题。
修改learning_logs/views.py
#--snip-- @login_required def topics(request): topics = Topic.objects.filter(owner=request.user).order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context) #--snip-- 用户登录后,request对象会有一个user属性,其存储了有关该用户的所有信息。`filter()`用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。在这里我们用这个函数从数据库中只获取owner属性为当前用户的Topic对象。
在此之前,已登录的用户可输入URLhttp://127.0.0.1:8000/topics/1/来访问显示相应主题的页面。
为修复这种问题,,我们在视图函数topic()获取请求的条目前执行检查。
from django.shortcuts import render from django.http import HttpResponseRedirect, Http404 from django.core.urlresolvers import reverse #--snip-- @login_required def topic(request, topic_id): topic = Topic.objects.get(id=topic_id) if topic.owner != request.user: raise Http404 entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context) #--snip--
渲染网页前检查该主题是否属于当前登录的用户,如果不是,则404。
接下来保护页面http://127.0.0.1:8000/edit_entry/1/。修改views.py
#--snip-- @login_required def edit_entry(request, entry_id): entry = Entry.objects.get(id=entry_id) topic = entry.topic if topic.owner != request.user: raise Http404 if request.method != 'POST': #--snip--
当前,添加新主题的页面没有将新主题关联到特定用户。修改views.py
#--snip-- @login_required def new_topic(request): if request.method != 'POST': form = TopicForm() else: form = TopicForm(request.POST) if form.is_valid(): new_topic = form.save(commit=False) new_topic.owner = request.user new_topic.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context) #--snip--
首先调用form.save(commit=False)是为了先修改新主题,暂时不提交到数据库中。接下来将新主题的 owner属性设置为当前用户后,最后在保存提交到数据库中。
现在这个项目允许任何人注册,而且每个用户可以添加任意数量的新主题。每个用户都只能访问自己的数据,无论是查看数据、输入新数据还是修改就数据都是如此。
设置项目“学习笔记”的样式
bootstrap是一组用于为Web应用程序设置样式的工具。
首先在活动的虚拟环境中执行pip install django-bootstrap3。安装完成之后,我们需要在settings.py的INSTALLED_APPS添加如下代码,在项目中包含应用程序django-bootstrap3
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'bootstrap3', 'learning_logs', 'users', ]
接着让django-bootstrap3包含jQuery,这是一个JavaScript库,让你能够使用bootstrap模板提供的交互元素。在settings.py的末尾添加如下代码。
BOOTSTRAP3 = { 'include_jquery': True, }
这些代码让你无需手工下载jQuery并将其放到正确的地方。
bootstrap基本上就是一个大型的样式设置工具集,它还提供了大量的模板。如果要查看bootstrap提供的模板,可以访问http://getbootstrap.com/,单击Getting Started,再向下滚动到Examples部分,并找到Navbars in action。我们将使用模板Static top navbar,他提供了简单的顶部导航条、页面标题和同于放置页面内容的容器。
这部分我们来修改base.html,先把他分成几部分进行修改。
1.定义HTML头部
第一个修改时在这个文件中定义HTML头部,使得显示“学习笔记”的每个页面时,浏览器标题栏都显示这个网站的名称。删除 base.html 的全部代码,并输入下面的代码:
{% load bootstrap3 %} <!DOCTYPE html> <html lang="en"> <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>Learning Log</title> {% bootstrap_css %} {% bootstrap_javascript %} </head>
首先加载django-bootstrap3中的模板标签集。接着将这个文件声明为使用英语编写的HTML文档。head部分不包含任何内容,只是将正确显示页面所需的内容告诉浏览器。
在title标签下面,我们使用了django-bootstrap3的一个自定义模板标签,它让django包含所有的boostsrap样式文件。接下来的标签启用你可能在页面中使用的所有交互式行为,如何折叠的导航栏。
2.定义导航栏
定义页面顶部的导航栏
<!--snip--> </head> <body> <!-- Static navbar --> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> </button> <a class="navbar-brand" href="{% url 'learning_logs:index' %}"> Learning Log</a> </div> <!--A处--> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a href="{% url 'learning_logs:topics' %}">Topics</a></li> </ul> <!--B处--> <ul class="nav navbar-nav navbar-right"> {% if user.is_authenticated %} <li><a>Hello, {{ user.username }}.</a></li> <li><a href="{% url 'users:logout' %}">log out</a></li> {% else %} <li><a href="{% url 'users:register' %}">register</a></li> <li><a href="{% url 'users:login' %}">log in</a></li> {% endif %} </ul> </div><!--/.nav-collapse --> </div> </nav>
首先是<nar>元素,其表示页面的导航链接部分。对于这个元素内的所有内容,都将根据选择器(selector)navbar、navbar-default和navbar-static-top定义的boostsrap样式规则来设置样式。选择器决定了特定样式规则将应用于页面上的哪些元素。
<button>定义了一个按钮,在浏览器窗口变窄时、无法水平显示完整的导航栏时显示出来。如果单击他将会出现下拉列表,其中包含所有的导航元素。当用户缩小窗口时,"collapse"会使导航栏折叠起来。接着我们在导航栏的最左边显示项目名,并设置了到主页的超链接。
在A处,我们定义了导航的链接。导航栏其实是一个以<ul>开头的列表,其中每个链接都是一个列表项<li>。
在B处,我们添加了第二个导航链接列表,这里使用了选择器为navbar-right来设置一组链接的样式,使其出现在导航栏的右边——登录、注册。在这里,已登录就显示问候语和注销;未登录就显示注册和登录。
3.定义页面的主要部分
<!--snip--> </nav> <div class="container"> <div class="page-header"> {% block header %}{% endblock header %} </div> <div> {% block content %}{% endblock content %} </div> </div> <!-- /container --> </body> </html>
下面使用新定义的header块和一个名为jumbotron的Boostsrap元素修改主页。jumbotron元素是一个大框,他通常用于在主页中呈现项目的简要描述。修改index.html
{% extends "learning_logs/base.html" %} {% block header %} <div class='jumbotron'> <h1>Track your learning.</h1> </div> {% endblock header %} {% block content %} <h2> <a href="{% url 'users:register' %}">Register an account</a> to make your own Learning Log, and list the topics you're learning about. </h2> <h2> Whenever you learn something new about a topic, make an entry summarizing what you're learned. </h2> {% endblock content %}
这里我们改进一下登录的表单,修改login.html
{% extends "learning_logs/base.html" %} {% load bootstrap3 %} {% block header %} <h2>Log in to your accout.</h2> {% endblock header %} {% block content %} <form method="post" action="{% url 'users:login' %}" class="form"> {% csrf_token %} {% bootstrap_form form %} {% buttons %} <button name="submit" class="btn btn-primary">log in</button> {% endbuttons %} <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> </form> {% endblock content %}
注意我们从这个模板删除了{% if form.errors %}代码块,因为django-bootstrap3会自动管理表单错误。{% bootstrap_form %}模板标签用来显示表单,其将bootstrap样式规则应用于各个表单元素。接下来的是将bootstrap应用于按钮。
修改new_topic.html
{% extends "learning_logs/base.html" %} {% load bootstrap3 %} {% block header %} <h2>Add a new topic:</h2> {% endblock header %} {% block content %} <form action="{% url 'learning_logs:new_topic' %}" method='post' class="form"> {% csrf_token %} {% bootstrap_form form %} {% button %} <button name="submit" class="btn btn-primary">add topic</button> {% endbutton %} </form> {% endblock content %}
修改topics.html
{% extends "learning_logs/base.html" %} {% block header %} <h1>Topics</h1> {% endblock header %} {% block content %} <ul> {% for topic in topics %} <li> <h3> <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></li> </h3 </li> {% empty %} <li>No topics have been added yet.</li> {% endfor %} </ul> <h3><a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a></h3> {% endblock content %}
我们将使用bootstrap面板(panel)来突出每个条目。面板是一个带预定义样式的div,适合用于显示主题的条目。修改topic.html
{% extends 'learning_logs/base.html' %} {% block header %} <h2>{{ topic }}</h2> {% endblock header %} {% block content %} <p> <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a> </p> {% for entry in entries %} <div class="panel panel-default"> <div class="panel-heading"> <h3> {{ entry.date_added|date:'M d, Y H:i' }} <small> <a href="{% url 'learning_logs:edit_entry' entry.id %}"> edit entry</a> </small> </h3> </div> <div class="panel-body"> {{ entry.text|linebreaks }} </div> </div> <!-- panel --> {% empty %} There are no entries for this topic yet. {% endfor %} {% endblock content %}
其他页面的话可以试着参考以上的学习笔记进行修改。或者等我后续补充哈,点关注会有更新通知哦~
接下来我们将使用Heroku(基于Web的平台)管理Web应用程序的部署。
访问https://signup.heroku.com注册一个帐号。
安装Heroku Toolbelt,对部署到Heroku服务器的项目进行管理。访问https://toolbelt.heroku.com/进行安装。
在虚拟环境中使用pip安装dj-database-url、dj-static、gunicorn,帮助在服务器上支持django项目提供的服务。
其中dj-database-url帮助django与Heroku使用的数据库进行通信,dj-static帮助django正确的管理静态文件,而gunicorn是一个服务器软件,能够在在线环境中支持应用程序提供的服务。
Heroku需要知道项目依赖那些包,因此我们使用pip来生成一个文件,其中列出依赖包。在虚拟环境中执行pip freeze > requirements.txt.
#requirements.txt dj-database-url==0.5.0 dj-static==0.0.6 Django==1.11.7 django-bootstrap3==9.1.0 gunicorn==19.7.1 pytz==2018.3 static3==0.7.0
我们部署时,Heroku将会安装以上列出的所有包。接着我们在包列表中添加 psycopg2,它帮助Heroku管理活动数据库。
#requirements.txt dj-database-url==0.5.0 dj-static==0.0.6 Django==1.11.7 django-bootstrap3==9.1.0 gunicorn==19.7.1 pytz==2018.3 static3==0.7.0 psycopg2>=2.6.1
如果没有指定python的版本,heroku将使用当前默认的版本。在虚拟环境中执行python --version.
接着在manage.py所在的文件夹 中仙剑一个名为runtime.txt:
#runtime.txt python-3.6.4
记住小写和减号!
在settings.py末尾添加
#settings.py #--snip-- # Heroku设置 if os.getcwd() == '/app': import dj_database_url DATABASES = { 'default': dj_database_url.config(default='postgres://localhost') } # 让request.is_secure()承认X-Forwarded-Proto头 SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # 支持所有的主机头(host header) ALLOWED_HOSTS = ['*'] # 静态资产配置 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) STATIC_ROOT = 'staticfiles' STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), )
首先我们使用了函数getcwd(),用来获取当前的工作目录。在Heroku部署中,这个目录总是/app。在本地部署中,通常是项目文件夹的名称。
这个if测试确保仅当项目被部署到Heroku时,才运行这个代码块。这种结构让我们能够将同一个设置文件用于本地开发环境和在线服务器。
接着我们导入了dj_database_url,用于在Heroku配置服务器。Heroku使用PostgreSQL作为数据库。这些设置对项目进行配置,使其在Heroku上使用PostgreSQL。其他设置思维作用分别是:支持HTTPS请求;让djang能够使用Heroku的URL来提供服务;设置项目,使其能够在Heroku上正确提供静态文件。
在manage.py的文件中创建Procfile(无后缀)
web: gunicorn learning_log.wsgi --log-file -
这行代码让Heroku将gunicorn用作服务器,并使用learning_log/wsgi.py里的设置来启动应用程序。标志log-file告诉Heroku应将那些类型的事件写入日志。
修改wsgi.py
import os from django.core.wsgi import get_wsgi_application from dj_static import Cling os.environ.setdefault("DJANGO_SETTINGS_MODULE", "learning_log.settings") application = Cling(get_wsgi_application())
我们导入了帮助正确地提供静态文件的Cling,并使用他来启动应用程序。
在目录
接着在manage.py的同一目录下创建文件夹static,并在static文件夹中新建一个placeholder.txt当作占位文件,因为项目被推送到Heroku时不会包含空文件夹。
This file ensures that learning_log/static/ will be added to the project. Django will collect static files and place them in learning_log/static/.
1.安装Git
如果你安装了Heroku Toolbelt,则里面已经包含Git了。
2.配置Git
执行
3.忽略文件
记住不要上传数据库文件!!!在manage.py所在的目录创建一个名为.gitignore的文件。
venv/ __pycache__/ *.sqlite3
4.提交项目
命令git init表示在“学习笔记”所在的目录中初始化一个空仓库。命令git add .表示将未被忽略的文件都添加到这个仓库。命令git status输出表明当前位于分支master中,而工作目录是干净的。
在虚拟环境中执行
接着执行命令heroku ps
接着执行命令heroku open
为建立在线数据库,我们需要再次执行命令migrate。要对Heroku项目执行django和python命令,可使用heroku run命令
现在可以将这个应用程序的URL分享给你的小伙伴了~
在这部分,我们将创建超级用户来改进部署。为了项目更安全,将DEBUG设置为False,防止他人看到错误信息。
1.在Heroku上创建超级用户
修改settings.py
# Heroku设置 if os.getcwd() == '/app': import dj_database_url DATABASES = { 'default': dj_database_url.config(default='postgres://localhost') } # 让request.is_secure()承认X-Forwarded-Proto头 SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # 只允许Heroku托管这个项目 ALLOWED_HOSTS = ['learning-log.herokuapp.com'] DEBUG = False # 静态资产配置 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) STATIC_ROOT = 'staticfiles' STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), )
现在将settings.py的修改提交到Git仓库,在将修改推送到Heroku。
Heroku发现仓库发生了变化,因此重建项目,但不会重建数据库,所以不用在迁移数据库。
当用户遇到404或者500的错误时,会返回错误页面。404表示代码没错,但请求的对象不存在。500表示代码有问题。现在我们将自定义错误页面。
1.创建自定义模板
在learning_log/learning_log中新建一个 templates文件夹,创建404.html、505.html
#404.html {% extends "learning_logs/base.html" %} {% block header %} <h2>The item you requested is not available. (404)</h2> {% endblock header %}
#500.html {% extends "learning_logs/base.html" %} {% block header %} <h2>The item you requested is not available. (404)</h2> {% endblock header %}
这些新文件要求对settings.py做修改。
#--snip-- TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'learning_log/templates')], 'APP_DIRS': True, #--snip-- }, ] #--snip--
2.在本地查看错误页面
对本地的settings.py进行修改
#--snip-- # 安全警告,不要在在线环境中启用调试 DEBUG = False ALLOWED_HOSTS = ['localhost'] #--snip--
当DUBUG
被设置False时,必须在ALLOWED_HOSTS中指定一个主机。
查看错误页面之后,将DUBUG
设置True,方便进一步开发。(在settings.py中用于Heroku部署的部分中,确保DEBUG = False)。
3.将修改推送到Heroku
4.使用方法get_object_or_404()
现在当用户手动请求不存在的主题或条目,将导致500错误。这种情形应该视为404错误合适一点。因此我们使用函数get_object_or_404()。修改views.py
#--snip-- from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect, Http404 #--snip-- @login_required def topic(request, topic_id): """显示单个主题及其所有的条目""" topic = get_object_or_404(Topic, id=topic_id) # 确定主题属于当前用户 #--snip--
修改之后记得提交。
首先,对本地项目做必要的修改,如果在修改过程中创建了新文件,使用命令git add .将他们加入到Git仓库中。如果有修改要求迁移数据库,也需要执行这个命令,因为每个迁移都将生成新的迁移文件。
然后,使用命令git commit -am "commit message"将修改提交到仓库,在使用命令git push
heroku master将修改推送到Heroku。如果你在本地迁移了数据库,也需要迁移在线数据库。为此,你可以执行一次性命令heroku run python manage.py migrate,也可以使用heroku run bash打开一个远程终端,在其中执行python manage.py migrate。然后访问在线项目,确认修改生效。
Django根据settings.py中设置SECRET_KEY的值来实现大量的安全协议。
1.在官网上删除。
2.执行heroku apps:destroy --app appname
---
修复笔记entry.text文本过长导致的溢出。现已自动换行。
打开base.html并添加
<!--snip--> <title>Learning Log</title> {% bootstrap_css %} {% bootstrap_javascript %} <style> div.lbreak{word-wrap: break-word;} </style> </head> <!--snip-->
打开topic.html并添加
<!--snip--> <div class="panel-body"> <div class='lbreak'>{{ entry.text|linebreaks }}</div> </div> </div> <!-- panel --> <!--snip-->
以上就是本次给大家分享的全部知识点内容总结,大家还可以在下方相关文章里找到spring+springmvc+mybatis整合注、 儿童python编程入门书籍推、 vue项目中使用md5加密以及、 等python文章进一步学习,感谢大家的阅读和支持。
展开 +
收起 -
Copyright 2018-2019 xz577.com 码农之家