from __future__ import unicode_literals

import re

from django.conf import settings
from django import template
from django.contrib.humanize.templatetags.humanize import intcomma

from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy, PageViewRestriction
from wagtail.wagtailcore.utils import camelcase_to_underscore
from wagtail.wagtailadmin.menu import admin_menu


register = template.Library()

register.filter('intcomma', intcomma)

@register.inclusion_tag('wagtailadmin/shared/explorer_nav.html')
def explorer_nav():
    return {
        'nodes': get_navigation_menu_items()
    }


@register.inclusion_tag('wagtailadmin/shared/explorer_nav_child.html')
def explorer_subnav(nodes):
    return {
        'nodes': nodes
    }


@register.inclusion_tag('wagtailadmin/shared/main_nav.html', takes_context=True)
def main_nav(context):
    request = context['request']

    return {
        'menu_html': admin_menu.render_html(request),
        'request': request,
    }

@register.simple_tag
def main_nav_js():
    return admin_menu.media['js']


@register.filter("ellipsistrim")
def ellipsistrim(value, max_length):
    if len(value) > max_length:
        truncd_val = value[:max_length]
        if not len(value) == max_length+1 and value[max_length+1] != " ":
            truncd_val = truncd_val[:truncd_val.rfind(" ")]
        return truncd_val + "..."
    return value


@register.filter
def fieldtype(bound_field):
    try:
        return camelcase_to_underscore(bound_field.field.__class__.__name__)
    except AttributeError:
        try:
            return camelcase_to_underscore(bound_field.__class__.__name__)
        except AttributeError:
            return ""


@register.filter
def widgettype(bound_field):
    try:
        return camelcase_to_underscore(bound_field.field.widget.__class__.__name__)
    except AttributeError:
        return ""


@register.filter
def meta_description(model):
    try:
        return model.model_class()._meta.description
    except:
        return ""


@register.assignment_tag(takes_context=True)
def page_permissions(context, page):
    """
    Usage: {% page_permissions page as page_perms %}
    Sets the variable 'page_perms' to a PagePermissionTester object that can be queried to find out
    what actions the current logged-in user can perform on the given page.
    """
    # Create a UserPagePermissionsProxy object to represent the user's global permissions, and
    # cache it in the context for the duration of the page request, if one does not exist already
    if 'user_page_permissions' not in context:
        context['user_page_permissions'] = UserPagePermissionsProxy(context['request'].user)

    # Now retrieve a PagePermissionTester from it, specific to the given page
    return context['user_page_permissions'].for_page(page)


@register.assignment_tag(takes_context=True)
def test_page_is_public(context, page):
    """
    Usage: {% test_page_is_public page as is_public %}
    Sets 'is_public' to True iff there are no page view restrictions in place on
    this page.
    Caches the list of page view restrictions in the context, to avoid repeated
    DB queries on repeated calls.
    """
    if 'all_page_view_restriction_paths' not in context:
        context['all_page_view_restriction_paths'] = PageViewRestriction.objects.select_related('page').values_list('page__path', flat=True)

    is_private = any([
        page.path.startswith(restricted_path)
        for restricted_path in context['all_page_view_restriction_paths']
    ])

    return not is_private


@register.simple_tag
def hook_output(hook_name):
    """
    Example: {% hook_output 'insert_editor_css' %}
    Whenever we have a hook whose functions take no parameters and return a string, this tag can be used
    to output the concatenation of all of those return values onto the page.
    Note that the output is not escaped - it is the hook function's responsibility to escape unsafe content.
    """
    snippets = [fn() for fn in hooks.get_hooks(hook_name)]
    return ''.join(snippets)


@register.assignment_tag
def usage_count_enabled():
    return getattr(settings, 'WAGTAIL_USAGE_COUNT_ENABLED', False)


class EscapeScriptNode(template.Node):
    TAG_NAME = 'escapescript'
    SCRIPT_RE = re.compile(r'<(-*)/script>')

    def __init__(self, nodelist):
        super(EscapeScriptNode, self).__init__()
        self.nodelist = nodelist

    def render(self, context):
        out = self.nodelist.render(context)
        escaped_out = self.SCRIPT_RE.sub(r'<-\1/script>', out)
        return escaped_out

    @classmethod
    def handle(cls, parser, token):
        nodelist = parser.parse(('end' + EscapeScriptNode.TAG_NAME,))
        parser.delete_first_token()
        return cls(nodelist)

