Repository: mkdocs/mkdocs

Issue:
Page_Title is "None" on root document
`{{ page_title }}` is displaying as "None" despite my mkdocs.yml configuration as such:

```
pages:
- Usage: 'index.md'
- Installing: 'Installing.md'
```

I just updated to version 0.15.3. This wasn't a problem with whatever version I had before. This only happens to the index page.



Discussion:
I'm not able to reproduce this. Which theme are you using?

Ok, I see it now. the problem does not manifest itself in the MkDocs theme without modification. Once I modified the theme (by inserting `{{ page_title }}` randomly in a template), the problem presented itself. I noticed that `{{ page_title }}` is called at [mkdocs/themes/mkdocs/base.html#L13](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/themes/mkdocs/base.html#L13) but is wrapped in an if statement. Sure enough, on the "home" page the title is not the title of the page but the `site_name`. Although, in that case, I think the `site_name` is probably the correct thing to display -- which explains why this problem was not caught before now.

This change happened quite a while ago in 61173d12. I understand why that change was made, but I don't agree that `page_title` should be `None` for the "homepage". It should still be the "title" of the page if it has one. The template should be using a different mechanism to identify the "homepage" IMO. For example, only the "homepage" gets a `page_description`. That is `None` for all other pages. Although, `page_description` hardly seems like an intuitive way to identify the "homepage". Why not just include `is_homepage` in the context?

I think you can already tell if the page is the homepage with `{{ current_page.is_homepage }}`. We should use that instead. Also, rather than using `{{ page_title }}` we should probably use `{{ current_page.title }}`.

Reclassifying as a bug, as I'm not sure what decision is needed but we should fix this 😄 

The _Needs design decision_ label was added as I was asking for clear direction on how we want to name the various context items.  Currently there is quite a bit of inconsistency.  Some things which are page specific are named `page_<item>` (ex: `page_title`) while other things are just `<item>` (ex: `content`). Nothing uses `current_page.<item>` as per your suggestion (personally, I find "current" redundant as there only ever is one page per page). 

As a reminder, while MkDocs creates an instance of a `Page` class for each page, and that class holds the data for a page, the instance is not passed to the template. Instead, the [`get_page_context`](/mkdocs/mkdocs/blob/b2cb385db37515afcc0143b4f3d4de9a524ee6b1/mkdocs/commands/build.py#L93) function is passed the `Page` instance and returns a dict of the various items with inconsistent naming. And `page.is_homepage` is not passed to the context as all.

My recommendation would be to pass the `Page` instance directly to the template (potentially removing the need for the `get_page_context` function). However, this would be a backward incompatible change and require custom and third party templates to be changed. I suppose, for the next release we could pass in both and give users the opportunity to update their templates. Not sure how to issue a warning if a template accesses a deprecated context item though.

> As a reminder, while MkDocs creates an instance of a Page class for each page, and that class holds the data for a page, the instance is not passed to the template. 

Actually, it is passed. [current_page](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/commands/build.py#L129) is the page instance. This is why I suggested accessing the property that way in my previous comment via `current_page.is_homepage` which calls [this property method](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/nav.py#L156). We actually use this already in both themes ([read the docs](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/themes/readthedocs/base.html#L113), [mkdocs](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/themes/mkdocs/base.html#L98)).

I think we should move towards accessing properties rather than creating variables for everything. this will take a bit of a migration process - we could make both available in 0.16 and then remove the old in 1.0, if we can make that the next release. I think there is a hook in jinja2 to handle unknown variable names, we could probably come up with a warning/error with that.

 I agree "current_" is strange, we don't need that.

We probably want to define and document a new context in detail and use that. I expect we will want a few global variables only, these spring to mind, but there might be more:
- Page (what current_page is now)
- Config (to access the mkdocs config)
- Menu (all the pages)
- Table of contents (for the current page)

(Note, I avoided giving them variable names, just starting to think of everything that we need to access).

You are taking about the nav stuff. Outside of nav the first part of your comment does not apply. 

The second part is very helpful though.

> You are taking about the nav stuff. Outside of nav the first part of your comment does not apply.

Sorry, then I must be totally confused because I don't know what this means either. This issue seems to be over my head.

> Actually, it is passed. current_page is the page instance.

Well don't I feel dumb. Sorry, you are correct, I missed that. Then all we need to do is deprecate many of the other page-specific context variables and switch the templates to use the page instance.

The real issue then is how to deprecate the variables: `page_title` and `page_description`, which are redundant. And it would make sense if `content`, `toc`, `meta`, and `canonical_url` were all on the Page instance as well.

So I just went up and reread your earlier comment @d0ugal and it is now clear that I completely misread it the first time. Sorry about that. Lots of good stuff in there which is on the mark.

> We probably want to define and document a new context in detail and use that. I expect we will want a few global variables only, 

Right. As it currently stands, a number of config values are copied to context variables and then the config is made available as well. The config should be sufficient.

And then we have a bunch of function calls when building the context (both page and global) which use the config to piece together URLs, etc. There's no reason that can't be done in the template. 

Sorry, I was probably vague before and confused things.

> The real issue then is how to deprecate the variables: page_title and page_description, which are redundant. And it would make sense if content, toc, meta, and canonical_url were all on the Page instance as well.

I had a play with the Jinja2 API, if you put this code in the same directory as a test.html file it will print out the names of all the top level variables. I think this is all we need? It is a bit ugly, but as something we want to use only for an upgrade path and plan to remove later I think that is okay.

``` python
from jinja2 import Environment, FileSystemLoader, DebugUndefined, meta

loader = FileSystemLoader('.')
env = Environment(loader=loader, undefined=DebugUndefined)

template_source = env.loader.get_source(env,   'test.html')[0]
parsed_content = env.parse(template_source)
print meta.find_undeclared_variables(parsed_content)
```

Unless I'm misunderstanding something (completely possible) Jinja's `undefined` only works for variables which are not defined. What about the interim version were they are still defined but we want to raise a warning telling people to stop using them before the next release? I have a few untested ideas. Not sure which will work yet.

I think the code I have will find all variables in our case. I think the function I called is named find_undeclared_variables because variables can be defined in the template. I didn't pass any context, so there is no way it would know what we have.

So, with that code, we would look at the variables the template uses. If they match a deprecated names list we store, we then display a warning.

Turns out its even easier than that. Jinja has a [undefined logger](http://jinja.pocoo.org/docs/dev/api/#jinja2.make_logging_undefined) which issues warnings for any undefined variables used in the template.  Just need to restrict that to only warn on the list we give it.


Resolving patch:
diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py
--- a/mkdocs/commands/build.py
+++ b/mkdocs/commands/build.py
@@ -95,6 +95,14 @@ def get_page_context(page, content, toc, meta, config):
     Generate the page context by extending the global context and adding page
     specific variables.
     """
+    if config['site_url']:
+        page.set_canonical_url(config['site_url'])
+
+    page.content = content
+    page.toc = toc
+    page.meta = meta
+
+    # TODO: remove the rest in version 1.0 as they are deprecated
 
     if page.is_homepage or page.title is None:
         page_title = None
@@ -106,16 +114,9 @@ def get_page_context(page, content, toc, meta, config):
     else:
         page_description = None
 
-    if config['site_url']:
-        base = config['site_url']
-        if not base.endswith('/'):
-            base += '/'
-        canonical_url = utils.urljoin(
-            base, page.abs_url.lstrip('/'))
-    else:
-        canonical_url = None
-
     return {
+        'page': page,
+        # TODO: remove the rest in version 1.0 as they are deprecated
         'page_title': page_title,
         'page_description': page_description,
 
@@ -123,8 +124,7 @@ def get_page_context(page, content, toc, meta, config):
         'toc': toc,
         'meta': meta,
 
-
-        'canonical_url': canonical_url,
+        'canonical_url': page.canonical_url,
 
         'current_page': page,
         'previous_page': page.previous_page,
@@ -141,10 +141,9 @@ def build_template(template_name, env, config, site_navigation=None):
     except TemplateNotFound:
         return False
 
+    context = {'page': None}
     if site_navigation is not None:
-        context = get_global_context(site_navigation, config)
-    else:
-        context = {}
+        context.update(get_global_context(site_navigation, config))
 
     output_content = template.render(context)
     output_path = os.path.join(config['site_dir'], template_name)
@@ -212,10 +211,9 @@ def build_extra_templates(extra_templates, config, site_navigation=None):
         with io.open(input_path, 'r', encoding='utf-8') as template_file:
             template = jinja2.Template(template_file.read())
 
+        context = {'page': None}
         if site_navigation is not None:
-            context = get_global_context(site_navigation, config)
-        else:
-            context = {}
+            context.update(get_global_context(site_navigation, config))
 
         output_content = template.render(context)
         output_path = os.path.join(config['site_dir'], extra_template)
@@ -229,6 +227,34 @@ def build_pages(config, dump_json=False):
     site_navigation = nav.SiteNavigation(config['pages'], config['use_directory_urls'])
     loader = jinja2.FileSystemLoader(config['theme_dir'] + [config['mkdocs_templates'], ])
     env = jinja2.Environment(loader=loader)
+
+    # TODO: remove DeprecationContext in v1.0 when all deprecated vars have been removed
+    from jinja2.runtime import Context
+    deprecated_vars = [
+        'page_title',
+        'content',
+        'toc',
+        'meta',
+        'current_page',
+        'canonical_url',
+        'previous_page',
+        'next_page'
+    ]
+
+    class DeprecationContext(Context):
+        def resolve(self, key):
+            """ Log a warning when acessing any deprecated variable name. """
+            if key in deprecated_vars:
+                replacement = "page" if key == 'current_page' else "page.{0}".format(key)
+                log.warn(
+                    "Template variable warning: '{0}' is being deprecated and will not be "
+                    "available in a future version. Use '{1}' instead.".format(key, replacement)
+                )
+            return super(DeprecationContext, self).resolve(key)
+
+    env.context_class = DeprecationContext
+    # TODO: end remove DeprecationContext
+
     env.filters['tojson'] = filters.tojson
     search_index = search.SearchIndex()
 
diff --git a/mkdocs/nav.py b/mkdocs/nav.py
--- a/mkdocs/nav.py
+++ b/mkdocs/nav.py
@@ -148,6 +148,13 @@ def __init__(self, title, url, path, url_context):
         self.next_page = None
         self.ancestors = []
 
+        # Placeholders to be filled in later in the build
+        # process when we have access to the config.
+        self.canonical_url = None
+        self.content = None
+        self.meta = None
+        self.toc = None
+
     @property
     def url(self):
         return self.url_context.make_relative(self.abs_url)
@@ -174,6 +181,11 @@ def set_active(self, active=True):
         for ancestor in self.ancestors:
             ancestor.set_active(active)
 
+    def set_canonical_url(self, base):
+        if not base.endswith('/'):
+            base += '/'
+        self.canonical_url = utils.urljoin(base, self.abs_url.lstrip('/'))
+
 
 class Header(object):
     def __init__(self, title, children):


Test patch: