爲了接收用戶的投票選擇,咱們須要在前段頁面顯示一個投票界面,讓咱們重寫以前的polls/detail.html文件,代碼以下:html
<h1>{{ question.question_text }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> {% endfor %} <input type="submit" value="Vote" /> </form>
簡要說明:前端
如今,讓咱們建立一個處理提交過來的數據視圖,前面咱們已經寫了一個「佔坑」的vote視圖的url(polls/urls.py)python
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
以及佔坑的vote視圖函數(polls.views.py),咱們把坑填起來:git
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse from .models import Choice, Question # ... def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # 發生choice未找到異常時,從新返回表單頁面,並給出提示信息 return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() # 成功處理數據後,自動跳轉到結果頁面,防止用戶連續屢次提交。 return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
有些新的東西,咱們要解釋一下:ajax
當有人對某個問題投票後,vote()視圖重定向到了問卷的結果顯示頁面,下面咱們來寫這個處理結果頁面的視圖(polls/views.py):正則表達式
from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question})
一樣,還須要些個模板polls/templates/polls/results.html (路由,視圖,模板,模型都是這個套路)sql
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
如今你能夠到瀏覽器中訪問/polls/1/ 了 ,投票了。你會看到一個結果頁面,每投一次,它的內容就會更新一次。若是你提交的時候沒有選擇項目,則會獲得一個錯誤提示。shell
若是你在前面漏掉了一部分操做沒作,好比沒有建立choice選項對象,那麼能夠按下面的操做,補充一下:數據庫
D:\Django\mysite>python manage.py shell Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:57:15) [MSC v.1915 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from polls.models import Question >>> q = Question.objects.get(pk=1) >>> q.choice_set.create(choice_text = 'Not much',votes=0) <Choice: Not much> >>> q.choice_set.create(choice_text = 'The sky',votes=0) <Choice: The sky> >>> q.choice_set.create(choice_text = 'Just hacking again',votes=0) <Choice: Just hacking again>
爲了方便,我將當前狀態的各主要內容一併貼出,依次能夠對照參考!django
1 ——完整的mysite/urls.py文件以下:
from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^polls/', include('polls.urls')), ]
2 ——完整的mysite/settings.py文件以下:
import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '85vvuta(p05ow!4pz2b0qbduu0%pq6x5q66-ei*pg+-lbdr#m^' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'polls', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'mysite.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'mysite.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = True
3 ——完整的polls/views.py 應該以下所示:
from django.shortcuts import reverse from django.shortcuts import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.shortcuts import HttpResponse from django.shortcuts import render from .models import Choice from .models import Question from django.template import loader # Create your views here. def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] template = loader.get_template('polls/index.html') context = { 'latest_question_list': latest_question_list, } return HttpResponse(template.render(context, request)) def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question}) def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
4 ——完整的polls/urls.py 應該以下所示:
from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ # ex: /polls/ url(r'^$', views.index, name='index'), # ex: /polls/5/ url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'), # ex: /polls/5/results/ url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'), # ex: /polls/5/vote/ url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
5 ——完整的polls.model.py文件以下:
from django.db import models import datetime from django.utils import timezone # Create your models here. class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) def __str__(self): return self.question_text class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) def __str__(self): return self.choice_text
6 ——完整的polls/admin.py 文件以下:
from django.contrib import admin # Register your models here. from .models import Question admin.site.register(Question)
7 ——完整的templates/polls/index.html 文件以下:
{% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}
8 ——完整的templates/polls/detail.html 文件以下:
<h1>{{ question.question_text }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> {% endfor %} <input type="submit" value="Vote" /> </form>
9 ——完整的templates/polls/results.html 文件以下:
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
vote()視圖沒有對應的HTML 模板,它直接跳轉到results視圖去了。
運行服務器,測試各功能以下:
這是問卷列表頁面:
這是「What's up」 問卷選項頁面:
這是選擇結果的頁面:
這是沒有選擇選項時,提示錯誤信息的頁面:
上面的detail ,index和results 視圖的代碼很是類似,有點冗餘。他們都具備相似的業務邏輯,實現相似的功能:經過從URL傳遞過來的參數去數據庫查詢數據,加載一個模板,利用剛纔的數據渲染模板,返回這個模板,因爲這個過程是如此的常見,Django很善解人意的幫你想辦法偷懶,因而他提供了一種快捷方式,名爲「類視圖」。
如今,讓咱們來試試看將原來的代碼改成使用類視圖的方式,整個過程分爲三步走:
一般在寫一個Django的APP時候,咱們一開始就要決定使用類視圖仍是不用,而不是等到代碼寫到一半了才重構你的代碼成類視圖。可是前面爲了理解視圖,因此故意走了一條比較曲折的路。
URL配置(URLConf)就像是Django所支撐網站的目錄。它的本質是URL與要爲該URL調用的視圖函數之間的映射表;你就是以這種方式告訴Django,對於客戶端發來的某個URL調用哪一段邏輯代碼對應執行。
打開polls/urls.py文件,將其修改爲下面的樣子:
from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
請注意:在上面的第2,3條目中將原來的question_id 改爲了pk。若要從URL中捕獲一個值,只須要在它周圍放置一對圓括號。不須要添加一個前導的反斜槓,由於每一個URL都有。例如,應該是^articles而不是^/articles。每一個正則表達式前面的 r是可選的可是建議加上,它告訴Python這個字符串是「原始的」——字符串中任何字符都不該該轉義。示例:
先看一個URL路由配置:
from django.urls import path,re_path from app01 import views urlpatterns = [ re_path(r'^articles/2003/$', views.special_case_2003), re_path(r'^articles/([0-9]{4})/$', views.year_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
解析:
一些請求的例子: /articles/2005/03/ 請求將匹配列表中的第三個模式。Django 將調用函數views.month_archive(request, '2005', '03')。 /articles/2005/3/ 不匹配任何URL 模式,由於列表中的第三個模式要求月份應該是兩個數字。 /articles/2003/ 將匹配列表中的第一個模式不是第二個,由於模式按順序匹配,第一個會首先測試是否匹配。請像這樣自由插入一些特殊的狀況來探測匹配的次序。 /articles/2003 不匹配任何一個模式,由於每一個模式要求URL 以一個反斜線結尾。 /articles/2003/03/03/ 將匹配最後一個模式。Django 將調用函數views.article_detail(request, '2003', '03', '03')。
接下來,打開polls/views.py文件,刪除index,detail和 results 視圖,替換成Django的類視圖,以下所示:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect from django.urls import reverse from django.views import generic from .models import Choice, Question class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): """返回最近發佈的5個問卷.""" return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name ='polls/results.html' def vote(request, question_id): ... # 這個視圖未改變!!!
在這裏,咱們使用了兩種類視圖ListView和DetailView(他們是做爲父類被繼承的)。這二者分別表明「顯示一個對象的列表」 和「顯示特定類型對象的詳細頁面」 的抽象概念。
默認狀況下,DetailView 類視圖使用一個稱做 <app name>/<model name>_detail.html 的模板。
在本例中,實際使用的是polls/detail.html。 template_name屬性就是用來指定這個模板名的,用於代替自動生成的默認模板名。(必定要仔細觀察上面的代碼,對號入座,注意細節)一樣的,在results列表視圖中,指定template_name 爲 'polls/results.html’ ,這樣就確保了雖然result視圖和detail 視圖一樣繼承了DetailView類,使用了一樣的model: Question,但他們依然會顯示不一樣的頁面。
相似的,ListView類視圖使用一個默認模板稱爲 <app name>/<model name>_list.html 。咱們也使用template_name 這個變量來告訴ListView使用咱們已經存在的'polls/index.html' 模板,而不是使用他本身默認的那個。
在前面部分,咱們給模板提供了一個包含question 和 lastest_question_list 的上下文變量,而對於DetailView,question變量會被自動提供,由於咱們使用Django的模型(Question),Django會智能的選擇合適的上下文變量。然而,對於ListView ,自動生成的上下文變量是 question_list 。爲了覆蓋它,咱們提供了 context_object_name 屬性,指定說咱們但願使用 lastest_question_list 而不是 question_list。
如今能夠運行開發服務器,而後試試基於類視圖的應用程序了,類視圖是Django比較高級的一種用法。
簡單的路由配置示例:
from django.urls import path,re_path from app01 import views urlpatterns = [ re_path(r'^articles/2003/$', views.special_case_2003), re_path(r'^articles/([0-9]{4})/$', views.year_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
上面的示例使用簡單的,沒有命名的正則表達式組(經過圓括號)來捕獲URL中的值並以位置參數傳遞給視圖。在更高級的用法中,可使用命名的正則表達式組來捕獲URL中的值並以關鍵字參數傳遞給視圖。
在Python正則表達式中,命名正則表達式組的語法是(?Ppattern),其中name是組的名稱,pattern是要匹配的模式。
下面是以上URLconf使用命名組的重寫:
from django.urls import path,re_path from app01 import views urlpatterns = [ re_path(r'^articles/2003/$', views.special_case_2003), re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail), ]
這個實現與前面的示例徹底相同,只有一個細微的差異:捕獲的值做爲關鍵字參數而不是位置參數傳遞給視圖函數。例如:
/articles/2005/03/ 請求將調用views.month_archive(request, year='2005', month='03')函數, 而不是views.month_archive(request, '2005', '03')。 /articles/2003/03/03/ 請求將調用函數 views.article_detail(request, year='2003', month='03', day='03')。
因此在實際應用中,這意味着你的URLconf會更加清晰且不容易產生順序問題的錯誤——你能夠在你的視圖函數中定義從新安排參數的順序。固然,這些好處是以簡介爲代價。
At any point, your urlpatterns can 「include」 other URLconf modules. This essentially 「roots」 a set of URLs below other ones. ''' from django.urls import path,re_path,include from app01 import views urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^blog/', include('blog.urls')), ]
在使用Django項目時,一個常見的需求是得到URL的最終形式,以用於嵌入到生成的內容中(視圖中和顯示給用戶的URL等)或者用於處理服務器端的導航(重定向等)。人們強烈但願不要硬編碼這些URL(費力,不可擴展且容易出現錯誤)或者設計一種與URLconf絕不相關的專門的URL生成機制,由於這樣容易致使必定程度上產生過時的URL。
在須要URL的地方,對於不一樣層級,DJango提供不一樣的工具用於URL反查:
urls.py
from django.conf.urls import url from . import views urlpatterns = [ re_path(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), ]
在模板中:
<a href="{% url 'news-year-archive' 2019 %}">2019 Archive</a> <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
在python中:
from django.urls import reverse from django.http import HttpResponseRedirect def redirect_to_year(request): year = 2019 return HttpResponseRedirect(reverse('news-year-archive', args=(year,))) # 同redirect("/path/")
當命名你的URL模式時,請確保使用的名稱不會與其餘應用中名稱衝突。若是你的URL模式叫作comment,而另一個應用中也有一個一樣的名稱,當你在模板中使用這個名稱的時候不能保證將插入哪一個URL。在URL名稱中加上一個前綴,好比應用的名稱,將減小衝突的可能。咱們建議使用myapp-comment ,而不是comment。
命名空間(英語:Namespace)是表示標識符的可見範圍。一個標識符可在多個命名空間中定義,它在不一樣命名空間中的含義是互不相干的。這樣,在一個新的命名空間中可定義任何標識符,他們不會與任何已有的標識符發生衝突,由於已有的定義都處於其餘命名空間中。
因爲name沒有做用域,Django在反解URL時,會在項目全局順序搜索,當查找到第一個name指定URL時,當即返回咱們再開發項目時,會常用name屬性反解出URL,當不當心在不一樣的app的urls中定義相同的name時,可能會致使URL反解錯誤,爲了不這種事情發生,引入了命名空間。
project的urls.py
urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^app01/', include("app01.urls",namespace="app01")), re_path(r'^app02/', include("app02.urls",namespace="app02")), ]
app01.urls:
urlpatterns = [ re_path(r'^index/', index,name="index"), ]
app02.urls:
urlpatterns = [ re_path(r'^index/', index,name="index"), ]
app01.views:
from django.core.urlresolvers import reverse def index(request): return HttpResponse(reverse("app01:index"))
app02.views:
from django.core.urlresolvers import reverse def index(request): return HttpResponse(reverse("app02:index"))
思考狀況以下:
urlpatterns = [ re_path('articles/(?P<year>[0-9]{4})/', year_archive), re_path('article/(?P<article_id>[a-zA-Z0-9]+)/detail/', detail_view), re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/edit/', edit_view), re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/delete/', delete_view), ]
考慮下這樣的兩個問題:
1,函數 year_archive 中 year 參數是字符串類型的,所以須要先轉化爲整數類型的變量值,固然 year= int(year) 不會有諸多如 TypeError 或者 ValyeError的異常。那麼有沒有一種方法,在url中,使得這一轉化步驟能夠由Django自動完成呢?
2,三個路由中的 article_id 都是一樣的正則表達式,可是你須要些三遍,當以後 article_id 規則改變後,須要同時修改三處代碼,那麼有沒有一種方法,只須要修改一處便可。
在DJango2.0中,可使用path解決以上兩個問題。
from django.urls import path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_case_2003), path('articles/<int:year>/<int:mouth>/', views.month_archive), path('articles/<int:year>/<int:month>/<slug>/', views.article_detial), ]
基本規則:
下面是根據2.0 官方文檔 整理的示例分析表:
對於一些複雜或者複用的須要,能夠定義本身的轉化器。轉化器是一個類或者接口,它的要求有三點:
regex類屬性,字符串類型
to_python(self, value) 方法,value是由類屬性regex全部匹配到的字符串,返回具體的Python變量值,以供Django傳遞到奧對應的視圖函數中。
to_url(selfm value)方法,和to_python 相反,value是一個具體的python變量值,返回器字符串,一般用於url反向引用。
例子:
class FourDigitYearConverter: regex = '[0-9]{4}' def to_python(self, value): return int(value) def to_url(self, value): return '%04d' % value
使用register_converter將其註冊到URL配置中:
from django.urls import register_converter, path from . import converters, views register_converter(converters.FourDigitYearConverter, 'yyyy') urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<yyyy:year>/', views.year_archive), ... ]
測試是一種例行的,不可缺失的工做,用於檢測你的程序是否符合預期。
測試能夠劃分爲不一樣的級別。一些測試可能專一於小細節(好比某一個模型的方法是否會返回預期的值?), 一些測試則專一於檢查軟件的總體運行是否正常(用戶在對網站進行了一系列的輸入後,是否返回了指望的結果?)。
測試能夠分爲手動測試和自動測試。手動測試很常見,有時候print一個變量內容,均可以看作是測試的一部分。手動測試每每很零碎、不成體系、不夠完整、耗時費力、效率低下,測試結果也不必定準確。
自動化測試則是系統地較爲完整地對程序進行測試,效率高,準確性高,而且大部分共同的測試工做會由系統來幫你完成。一旦你建立了一組自動化測試程序,當你修改了你的應用,你就能夠用這組測試程序來檢查你的代碼是否仍然同預期的那樣運行,而無需執行耗時的手動測試。
Django是一個全面,完善,嚴謹的Web框架,固然不會缺失測試功能。
在前面咱們的投票應用中有一個小BUG須要修改:在Question.was_published_recently() 方法的返回值中,當Question 在最近的一天發佈的時候返回True(這是正確的),然而當Question在將來的日期內發佈的時候也返回True(這是錯誤的)。
咱們能夠在admin後臺建立一個發佈日期在將來的Question,而後在shell裏面驗證這個bug:
>>> import datetime >>> from django.utils import timezone >>> from polls.models import Question >>> # 建立一個發佈日期在30天后的問卷 >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) >>> # 測試一下返回值 >>> future_question.was_published_recently() True
問題的核心在於咱們容許建立在將來時間才發佈的問卷,因爲「將來」不等於「最近」,所以這顯然是個bug。
剛纔咱們是在shell裏面測試了這個bug,那麼如何經過自動化測試來發現這個bug呢?
一般咱們會把測試代碼放在應用的tests.py文件中,測試系統將自動的從任何名字以test開頭的文件中查找測試程序,每一個APP在建立的時候,都會自動建立一個test.py 文件,就像view.py 等文件同樣。
將下面的代碼輸入投票應用的polls/tests.py 文件中:
import datetime from django.utils import timezone from django.test import TestCase from .models import Question class QuestionMethodTests(TestCase): def test_was_published_recently_with_future_question(self): """ 在未來發布的問卷應該返回False """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False)
咱們再這裏建立了一個django.test.TestCase 的子類,它具備一個方法,該方法建立一個pub_date在將來的Question實例,最後咱們檢查was_published_recently() 的輸出,他應該是FALSE。
在終端中,運行下面的命令:
python manage.py test polls
你將看到結果以下:
Creating test database for alias 'default'... System check identified no issues (0 silenced). F ====================================================================== FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTest2) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\Django\mysite\polls\tests.py", line 15, in test_was_published_recently_with_future_question self.assertIs(future_question.was_published_recently(),False) AssertionError: True is not False ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1) Destroying test database for alias 'default'...
這其中都發生了什麼?
最後,測試程序會通知咱們那個測試失敗了,錯誤出如今哪一行。
整個測試用例基本上和Python內置的unittest很是類似。
咱們已經知道問題所在,如今能夠去修復bug了,修復源代碼,具體以下:
# polls/models.py def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now
再次運行測試程序:
Creating test database for alias 'default'... System check identified no issues (0 silenced). . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK Destroying test database for alias 'default'...
能夠看到沒有bug了。
事實上,前面的測試用例還不夠完整,爲了使was_published_recently() 方法更加可靠,咱們在上面的測試類中再額外添加兩個其餘的方法,來更加全面的進行測試。
# polls/tests.py def test_was_published_recently_with_old_question(self): """ 只要是超過1天的問卷,返回False """ time = timezone.now() - datetime.timedelta(days=1, seconds=1) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): """ 最近一天內的問卷,返回True """ time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True)
如今咱們有三個測試來保證不管發佈時間在過去,如今仍是將來。Question.was_published_recently() 都將返回正確的結果。
在Django模板中遍歷複雜數據結構的關鍵是句點字符。
其組成: HTML代碼 + 邏輯控制代碼
語法格式: {{var_name}}
舉個例子:
def index(request): import datetime s="hello" l=[111,222,333] # 列表 dic={"name":"james","age":18} # 字典 date = datetime.date(1999, 5, 2) # 日期對象 class Person(object): def __init__(self,name): self.name=name person_james=Person("james") # 自定義類對象 person_durant=Person("durant") person_curry=Person("curry") person_list=[person_james,person_durant,person_curry] return render(request,"index.html",{"l":l,"dic":dic,"date":date,"person_list":person_list})
template:
<h4>{{s}}</h4> <h4>列表:{{ l.0 }}</h4> <h4>列表:{{ l.2 }}</h4> <h4>字典:{{ dic.name }}</h4> <h4>日期:{{ date.year }}</h4> <h4>類對象列表:{{ person_list.0.name }}</h4>
注意:句點符也能夠用來引用對象的方法(無參數方法)
<h4>字典:{{ dic.name.upper }}</h4>
Template 和Context對象
>>> python manange.py shell (進入該django項目的環境) >>> from django.template import Context, Template >>> t = Template('My name is {{ name }}.') >>> c = Context({'name': 'Stephane'}) >>> t.render(c) 'My name is Stephane.' # 同一模板,多個上下文,一旦有了模板對象,你就能夠經過它渲染多個context,不管什麼時候咱們均可以 # 像這樣使用同一模板源渲染多個context,只進行 一次模板建立而後屢次調用render()方法渲染會 # 更爲高效: # Low for name in ('John', 'Julie', 'Pat'): t = Template('Hello, {{ name }}') print(t.render(Context({'name': name}))) # Good t = Template('Hello, {{ name }}') for name in ('John', 'Julie', 'Pat'): print(t.render(Context({'name': name})))
Django模板解析很是快捷。大部分的解析工做都是在後臺經過對簡短正則表達式一次性調用來完成。這和基於XML的模板引擎造成鮮明對比,那些引擎承擔了XML解析器的開銷,且每每比Django模板渲染引擎要慢上幾個數量級。
推薦方法
from django.shortcuts import render,HttpResponse from django.template.loader import get_template #記得導入 # Create your views here. import datetime from django.template import Template,Context # def current_time(req): #原始的視圖函數 # now=datetime.datetime.now() # html="<html><body>如今時刻:<h1>%s.</h1></body></html>" %now # return HttpResponse(html) # def current_time(req): #django模板修改的視圖函數 # now=datetime.datetime.now() # t=Template('<html><body>如今時刻是:<h1 style="color:red">{{current_date}}</h1></body></html>') #t=get_template('current_datetime.html') # c=Context({'current_date':now}) # html=t.render(c) # return HttpResponse(html) #另外一種寫法(推薦) def current_time(req): now=datetime.datetime.now() return render(req, 'current_datetime.html', {'current_date':now})
深度變量的查找(萬能的句點號)
在到目前爲止的例子中,咱們經過context傳遞的簡單參數值主要是字符串,然而,模板系統可以很是簡潔的處理更加複雜的數據結構,例如list,dictionary和自定義的對象。
在Django模板中遍歷複雜數據結構的關鍵是句點字符(.)
#最好是用幾個例子來講明一下。 # 首先,句點可用於訪問列表索引,例如: >>> from django.template import Template, Context >>> t = Template('Item 2 is {{ items.2 }}.') >>> c = Context({'items': ['apples', 'bananas', 'carrots']}) >>> t.render(c) 'Item 2 is carrots.' #假設你要向模板傳遞一個 Python 字典。 要經過字典鍵訪問該字典的值,可以使用一個句點: >>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) 'Sally is 43 years old.' #一樣,也能夠經過句點來訪問對象的屬性。 比方說, Python 的 datetime.date 對象有 #year 、 month 和 day 幾個屬性,你一樣能夠在模板中使用句點來訪問這些屬性: >>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year >>> d.month >>> d.day >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.') >>> c = Context({'date': d}) >>> t.render(c) 'The month is 5 and the year is 1993.' # 這個例子使用了一個自定義的類,演示了經過實例變量加一點(dots)來訪問它的屬性,這個方法適 # 用於任意的對象。 >>> from django.template import Template, Context >>> class Person(object): ... def __init__(self, first_name, last_name): ... self.first_name, self.last_name = first_name, last_name >>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.') >>> c = Context({'person': Person('John', 'Smith')}) >>> t.render(c) 'Hello, John Smith.' # 點語法也能夠用來引用對象的方法。 例如,每一個 Python 字符串都有 upper() 和 isdigit() # 方法,你在模板中可使用一樣的句點語法來調用它們: >>> from django.template import Template, Context >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') >>> t.render(Context({'var': 'hello'})) 'hello -- HELLO -- False' >>> t.render(Context({'var': '123'})) '123 -- 123 -- True' # 注意這裏調用方法時並* 沒有* 使用圓括號 並且也沒法給該方法傳遞參數;你只能調用不需參數的 # 方法。
變量的過濾器(filter)的使用
語法格式: {{ obj|filter:param}}
若是一個變量是false或者爲空,使用給定的默認值。不然,使用變量的值,例如:
{{ value|default:"nothing" }}
返回值的長度,它對字符串和列表都有起做用。例如:
{{ value|length }}
若是value是 ['a', 'b', 'c', 'd'],那麼輸出是4。
將值格式化爲一個「人類可讀的」文件尺寸(例如:‘13kb’, ‘4.1mb’, ‘102bytes'’)
{{ value|filesizeformat }}
若是 value
是 123456789,輸出將會是 117.7 MB
。
若是 value= datetime.datetime.now()
{{ value|date:"Y-m-d" }}
若是 value ='hello world:'
{{ value|slice:"2:-1" }}
若是字符串字符多於指定的字符數量,那麼會被截斷。階段的字符串將能夠翻譯的省略號序列(「...」)結尾。
參數:要截斷的字符數
例如:
{{ value|truncatechars:9 }}
Django的模板中會對HTML標籤和JS等語法標籤進行自動轉義。緣由顯而易見,這樣是爲了安全。可是有的時候咱們可能不但願這些HTML元素被轉義。好比咱們作一個內容管理系統,後臺添加的文章中是通過修飾的,這些修飾多是經過一個相似於FCKeditor編輯加註了HTML修飾符的文本,若是自動轉義的話顯示的就是保護HTML標籤的源文件。爲了在Django中關閉HTML的自動轉義由兩種方法,若是是一個單獨的變量咱們能夠經過過濾器「|safe」 的方式告訴Django這段代碼是安全的沒必要轉義。好比:
value="<a href="">點擊</a>" {{ value|safe}}
# 1 add : 給變量加上相應的值 # # 2 addslashes : 給變量中的引號前加上斜線 # # 3 capfirst : 首字母大寫 # # 4 cut : 從字符串中移除指定的字符 # # 5 date : 格式化日期字符串 # # 6 default : 若是值是False,就替換成設置的默認值,不然就是用原本的值 # # 7 default_if_none: 若是值是None,就替換成設置的默認值,不然就使用原本的值 #實例: #value1="aBcDe" {{ value1|upper }}<br> #value2=5 {{ value2|add:3 }}<br> #value3='he llo wo r ld' {{ value3|cut:' ' }}<br> #import datetime #value4=datetime.datetime.now() {{ value4|date:'Y-m-d' }}<br> #value5=[] {{ value5|default:'空的' }}<br> #value6='<a href="#">跳轉</a>' {{ value6 }} {% autoescape off %} {{ value6 }} {% endautoescape %} {{ value6|safe }}<br> {{ value6|striptags }} #value7='1234' {{ value7|filesizeformat }}<br> {{ value7|first }}<br> {{ value7|length }}<br> {{ value7|slice:":-1" }}<br> #value8='http://www.baidu.com/?a=1&b=3' {{ value8|urlencode }}<br> value9='hello I am yuan'
標籤看起來像下面的格式。可是標籤比變量更加複雜:一些在輸出中建立文本,一些經過循環或者邏輯來控制流程,一些加載其後的變量將使用到的額外信息到模板中。一些標籤須要開始和結束標籤等等
{% tags %}
{% if %} 的使用
{% if %}標籤計算一個變量值,若是是「true」,即它存在,不爲空而且不是FALSE的boolean值,系統則會顯示{% if %} 和{% endif %} 間全部內容
{% if num >= 100 and 8 %} {% if num > 200 %} <p>num大於200</p> {% else %} <p>num大於100小於200</p> {% endif %} {% elif num < 100%} <p>num小於100</p> {% else %} <p>num等於100</p> {% endif %} {% if %} 標籤接受and,or或者not來測試多個變量值或者否認一個給定的變量 {% if %} 標籤不容許同一標籤裏同時出現and和or,不然邏輯容易產生歧義,例以下面的標籤是不合法的: {% if obj1 and obj2 or obj3 %}
{% for %}的使用
{% for %}標籤運行你按順序遍歷一個序列中的各個元素,每次循環模板系統都會渲染{% for %}和{% endfor %} 之間的全部內容。
<ul> {% for obj in list %} <li>{{ obj.name }}</li> {% endfor %} </ul> #在標籤裏添加reversed來反序循環列表: {% for obj in list reversed %} ... {% endfor %} #{% for %}標籤能夠嵌套: {% for country in countries %} <h1>{{ country.name }}</h1> <ul> {% for city in country.city_list %} <li>{{ city }}</li> {% endfor %} </ul> {% endfor %} #系統不支持中斷循環,系統也不支持continue語句,{% for %}標籤內置了一個forloop模板變量, #這個變量含有一些屬性能夠提供給你一些關於循環的信息 1,forloop.counter表示循環的次數,它從1開始計數,第一次循環設爲1: {% for item in todo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %} 2,forloop.counter0 相似於forloop.counter,但它是從0開始計數,第一次循環設爲0 3,forloop.revcounter 4,forloop.revcounter0 5,forloop.first當第一次循環時值爲True,在特別狀況下頗有用: {% for object in objects %} {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} {{ object }} </li> {% endfor %} # 富有魔力的forloop變量只能在循環中獲得,當模板解析器到達{% endfor %}時forloop就消失了 # 若是你的模板context已經包含一個叫forloop的變量,Django會用{% for %}標籤替代它 # Django會在for標籤的塊中覆蓋你定義的forloop變量的值 # 在其餘非循環的地方,你的forloop變量仍然可用 #{% empty %} {{li }} {% for i in li %} <li>{{ forloop.counter0 }}----{{ i }}</li> {% empty %} <li>this is empty!</li> {% endfor %} # [11, 22, 33, 44, 55] # 0----11 # 1----22 # 2----33 # 3----44 # 4----55
{% csrf_token%} : csrf_token標籤
用於生成csrf_token的標籤,用於防治跨站攻擊驗證。注意若是你在view的index裏用的是render_to_response方法,不會生效。
其實,這裏只會生成一個input標籤,和其餘表單標籤一塊兒提交給後臺。
{% url %}:引用路由配置的地址
<form action="{% url "bieming"%}" > <input type="text"> <input type="submit"value="提交"> {%csrf_token%} </form>
{% with %}:用更簡單的變量名替代複雜的變量名
使用簡單的名字緩存一個複雜的變量,當你須要使用一個「昂貴」的方法(好比訪問數據庫)不少次的時候很是有用
{% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
{% verbatim %} 禁止render
{% verbatim %} {{ hello }} {% endverbatim %}
{% load %} :加載標籤庫
1,在app中建立templatetags模塊(必須的)
2,建立任意.py文件,如my_tag.py
from django import template from django.utils.safestring import mark_safe register = template.Library() #register的名字是固定的,不可改變 @register.filter def filter_multi(v1,v2): return v1 * v2 @register.simple_tag def simple_tag_multi(v1,v2): return v1 * v2 @register.simple_tag def my_input(id,arg): result = "<input type='text' id='%s' class='%s' />" %(id,arg,) return mark_safe(result)
3,在使用自定義simple_tag 和filter的HTML文件中導入以前建立的my_tags.py:
{% load my_tags %}
4,使用simple_tag 和filter (如何調用)
-------------------------------.html {% load xxx %} #首行 # num=12 {{ num|filter_multi:2 }} #24 {{ num|filter_multi:"[22,333,4444]" }} {% simple_tag_multi 2 5 %} 參數不限,但不能放在if for語句中 {% simple_tag_multi num 5 %}
5,在settings的INSTALLED_APPS配置當前APP,否則Django沒法找到自定義的simple_tag
注意:filter能夠用在if等語句後,simpke_tag不能夠
{% if num|filter_multi:30 > 100 %} {{ num|filter_multi:30 }} {% endif %}
include模板標籤
在講解了模板加載機制以後,咱們再介紹一個利用該機制的內建模板標籤: {% include %} 。該標籤容許在(模板中)包含其它的模板的內容。 標籤的參數是所要包含的模板名稱,能夠是一個變量,也能夠是用單/雙引號硬編碼的字符串。 每當在多個模板中出現相同的代碼時,就應該考慮是否要使用 {% include %} 來減小重複。
extend(繼承)模板標籤
到目前爲止,咱們的模板範例都只是些零星的 HTML 片斷,但在實際應用中,你將用 Django 模板系統來建立整個 HTML 頁面。 這就帶來一個常見的 Web 開發問題: 在整個網站中,如何減小共用頁面區域(好比站點導航)所引發的重複和冗餘代碼?
解決該問題的傳統作法是使用 服務器端的 includes ,你能夠在 HTML 頁面中使用該指令將一個網頁嵌入到另外一箇中。 事實上, Django 經過剛纔講述的 {% include %} 支持了這種方法。 可是用 Django 解決此類問題的首選方法是使用更加優雅的策略—— 模板繼承 。
本質上來講,模板繼承就是先構造一個基礎框架模板,然後在其子模板中對它所包含站點公用部分和定義塊進行重載。
讓咱們經過修改 current_datetime.html 文件,爲 current_datetime 建立一個更加完整的模板來體會一下這種作法:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>The current time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>It is now {{ current_date }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
這看起來很棒,但若是咱們要爲 hours_ahead 視圖建立另外一個模板會發生什麼事情呢?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>Future time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
很明顯,咱們剛纔重複了大量的 HTML 代碼。 想象一下,若是有一個更典型的網站,它有導航條、樣式表,可能還有一些 JavaScript 代碼,事情必將以向每一個模板填充各類冗餘的 HTML 而了結。
解決這個問題的服務器端 include 方案是找出兩個模板中的共同部分,將其保存爲不一樣的模板片斷,而後在每一個模板中進行 include。 也許你會把模板頭部的一些代碼保存爲 header.html 文件:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head>
你可能會把底部保存到文件 footer.html :
<hr> <p>Thanks for visiting my site.</p> </body> </html>
對基於 include 的策略,頭部和底部的包含很簡單。 麻煩的是中間部分。 在此範例中,每一個頁面都有一個<h1>My helpful timestamp site</h1> 標題,可是這個標題不能放在 header.html 中,由於每一個頁面的 <title> 是不一樣的。 若是咱們將 <h1> 包含在頭部,咱們就不得不包含 <title> ,但這樣又不容許在每一個頁面對它進行定製。 何去何從呢?
Django 的模板繼承系統解決了這些問題。 你能夠將其視爲服務器端 include 的逆向思惟版本。 你能夠對那些不一樣 的代碼段進行定義,而不是 共同 代碼段。
第一步是定義 基礎模板,該框架以後將由子模板所繼承。 如下是咱們目前所講述範例的基礎模板:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html>
這個叫作 base.html 的模板定義了一個簡單的 HTML 框架文檔,咱們將在本站點的全部頁面中使用。 子模板的做用就是重載、添加或保留那些塊的內容。 (若是你一直按順序學習到這裏,保存這個文件到你的template目錄下,命名爲 base.html .)
咱們使用模板標籤: {% block %} 。 全部的 {% block %} 標籤告訴模板引擎,子模板能夠重載這些部分。 每一個{% block %}標籤所要作的是告訴模板引擎,該模板下的這一塊內容將有可能被子模板覆蓋。
如今咱們已經有了一個基本模板,咱們能夠修改 current_datetime.html 模板來 使用它:
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %}
再爲 hours_ahead 視圖建立一個模板,看起來是這樣的:
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> {% endblock %}
看起來很漂亮是否是? 每一個模板只包含對本身而言 獨一無二 的代碼。 無需多餘的部分。 若是想進行站點級的設計修改,僅需修改 base.html ,全部其它模板會當即反映出所做修改。
如下是其工做方式:
在加載 current_datetime.html 模板時,模板引擎發現了 {% extends %} 標籤, 注意到該模板是一個子模板。 模板引擎當即裝載其父模板,即本例中的 base.html 。此時,模板引擎注意到 base.html 中的三個 {% block %} 標籤,並用子模板的內容替換這些 block 。所以,引擎將會使用咱們在 { block title %} 中定義的標題,對 {% block content %} 也是如此。 因此,網頁標題一塊將由{% block title %}替換,一樣地,網頁的內容一塊將由 {% block content %}替換。
注意因爲子模板並無定義 footer 塊,模板系統將使用在父模板中定義的值。 父模板 {% block %} 標籤中的內容老是被看成一條退路。繼承並不會影響到模板的上下文。 換句話說,任何處在繼承樹上的模板均可以訪問到你傳到模板中的每個模板變量。你能夠根據須要使用任意多的繼承次數。 使用繼承的一種常見方式是下面的三層法:
<1> 建立 base.html 模板,在其中定義站點的主要外觀感覺。 這些都是不常 修改甚至從不修改的部分。 <2> 爲網站的每一個區域建立 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。這些模板對base.html 進行拓展, 幷包含區域特定的風格與設計。 <3> 爲每種類型的頁面建立獨立的模板,例如論壇頁面或者圖片庫。 這些模板拓展 相應的區域模板。
這個方法可最大限度地重用代碼,並使得向公共區域(如區域級的導航)添加內容成爲一件輕鬆的工做。
如下是使用模板繼承的一些訣竅:
<1>若是在模板中使用 {% extends %} ,必須保證其爲模板中的第一個模板標記。 不然,模板繼承將不起做用。 <2>通常來講,基礎模板中的 {% block %} 標籤越多越好。 記住,子模板沒必要定義 父模板中全部的代碼塊,所以 你能夠用合理的缺省值對一些代碼塊進行填充,而後只對子模板所需的代碼塊進行 (重)定義。 俗話說,鉤子越多越好。 <3>若是發覺本身在多個模板之間拷貝代碼,你應該考慮將該代碼段放置到父模板的 某個 {% block %} 中。 若是你須要訪問父模板中的塊的內容,使用 {{ block.super }}這個標籤吧,這一 個魔法變量將會表現出父模板中的內容。 若是隻想在上級代碼塊基礎上添加內容,而 不是所有重載,該變量就顯得很是有用了。 <4>不容許在同一個模板中定義多個同名的 {% block %} 。 存在這樣的限制是因 爲block 標籤的工做方式是雙向的。 也就是說,block 標籤不只挖了一個要填的坑,也定義了在父模板中這個坑所填充 的內容。若是模板中出現了兩個相同名稱的 {% block %} 標籤,父模板將無從得知 要使用哪一個塊的內容。
好比文中出現的例子:
class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) def __str__(self): return self.name
當python中方法名若是是__XXXX__() 的,那麼就有特殊的功能,所以叫作「魔法」方法,當使用print輸出對象的時候,只要本身定義了__str__(self) 方法,那麼就會打印從這個方法中return的數據。
若是要把一個類的實例變成str ,就須要實現特殊方法__str__() :
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __str__(self): return '(Person: %s, %s)' % (self.name, self.gender)
下面舉個例子:
如今,在交互式命令行下用 print 試試: >>> p = Person('Bob', 'male') >>> print p (Person: Bob, male) 可是,若是直接敲變量 p: >>> p <main.Person object at 0x10c941890> 彷佛__str__() 不會被調用。
由於 python定義了__str__() 和 __repr__() 兩種方法,__str__() 用於顯示給用戶,而__repr__() 用於顯示給開發人員。
參考文獻:https://www.cnblogs.com/feixuelove1009/p/8404054.html