register.tag(EscapeScriptNode.TAG_NAME, EscapeScriptNode.handle)

wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py
from __future__ import unicode_literals

import re

from django.conf import settings
from django import template
from django.contrib.humanize.templatetags.humanize import intcomma

from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy, PageViewRestriction
from wagtail.wagtailcore.utils import camelcase_to_underscore
from wagtail.wagtailadmin.menu import admin_menu


register = template.Library()

register.filter('intcomma', intcomma)

@register.inclusion_tag('wagtailadmin/shared/explorer_nav.html')
def explorer_nav():
    return {
        'nodes': get_navigation_menu_items()
    }


@register.inclusion_tag('wagtailadmin/shared/explorer_nav_child.html')
def explorer_subnav(nodes):
    return {
        'nodes': nodes
    }


@register.inclusion_tag('wagtailadmin/shared/main_nav.html', takes_context=True)
def main_nav(context):
    request = context['request']

    return {
        'menu_html': admin_menu.render_html(request),
        'request': request,
    }

@register.simple_tag
def main_nav_js():
    return admin_menu.media['js']


@register.filter("ellipsistrim")
def ellipsistrim(value, max_length):
    if len(value) > max_length:
        truncd_val = value[:max_length]
        if not len(value) == max_length+1 and value[max_length+1] != " ":
            truncd_val = truncd_val[:truncd_val.rfind(" ")]
        return truncd_val + "..."
    return value


@register.filter
def fieldtype(bound_field):
    try:
        return camelcase_to_underscore(bound_field.field.__class__.__name__)
    except AttributeError:
        try:
            return camelcase_to_underscore(bound_field.__class__.__name__)
        except AttributeError:
            return ""


@register.filter
def widgettype(bound_field):
    try:
        return camelcase_to_underscore(bound_field.field.widget.__class__.__name__)
    except AttributeError:
        return ""


@register.filter
def meta_description(model):
    try:
        return model.model_class()._meta.description
    except:
        return ""


@register.assignment_tag(takes_context=True)
def page_permissions(context, page):
    """
    Usage: {% page_permissions page as page_perms %}
    Sets the variable 'page_perms' to a PagePermissionTester object that can be queried to find out
    what actions the current logged-in user can perform on the given page.
    """
    # Create a UserPagePermissionsProxy object to represent the user's global permissions, and
    # cache it in the context for the duration of the page request, if one does not exist already
    if 'user_page_permissions' not in context:
        context['user_page_permissions'] = UserPagePermissionsProxy(context['request'].user)

    # Now retrieve a PagePermissionTester from it, specific to the given page
    return context['user_page_permissions'].for_page(page)


@register.assignment_tag(takes_context=True)
def test_page_is_public(context, page):
    """
    Usage: {% test_page_is_public page as is_public %}
    Sets 'is_public' to True iff there are no page view restrictions in place on
    this page.
    Caches the list of page view restrictions in the context, to avoid repeated
    DB queries on repeated calls.
    """
    if 'all_page_view_restriction_paths' not in context:
        context['all_page_view_restriction_paths'] = PageViewRestriction.objects.select_related('page').values_list('page__path', flat=True)

    is_private = any([
        page.path.startswith(restricted_path)
        for restricted_path in context['all_page_view_restriction_paths']
    ])

    return not is_private


@register.simple_tag
def hook_output(hook_name):
    """
    Example: {% hook_output 'insert_editor_css' %}
    Whenever we have a hook whose functions take no parameters and return a string, this tag can be used
    to output the concatenation of all of those return values onto the page.
    Note that the output is not escaped - it is the hook function's responsibility to escape unsafe content.
    """
    snippets = [fn() for fn in hooks.get_hooks(hook_name)]
    return ''.join(snippets)


