前回までの流れ
①. リクエストを受信
↓
②. ミドルウェアチェーンの作成
↓
③. ミドルウェアチェーンにリクエストを渡す(ここまで)
前回の記事はこちら。
Djangoのサーバーがリクエストを受け取ってからの流れ ①
前回は、ミドルウェアチェーンにリクエストを渡すところまでを追ったので、今回は_get_responseメソッドが動くところから追ってみようと思います。この辺からURLResolverなどが出てきます。
今回やる事
①. ミドルウェアチェーンに含まれる、_get_responseメソッドが呼び出される
↓
②. 該当Viewのメソッドを呼び出す事により、レスポンスクラスの取得
↓
③. テンプレートエンジンによりテンプレートのレンダリング
この間にミドルウェアによるprocess_responseも呼び出される。
↓
④. レンダリングされたレスポンスクラスをストリームに書き込む
↓
クライアントに返る。
までを追っていきたいと思います!
※レンダリング処理は一部省きます。
実装を追っていく
_get_responseメソッド
class BaseHandler:
def _get_response(self, request):
response = None
# 1. resolve_requestメソッドによって該当メソッド、引数取得
callback, callback_args, callback_kwargs = self.resolve_request(request)
# viewミドルウェアを通したり...
if response is None:
# 2. viewに対してトランザクション関連のチェック
wrapped_callback = self.make_view_atomic(callback)
# 非同期のviewだったらサブスレッドで動かしたり...
try:
# 3. 該当メソッドにリクエストと引数を渡し、レスポンスを取得する。
response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e:
# exceptionミドルウェアを通す
response = self.process_exception_by_middleware(e, request)
if response is None:
raise
# エラーチェックとか...
if hasattr(response, 'render') and callable(response.render): # 4
# templateミドルウェアを通したり...
try:
# renderが実装されてたら、テンプレートをレンダリングし、テンプレートレスポンスを取得する。
response = response.render()
except Exception as e:
# exceptionミドルウェアを通す
response = self.process_exception_by_middleware(e, request)
if response is None:
raise
# 取得したレスポンスを返す。
return response # 4
_get_responseメソッドでは、大まかに以下の様な処理が実装されているようです。
- resolve_requestで該当View、引数取得
- make_view_atomicでトランザクションチェック
- 該当viewにリクエストと引数を渡して呼び出す事でレスポンスを取得する
- レスポンスを返す(renderが実装されていればテンプレートレスポンスを返す)
ここらへんは1年くらい前に追ったのですが、1年前に比べると色々とソースが変わっていてびっくりしました。オープンソースだしそりゃそうですね。
では、1から追っていきます。
①. resolve_requestで該当Viewらの取得
class BaseHandler:
def resolve_request(self, request):
if hasattr(request, 'urfconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver()
resolver_match = resolver.resolve(request.path_info)
request.resolver_match = resolver_match
return resolver_match
ここではリクエストがurlconfを持っていたら、その引数をget_resolverメソッドに渡してURLResolverを取得しています。
※urlconfがリクエストに無くても後でsettings.pyに記載されているROOT_URLCONFをurlconfとして設定します。
そして、URLResolverのresolveメソッドにより、ResolverMatchクラスを返しています。
このResolverMatchクラスには__getitem__メソッドが実装されており、このクラスからViewの該当メソッド、該当引数が取得出来るようです。
get_resolver
urlconfを設定し、URLResolverを取得するメソッドです。
URLResolverインスタンス化時には、patternにRegexPattern, urlconf_nameにurlconfを設定しています。あとで、RegexPatternのmatchメソッドにより、まず最初のURLの探索が始まります。
def get_resolver(urlconf=None):
if urlconf is None:
urlconf = settings.ROOT_URLCONF
return _get_cached_resolver(urlconf)
@functools.lru_cache(maxsize=None)
def _get_cached_resolver(urlconf=None):
return URLResolver(RegexPattern(r'^/'), urlconf))
class URLResolver:
# 長いので省略
def __init___(self, pattern, urlconf_name ... ):
self.pattern = pattern
self.urlconf_name = urlconf_name
functools.lru_cacheというのは、LRUという仮想メモリのページングアルゴリズムを使用しキャッシュ化出来るメソッドです。
このメソッドにより、キャッシュからURLResolverを取得しているようです。
LRU(Least Recently Used)
メモリ中で今後使われる可能性が少ないページをHD(仮想メモリ)に退避させ実メモリを空ける機能があり、これを仮想メモリと言う。
LRUは、最後に利用してから最も時間が経過しているページを仮想メモリに退避させる。
以下ドキュメントから。
If maxsize is set to None, the LRU feature is disabled and the cache can grow without bound
しかし、上記の通り、ここではmaxsizeがNoneと指定されているため、LRU機能は無効になりキャッシュは無制限に拡大される様子。
URLResolverクラス
まず最初にRegexPatternのmatchメソッドにより探索が行われ、その結果一致するグループがあれば、一致するパスの検索に入りViewを取得しに行きます。
流れとしては、ユーザーが定義したurlpatternsをループし、該当のパスが存在するか検索します。該当のものがあれば、ResolverMatchクラスに諸々情報を詰め、returnする感じです。
RegexPatternから見ていきます。
まず↑でRegexPattern(r'^/')
という風にインスタンス化しました。それはself._regex
として保持されます。
class RegexPattern(CheckURLMixin):
regex = LocaleRegexDescriptor('_regex')
def __init__(self, regex, name=None, is_endpoint=False):
self._regex = regex
self._regex_dict = {}
self._is_endpoint = is_endpoint
self.name = name
self.converters = {}
そしてself.regex
であるLocaleRegexDescriptor
のsearchメソッドによって正規表現で探索を行います。
class LocaleRegexDescriptor:
def __init__(self, attr):
# attrには_regexという文字列が入る。
self.attr = attr
def __get__(self, instance, cls=None):
...
# RegexPatternの_regex ^/ をpatternに代入する。この場合、リクエストのpath_infoがスラッシュ始まりかどうか判定する。
pattern = getattr(instance, self.attr)
if isinstance(pattern, str):
instance.__dict__['regex'] = instance._compile(pattern)
# そして、RegexPatternの_compileメソッドが返る。
return instance.__dict__['regex']
...
class RegexPattern(CheckURLMixin):
...
def match(self, path):
# これにより、self.regexの__get__メソッドが動いて、self._compileメソッドにより返されたものに対してsearchを行う。
match = self.regex.search(path)
if match:
kwargs = match.groupdict()
args = () if kwargs else match.groups()
kwargs = {k: v for k, v in kwargs.items() if v is not None}
# ここでマッチした場所の終わり移行のパスを返すようにしている。
return path[match.end):], args, kwargs
return None
def _compile(self, regex):
try:
return re.compile(regex)
...
まとめると、リクエストにpath_infoが含まれていた場合は最初のスラッシュ以降のパスを返す。path_infoが含まれていない場合でも、/がWSGIRequestのインスタンス化時に設定されているので、matchしたと判定される。
path_infoについて公式ドキュメントから引用すると、
HttpRequest.path_info
Under some Web server configurations, the portion of the URL after the host name is split up into a script
prefix portion and a path info portion. The path_info attribute always contains the path info portion of the
path, no matter what Web server is being used. Using this instead of path can make your code easier to move
between test and deployment servers.
For example, if the WSGIScriptAlias for your application is set to "/minfo",
then path might be "/minfo/music/bands/the_beatles/" and path_info would be "/music/bands/the_beatles/".
てな感じみたいです。
RegexPatternのmatchメソッドがなんとなく掴めたところで、次の処理を見ていきます。
class URLResolver:
...
def resolve(self, path):
path = str(path)
tried = []
# このmatchメソッドが↑でやったもの。パスとargs, kwargsが分けられる。
# ここはpath_infoが変なものでない限り必ず通るっぽい。
match = self.pattern.match(path)
if match:
new_path, args, kwargs = match
for pattern in self.url_patterns: # urls.pyのurlpatternsのループ
try:
sub_match = pattern.resolve(new_path)
except Resolver404 as e:
self._extend_tried(tried, pattern, e.args[0].get('tried'))
else:
if sub_match:
sub_match_dict = {**kwargs, **self.default_kwargs}
sub_match_dict.update(sub_match.kwargs)
sub_match_args = sub_match.args
if not sub_match_dict:
sub_match_args = args + sub_match.args
...
return ResolverMatch(
sub_match.func,
sub_match_args,
sub_match_dict,
sub_match.url_name,
# 省略
)
...
urls.pyのurlpatternsのループ
例として適当なListViewを用意しました。
class IndexView(generics.ListView):
template_name = 'index.html'
model = Test
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
]
このurlpatternsがループされていき、個々のpathのresolveメソッドが走るようです。
※includeで指定した場合は、URLResolverのresolverが再度走る事によって、指定したURLもちゃんと検索されるようです。
urls.pyを記載する際は、Djangoだったら今は大体pathで書くと思いますが、このpathというのは何なのか。
pathの実装
以下のソースから、
pathの正体はどうやらURLResolverクラスか、URLPatternクラスになっているようです。
path = partial(_path, Pattern=RoutePattern)
def _path(route, view, kwargs=None, name=None, Pattern=None):
if isinstance(view, (list, tuple)):
pattern = Pattern(route, is_endpoint=False)
urlconf_module, app_name, namespace = view
return URLResolver(
pattern,
urlconf_module,
kwargs,
app_name=app_name,
namespace=namespace,
)
elif callable(view):
pattern = Pattern(route, name=name, is_endpoint=True)
return URLPattern(pattern, view, kwargs, name)
else:
raise TypeError('view must be a callable or a list/tuple in the case of include().')
pathではviewがリストかタプルだったらURLResolverが返り、そうじゃなくcallableだったらURLPatternクラスを返します。includeで指定した場合はタプルが返るのでURLResolverが返りますが、View(callable)を指定したら、__URLPattern__が返ります。
ここでは、
path('', views.IndexView.as_view(), name='index'),
とViewを指定しているので、
URLPattern('', view, kwargs, 'index')
といった風にURLPatternを宣言している事になります。
ここで渡しているviewはas_viewによって返された__dispatchメソッドを返すview__メソッドで、別記事でまとめました。
※また、URLPatternに渡しているpatternは、__RoutePattern__というクラスを指定していてURLの検索に使用します。
URLPatternクラス
urls.pyに記述したurlpatternsがループされると、このURLPatternクラスがループされresolveメソッドの中で、
__RoutePatternクラスのmatchメソッド__により、正規表現による該当URLの検索が行われ、それによりResolverMatchクラスが返されます。
class URLPattern:
def resolve(self, path):
# self.patternはRoutePatternクラス
match = self.pattern.match(path)
if match:
new_path, args, kwargs = match
kwargs.update(self.default_args)
return ResolverMatch(self.callback, args, kwargs, self.pattern.name, route=str(self.pattern))
RoutePatternクラス
上記のURLResolverのresolveで呼ばれるmatchメソッドはこのクラスのメソッドです。
LocaleRegexDescriptorクラスから返される、re.compileの結果に対して、searchメソッドにより正規表現で該当のパスを検索します。
class RoutePattern:
regex = LocaleRegexDescriptor('_route')
def __init__(self, route, name=None, is_endpoint=False):
self._route = route
self._regex_dict = {}
self._is_endpoint = is_endpoint
self.name = name
self.converters = _route_to_regex(str(route), is_endpoint)[1]
def match(self, path):
# ここでself.regexが呼ばれる事で、LocaleRegexDescriptorの__get__メソッドが呼ばれ、
# re.compileが返りそれに対してsearchをかける。
match = self.regex.search(path)
if match:
kwargs = match.groupdict()
for key, value in kwargs.items():
converter = self.converters[key]
try:
kwargs[key] = converter.to_python(value)
except ValueError:
return None
return path[match.end():], (), kwargs
return None
ResolverMatch
このクラスが返される事により、該当Viewの該当メソッド、該当引数らが返されます。
class ResolverMatch:
def __getitem__(self, index):
return (self.func, self.args, self.kwargs)[index]
※__getitem__の挙動が気になったのでチェックしてみました。
class Sample:
def __init__(self, name, func=None):
self.name = name
setattr(self, 'func', func)
def __getitem__(self, index):
if hasattr(self, 'func'):
return (self.name, self.func)[index]
return (self.name)[index]
def foo():
pass
s = Sample('abc', foo)
a, b = s
print(a, b)
abc <function foo at ...>
と言う風にとれました^^
また、[]でs[0]みたいに指定しても呼ばれるみたいです。
②. トランザクションチェック
class BaseHandler:
def make_view_atomic(self, view):
non_atomic_requests = getattr(view, '_non_atomic_requests', set())
for db in connections.all():
if db.settings_dict['ATOMIC_REQUESTS'] and db.alias not in non_atomic_requests:
if asyncio.iscoroutinefunction(view):
# asyncのviewではトランザクションは使用出来ない。
raise RuntimeError(
'You cannot use ATOMIC_REQUESTS with async views.'
)
# トランザクション処理を行うViewとしてラップ
view = transaction.atomic(using=db.alias)(view)
return view
ATOMIC_REQUESTSの設定がされていて、dbのaliasがviewの_non_atomic_requestsに含まれていなかったら、そのViewをトランザクション処理を行うViewとしてラップする。
③. 該当Viewの該当メソッドの呼び出し
この例では汎用Viewを定義しているので、as_viewによって渡したリクエストのメソッドの名前のメソッドが呼び出されます。
ここでは、リクエストがindexページへgetメソッドでやって来たと仮定すると、__IndexViewの親クラスのgetメソッド__が呼び出されます。
generics.ListViewはただ継承しているだけのクラスなので、BaseListViewのgetメソッドが呼び出されます。
class BaseListView:
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
...
context = self.get_context_data()
return self.render_to_response(context)
render_to_responseメソッドの結果を返すみたいです。
class TemplateResponseMixin:
response_class = TemplateResponse
def render_to_response(self, context, **response_kwargs):
response_kwargs.setdefault('content_type', self.content_type)
# 最終的にTemplateResponseクラスが返される。
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs
)
getメソッドを追っていくと、最終的にTemplateResponseクラスが返されます。
④. TemplateResponse.render
そして、TemplateResponseクラスの親クラスには、renderメソッドが実装されているので、そのrenderメソッドが呼ばれる事でレスポンスをレンダリングします。
class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request']
def __init__(self, request, template, context=None, content_type=None,
status=None, charset=None, using=None, headers=None):
super().__init__(template, context, content_type, status, charset, using, headers=headers)
self._request = request
class SimpleTemplateResponse(HttpResponse):
def render(self):
retval = self
if not self._is_rendered:
# self.rendered_contentにより、テンプレートがレンダリングされる。
self.content = self.rendered_content
# キャッシュミドルウェアとかで設定された処理を通す。
for post_callback in self._post_render_callbacks:
newretval = post_callback(retval)
if newretval is not None:
retval = newretval
return retval
レンダリングの部分
class SimpleTemplateResponse(HttpResponse):
@property
def rendered_content(self):
template = self.resolve_template(self.template_names) # 1 テンプレートを取得
context = self.resolve_context(self.context_data) # 2 コンテキスト取得
return template.render(context, self._request) # 3 レンダリング
- self.resolve_template(テンプレートの取得)
- self.resolve_context(コンテキストの取得)
- template.render(テンプレートのレンダリング)
という流れで動いていきます。
1. self.resolve_template(テンプレートの取得)
class SimpleTemplateResponse(HttpResponse):
def resolve_template(self, template):
# ここでのtemplateは、IndexViewで指定したtemplate_nameがリストで渡されているためこっち。
if isinstance(template, (list, tuple)):
return select_template(template, using=self.using)
elif isinstance(template, str):
return get_template(template, using=self.using)
else:
return template
def select_template(template_name_list, using=None):
...
# テンプレートエンジンを取得
engines = _engine_list(using)
for template_name in template_name_list:
for engine in engines:
try:
# テンプレートエンジンのget_templateメソッド
return engine.get_template(template_name)
...
1-1. テンプレートエンジン取得の部分
def _engine_list(using=None):
# ここではusingは指定していないため、engine.all()が返る。
return engines.all() if using is None else [engines[using]]
engines = EngineHanlder()
usingはNoneのため、EngineHandlerのallメソッドが返る。
このusingは、TemplateResponseクラスのインスタンス化時にself.template_engineとして渡しているもののようです。default=Noneなので、指定するには汎用Viewとかで指定しておく必要があります。
この記述の仕方的に、設定でテンプレートエンジンを複数記述しておき、使用するテンプレートエンジンをView毎に分けたりしたい時などに、それぞれのViewでテンプレートエンジンを指定しておけばいいものだと思います。
テンプレート設定
設定ファイルにはテンプレート設定はデフォルトだと大体こんな感じだと思います。複数指定するには、このTEMPLATEリストに辞書をつっこめばOKです。
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',
],
},
},
]
上記の設定に基づいてテンプレートエンジンを設定、取得します。
class EngineHandler:
def __init__(self, templates=None):
self._templates = templates
self._engines = {}
@cached_property
def templates(self):
# __iter__メソッドに最初に呼び出される。# 3
# cached_propertyにより、次からはキャッシュから取得出来る。
# 設定ファイルに記述したテンプレート設定を取得する。
if self._templates is None:
self._templates = settings.TEMPLATES
templates = {}
backend_names = []
for tpl in self._templates:
try:
# 「.」で右から2回分割した時の右から2個めの文字列を取得。
# Djangoのデフォルトだと、「django」を取得。
default_name = tpl['BACKEND'].rsplit('.', 2)[-2]
...
# 辞書にまとめる。
tpl = {
'NAME': default_name,
'DIRS': [],
'APP_DIRS': False,
'OPTIONS': {},
# **と付ける事で、テンプレート設定に基づいて辞書を上書きする。
**tpl,
}
templates[tpl['NAME']] = tpl
backend_names.append(tpl['NAME'])
# 同じテンプレートエンジンが複数指定されていた場合エラー吐いたり..
return templates
def __getitem__(self, alias):
# allメソッドのself[alias]という記述により呼び出される。 # 4
try:
# 既にself._enginesに設定されていたらそこから取得。
return self._engines[alias]
except KeyError:
try:
# テンプレート設定から作成した辞書からパラメータを取得。
params = self.templates[alias]
...
params = params.copy()
backend = params.pop('BACKEND')
engine_cls = import_string(backend)
# テンプレートエンジンにパラメータを渡しインスタンス化。
engine = engine_cls(params)
# self._enginesにセットしておく。
self._engines[alias] = engine
return engine
def __iter__(self):
# self.templatesのgetterが呼ばれそれをイテレータとして返す。 # 2
return iter(self.templates)
def all(self):
# ループするため__iter__が呼ばれる。 # 1
return [self[alias] for alias in self]
動く順番としては、
①. allメソッドによりforを宣言
②. __iter__メソッドによりtemplatesをイテレータ化
③. templates設定取得(cached_propertyにより以降はキャッシュから取得)
④. __getitem__メソッドによりテンプレートエンジンをインスタンス化し取得
⑤. allメソッドによりテンプレートエンジンインスタンスのリストが返る
また、上記のコードで、ゲッターによりテンプレート設定から取得出来るテンプレート情報は以下の様になっています。
{
'django':
{
'NAME': 'django',
'DIRS': ['ここはpath'],
'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'
]
},
'BACKEND': 'django.template.backends.django.DjangoTemplates'
}
}
キーの__django__がaliasになっており、BACKENDはテンプレートエンジンをインスタンス化する際に必要なテンプレートエンジンへのpathを示しています。テンプレートエンジンインスタンス化の際にはNAME~BACKENDが引数として渡されます。
jinja2とかだったら、キーがjinja2になって、バリューにパラメータが入ります。
1-2. テンプレートエンジンのget_templateメソッド
ここではDjangoのデフォルトテンプレートエンジンを使用しているので、__django.template.backends.django.DjangoTemplates__のget_templateメソッドが呼ばれます。
class DjangoTemplates(BaseEngine):
def __init__(self, params):
...
super().__init__(params)
self.engine = Engine(self.dirs, self.app_dirs, **options)
def get_template(self, template_name):
try:
return Template(self.engine.get_template(template_name), self)
class BaseEngine:
def __init__(self, params):
# BaseEngineのinitでは、dirsとかapp_dirsとかを設定する。
params = params.copy()
self.name = params.pop('NAME')
self.dirs = list(params.pop('DIRS'))
self.app_dirs = params.pop('APP_DIRS')
...
DjangoTemplatesクラスでは、initでEngine等のパラメータを設定し、get_templateメソッドではテンプレートクラスを第1引数にはテンプレートクラスを、第2引数にはDjangoTemplatesクラスを渡しインスタンス化します。
このself.engine.get_templateメソッドはinitで指定した、Engineクラスのメソッドです。
このメソッドではファイル検索クラスLoaderによりテンプレートを検索する処理が走り、最終的にテンプレートクラスが返されます。
※返されるTemplateクラスはdjango.template.base.pyのものです。
class Engine:
def __init__(self, dirs=None, app_dirs=False, context_processors=None,
debug=False, loaders=None, string_if_invalid='',
file_charset='utf-8', libraries=None, builtins=None, autoescape=True):
if dirs is None:
dirs = []
if context_processors is None:
context_processors = []
# これらのLoaderクラスにより、テンプレートファイルが検索されます。
if loaders is None:
loaders = ['django.template.loaders.filesystem.Loader']
if app_dirs:
loaders += ['django.template.loaders.app_directories.Loader']
if not debug:
loaders = [('django.template.loaders.cached.Loader', loaders)]
...
self.dirs = dirs
self.app_dirs = app_dirs
...
Engineクラスのinitで、テンプレートファイルを検索するためのLoaderクラスを指定しています。
class Engine:
def get_template(self, template_name):
template, origin = self.find_template(template_name)
if not hasattr(template, 'render'):
template = Template(template, origin, template_name, engine=self)
return template
def find_template(self, name, dirs=None, skip=None):
tried = []
for loader in self.template_loaders:
try:
template = loader.get_template(name, skip=skip)
return template, template.origin
...
そして、find_templateメソッド内で、Loaderクラスによるテンプレートファイルを検索する処理が走り、テンプレートクラスとoriginが返る。
Loaderクラスによるファイル検索処理らへんは省略します。
2. self.resolve_context(コンテキストの取得)
これは、ただcontextを取得するだけのメソッドが実装されているだけでした。
class SimpleTemplateResponse(HttpResponse):
def resolve_context(self, context):
return context
3. template.render(テンプレートのレンダリング)
ここでやっと、取得したTemplateクラスのrenderメソッドが呼ばれて、テンプレートのレンダリングが行われます。
class Template:
def __init__(self, template, backend):
self.template = template
self.backend = backend
def render(self, context=None, request=None):
context = make_context(context, request, autoescape=self.backend.engine.autoescape)
try:
return self.template.render(context)
except TemplateDoesNotExist as exc:
reraise(exc, self.backend)
まず、make_contextメソッドにより、contextを生成します。
def make_context(context, request=None, **kwargs):
...
else:
original_context = context
context = RequestContext(request, **kwargs)
if original_context:
context.push(original_context)
return context
contextを作ったらrenderメソッドにより、テンプレートのレンダリングを行います。
※このLoaderクラスによって取得したTemplateクラスは、上記のTemplateクラスとは別で、django.template.base.pyのTemplateクラスです。
class Template:
...
def _render(self, context):
return self.nodelist.render(context)
def render(self, context):
with context.render_context.push_state(self):
if context.template is None:
with context.bind_template(self):
context.template_name = self.name
return self._render(context)
else:
return self._render(context)
このレンダリング処理によって、テンプレートがレンダリングされ、最終的なテンプレートレスポンスがストリームに書き込まれ、クライアントに送信されるようですね。
ここらへんのレンダリング処理はまた奥が深そうなので、別でまとめたいと思います。
まとめ
【汎用ViewでViewを実装した場合】
ミドルウェアチェーンの流れの中で、process_request系の処理が終わったら
- URLResolverによって該当パス、View、引数等の取得
- as_viewによって該当のHTTPメソッドの名前のメソッドが呼ばれる
- renderが実装されていたら、テンプレートエンジンによりテンプレートのレンダリングが行われる(ミドルウェアによるprocess_responseの処理も行われる)
- レンダリングされたテンプレートレスポンスがストリームに書き込まれクライアントに送信される
という流れがおおまかに理解出来ました。
理解出来てないところもまだあるので、もっと細かい仕様の把握をしつつ、制作に生かせればと思います!
前回の記事はこちら。
Djangoのサーバーがリクエストを受け取ってからの流れ ①