@register.assignment_tag
def usage_count_enabled():
    return getattr(settings, 'WAGTAIL_USAGE_COUNT_ENABLED', False)


@register.assignment_tag
def base_url_setting():
    return getattr(settings, 'BASE_URL', None)


class EscapeScriptNode(template.Node):
    TAG_NAME = 'escapescript'
    SCRIPT_RE = re.compile(r'<(-*)/script>')

    def __init__(self, nodelist):
        super(EscapeScriptNode, self).__init__()
        self.nodelist = nodelist

    def render(self, context):
        out = self.nodelist.render(context)
        escaped_out = self.SCRIPT_RE.sub(r'<-\1/script>', out)
        return escaped_out

    @classmethod
    def handle(cls, parser, token):
        nodelist = parser.parse(('end' + EscapeScriptNode.TAG_NAME,))
        parser.delete_first_token()
        return cls(nodelist)

register.tag(EscapeScriptNode.TAG_NAME, EscapeScriptNode.handle)

wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py
diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt b/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt
index b9b64d35e09b..892a4b93d9e7 100644
--- a/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt
+++ b/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt
@@ -1,3 +1,3 @@
-{% load i18n %}
+{% load i18n wagtailadmin_tags %}{% base_url_setting as base_url %}
 {% trans "Please follow the link below to reset your password" %}
-{{ protocol }}://{{ domain }}{% url 'wagtailadmin_password_reset_confirm' uidb64=uid token=token %}
\ No newline at end of file
+{% if base_url %}{{ base_url }}{% else %}{{ protocol }}://{{ domain }}{% endif %}{% url 'wagtailadmin_password_reset_confirm' uidb64=uid token=token %}
\ No newline at end of file
diff --git a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py
index cb049d0e88a7..3b9302fce4a8 100644
--- a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py
+++ b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py
@@ -134,6 +134,11 @@ def usage_count_enabled():
     return getattr(settings, 'WAGTAIL_USAGE_COUNT_ENABLED', False)
 
 
+@register.assignment_tag
+def base_url_setting():
+    return getattr(settings, 'BASE_URL', None)
+
+
 class EscapeScriptNode(template.Node):
     TAG_NAME = 'escapescript'
     SCRIPT_RE = re.compile(r'<(-*)/script>')
diff --git a/wagtail/wagtailadmin/tests/test_password_reset.py b/wagtail/wagtailadmin/tests/test_password_reset.py
new file mode 100644
index 000000000000..e0a1dd1b4a3b
--- /dev/null
+++ b/wagtail/wagtailadmin/tests/test_password_reset.py
@@ -0,0 +1,33 @@
+from django.test import TestCase, override_settings
+from django.core import mail
+
+from wagtail.tests.utils import WagtailTestUtils
+from wagtail.wagtailcore.models import Site
+
+
+class TestUserPasswordReset(TestCase, WagtailTestUtils):
+    fixtures = ['test.json']
+
+    # need to clear urlresolver caches before/after tests, because we override ROOT_URLCONF
+    # in some tests here
+    def setUp(self):
+        from django.core.urlresolvers import clear_url_caches
+        clear_url_caches()
+
+    def tearDown(self):
+        from django.core.urlresolvers import clear_url_caches
+        clear_url_caches()
+
+    @override_settings(ROOT_URLCONF="wagtail.wagtailadmin.urls")
+    def test_email_found_default_url(self):
+        response = self.client.post('/password_reset/', {'email': 'siteeditor@example.com'})
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertIn("testserver", mail.outbox[0].body)
+
+    @override_settings(ROOT_URLCONF="wagtail.wagtailadmin.urls", BASE_URL='http://mysite.com')
+    def test_email_found_base_url(self):
+        response = self.client.post('/password_reset/', {'email': 'siteeditor@example.com'})
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertIn("mysite.com", mail.outbox[0].body)

Password reset request generates email with URL "example.com"
Received:

```
Please follow the link below to reset your password
http://example.com/admin/password_reset/confirm/NA/3x7-cfc1f37209f0c04d1ee1/
```

This time `BASE_URL` _is_ configured, but as this view is from django.contrib this is perhaps due to some other missing setting. 

Related to #693 #826

