prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  wagtailfrontendcache throws an error when a root page is created without a site
  On a new install of wagtail w/ wagtailfrontendcache enabled, I go through the following steps:
  1. Go to the admin
  2. Delete the default "welcome" page from the database
  3. Create a new root page
  
  After I create the new root page, I get the following error:
  
  ```
  [17/Apr/2015 20:02:28] ERROR [django.request:231] Internal Server Error: /admin/pages/new/pages/genericpage/1/
  Traceback (most recent call last):
    File "/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/django/core/handlers/base.py", line 111, in get_response
      response = wrapped_callback(request, *callback_args, **callback_kwargs)
    File "/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
      return view_func(request, *args, **kwargs)
    File "/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/wagtailadmin/views/pages.py", line 211, in create
      revision.publish()
    File "/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/wagtailcore/models.py", line 1141, in publish
      page_published.send(sender=page.specific_class, instance=page.specific)
    File "/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/django/dispatch/dispatcher.py", line 198, in send
      response = receiver(signal=self, sender=sender, **named)
    File "/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/contrib/wagtailfrontendcache/signal_handlers.py", line 9, in page_published_signal_handler
      purge_page_from_cache(instance)
    File "/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/contrib/wagtailfrontendcache/utils.py", line 100, in purge_page_from_cache
      logger.info("[%s] Purging URL: %s", backend_name, page.full_url + path[1:])
  TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
  ```
  
  Digging into the code, this error is the request of the following line:
  
  ```
  logger.info("[%s] Purging URL: %s", backend_name, page.full_url + path[1:])
  ```
  
  This line failes because `page.full_url` is set to `None` when it executes, which results in the line throwing an exception. The new page model is still saved, so I just need to refresh the admin in my browser to get back into a good working state.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `wagtail/contrib/wagtailfrontendcache/utils.py`
  Content:
  ```
  1 import logging
  2 
  3 from django.conf import settings
  4 from django.utils.module_loading import import_string
  5 from django.core.exceptions import ImproperlyConfigured
  6 
  7 
  8 logger = logging.getLogger('wagtail.frontendcache')
  9 
  10 
  11 class InvalidFrontendCacheBackendError(ImproperlyConfigured):
  12     pass
  13 
  14 
  15 def get_backends(backend_settings=None, backends=None):
  16     # Get backend settings from WAGTAILFRONTENDCACHE setting
  17     if backend_settings is None:
  18         backend_settings = getattr(settings, 'WAGTAILFRONTENDCACHE', None)
  19 
  20     # Fallback to using WAGTAILFRONTENDCACHE_LOCATION setting (backwards compatibility)
  21     if backend_settings is None:
  22         cache_location = getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', None)
  23 
  24         if cache_location is not None:
  25             backend_settings = {
  26                 'default': {
  27                     'BACKEND': 'wagtail.contrib.wagtailfrontendcache.backends.HTTPBackend',
  28                     'LOCATION': cache_location,
  29                 },
  30             }
  31 
  32     # No settings found, return empty list
  33     if backend_settings is None:
  34         return {}
  35 
  36     backend_objects = {}
  37 
  38     for backend_name, _backend_config in backend_settings.items():
  39         if backends is not None and backend_name not in backends:
  40             continue
  41 
  42         backend_config = _backend_config.copy()
  43         backend = backend_config.pop('BACKEND')
  44 
  45         # Try to import the backend
  46         try:
  47             backend_cls = import_string(backend)
  48         except ImportError as e:
  49             raise InvalidFrontendCacheBackendError("Could not find backend '%s': %s" % (
  50                 backend, e))
  51 
  52         backend_objects[backend_name] = backend_cls(backend_config)
  53 
  54     return backend_objects
  55 
  56 
  57 def purge_url_from_cache(url, backend_settings=None, backends=None):
  58     for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():
  59         logger.info("[%s] Purging URL: %s", backend_name, url)
  60         backend.purge(url)
  61 
  62 
  63 def purge_page_from_cache(page, backend_settings=None, backends=None):
  64     for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():
  65         # Purge cached paths from cache
  66         for path in page.specific.get_cached_paths():
  67             logger.info("[%s] Purging URL: %s", backend_name, page.full_url + path[1:])
  68             backend.purge(page.full_url + path[1:])
  69
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/wagtail/contrib/wagtailfrontendcache/utils.py b/wagtail/contrib/wagtailfrontendcache/utils.py
  --- a/wagtail/contrib/wagtailfrontendcache/utils.py
  +++ b/wagtail/contrib/wagtailfrontendcache/utils.py
  @@ -61,8 +61,12 @@
   
   
   def purge_page_from_cache(page, backend_settings=None, backends=None):
  +    page_url = page.full_url
  +    if page_url is None:  # nothing to be done if the page has no routable URL
  +        return
  +
       for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():
           # Purge cached paths from cache
           for path in page.specific.get_cached_paths():
  -            logger.info("[%s] Purging URL: %s", backend_name, page.full_url + path[1:])
  -            backend.purge(page.full_url + path[1:])
  +            logger.info("[%s] Purging URL: %s", backend_name, page_url + path[1:])
  +            backend.purge(page_url + path[1:])
  
verification_info: {"golden_diff": "diff --git a/wagtail/contrib/wagtailfrontendcache/utils.py b/wagtail/contrib/wagtailfrontendcache/utils.py\n--- a/wagtail/contrib/wagtailfrontendcache/utils.py\n+++ b/wagtail/contrib/wagtailfrontendcache/utils.py\n@@ -61,8 +61,12 @@\n \n \n def purge_page_from_cache(page, backend_settings=None, backends=None):\n+    page_url = page.full_url\n+    if page_url is None:  # nothing to be done if the page has no routable URL\n+        return\n+\n     for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():\n         # Purge cached paths from cache\n         for path in page.specific.get_cached_paths():\n-            logger.info(\"[%s] Purging URL: %s\", backend_name, page.full_url + path[1:])\n-            backend.purge(page.full_url + path[1:])\n+            logger.info(\"[%s] Purging URL: %s\", backend_name, page_url + path[1:])\n+            backend.purge(page_url + path[1:])\n", "issue": "wagtailfrontendcache throws an error when a root page is created without a site\nOn a new install of wagtail w/ wagtailfrontendcache enabled, I go through the following steps:\n1. Go to the admin\n2. Delete the default \"welcome\" page from the database\n3. Create a new root page\n\nAfter I create the new root page, I get the following error:\n\n```\n[17/Apr/2015 20:02:28] ERROR [django.request:231] Internal Server Error: /admin/pages/new/pages/genericpage/1/\nTraceback (most recent call last):\n  File \"/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/django/core/handlers/base.py\", line 111, in get_response\n    response = wrapped_callback(request, *callback_args, **callback_kwargs)\n  File \"/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/django/contrib/auth/decorators.py\", line 21, in _wrapped_view\n    return view_func(request, *args, **kwargs)\n  File \"/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/wagtailadmin/views/pages.py\", line 211, in create\n    revision.publish()\n  File \"/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/wagtailcore/models.py\", line 1141, in publish\n    page_published.send(sender=page.specific_class, instance=page.specific)\n  File \"/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/django/dispatch/dispatcher.py\", line 198, in send\n    response = receiver(signal=self, sender=sender, **named)\n  File \"/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/contrib/wagtailfrontendcache/signal_handlers.py\", line 9, in page_published_signal_handler\n    purge_page_from_cache(instance)\n  File \"/Users/jryding/.virtualenvs/cms/lib/python2.7/site-packages/wagtail/contrib/wagtailfrontendcache/utils.py\", line 100, in purge_page_from_cache\n    logger.info(\"[%s] Purging URL: %s\", backend_name, page.full_url + path[1:])\nTypeError: unsupported operand type(s) for +: 'NoneType' and 'str'\n```\n\nDigging into the code, this error is the request of the following line:\n\n```\nlogger.info(\"[%s] Purging URL: %s\", backend_name, page.full_url + path[1:])\n```\n\nThis line failes because `page.full_url` is set to `None` when it executes, which results in the line throwing an exception. The new page model is still saved, so I just need to refresh the admin in my browser to get back into a good working state.\n\n", "before_files": [{"content": "import logging\n\nfrom django.conf import settings\nfrom django.utils.module_loading import import_string\nfrom django.core.exceptions import ImproperlyConfigured\n\n\nlogger = logging.getLogger('wagtail.frontendcache')\n\n\nclass InvalidFrontendCacheBackendError(ImproperlyConfigured):\n    pass\n\n\ndef get_backends(backend_settings=None, backends=None):\n    # Get backend settings from WAGTAILFRONTENDCACHE setting\n    if backend_settings is None:\n        backend_settings = getattr(settings, 'WAGTAILFRONTENDCACHE', None)\n\n    # Fallback to using WAGTAILFRONTENDCACHE_LOCATION setting (backwards compatibility)\n    if backend_settings is None:\n        cache_location = getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', None)\n\n        if cache_location is not None:\n            backend_settings = {\n                'default': {\n                    'BACKEND': 'wagtail.contrib.wagtailfrontendcache.backends.HTTPBackend',\n                    'LOCATION': cache_location,\n                },\n            }\n\n    # No settings found, return empty list\n    if backend_settings is None:\n        return {}\n\n    backend_objects = {}\n\n    for backend_name, _backend_config in backend_settings.items():\n        if backends is not None and backend_name not in backends:\n            continue\n\n        backend_config = _backend_config.copy()\n        backend = backend_config.pop('BACKEND')\n\n        # Try to import the backend\n        try:\n            backend_cls = import_string(backend)\n        except ImportError as e:\n            raise InvalidFrontendCacheBackendError(\"Could not find backend '%s': %s\" % (\n                backend, e))\n\n        backend_objects[backend_name] = backend_cls(backend_config)\n\n    return backend_objects\n\n\ndef purge_url_from_cache(url, backend_settings=None, backends=None):\n    for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():\n        logger.info(\"[%s] Purging URL: %s\", backend_name, url)\n        backend.purge(url)\n\n\ndef purge_page_from_cache(page, backend_settings=None, backends=None):\n    for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():\n        # Purge cached paths from cache\n        for path in page.specific.get_cached_paths():\n            logger.info(\"[%s] Purging URL: %s\", backend_name, page.full_url + path[1:])\n            backend.purge(page.full_url + path[1:])\n", "path": "wagtail/contrib/wagtailfrontendcache/utils.py"}], "after_files": [{"content": "import logging\n\nfrom django.conf import settings\nfrom django.utils.module_loading import import_string\nfrom django.core.exceptions import ImproperlyConfigured\n\n\nlogger = logging.getLogger('wagtail.frontendcache')\n\n\nclass InvalidFrontendCacheBackendError(ImproperlyConfigured):\n    pass\n\n\ndef get_backends(backend_settings=None, backends=None):\n    # Get backend settings from WAGTAILFRONTENDCACHE setting\n    if backend_settings is None:\n        backend_settings = getattr(settings, 'WAGTAILFRONTENDCACHE', None)\n\n    # Fallback to using WAGTAILFRONTENDCACHE_LOCATION setting (backwards compatibility)\n    if backend_settings is None:\n        cache_location = getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', None)\n\n        if cache_location is not None:\n            backend_settings = {\n                'default': {\n                    'BACKEND': 'wagtail.contrib.wagtailfrontendcache.backends.HTTPBackend',\n                    'LOCATION': cache_location,\n                },\n            }\n\n    # No settings found, return empty list\n    if backend_settings is None:\n        return {}\n\n    backend_objects = {}\n\n    for backend_name, _backend_config in backend_settings.items():\n        if backends is not None and backend_name not in backends:\n            continue\n\n        backend_config = _backend_config.copy()\n        backend = backend_config.pop('BACKEND')\n\n        # Try to import the backend\n        try:\n            backend_cls = import_string(backend)\n        except ImportError as e:\n            raise InvalidFrontendCacheBackendError(\"Could not find backend '%s': %s\" % (\n                backend, e))\n\n        backend_objects[backend_name] = backend_cls(backend_config)\n\n    return backend_objects\n\n\ndef purge_url_from_cache(url, backend_settings=None, backends=None):\n    for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():\n        logger.info(\"[%s] Purging URL: %s\", backend_name, url)\n        backend.purge(url)\n\n\ndef purge_page_from_cache(page, backend_settings=None, backends=None):\n    page_url = page.full_url\n    if page_url is None:  # nothing to be done if the page has no routable URL\n        return\n\n    for backend_name, backend in get_backends(backend_settings=backend_settings, backends=backends).items():\n        # Purge cached paths from cache\n        for path in page.specific.get_cached_paths():\n            logger.info(\"[%s] Purging URL: %s\", backend_name, page_url + path[1:])\n            backend.purge(page_url + path[1:])\n", "path": "wagtail/contrib/wagtailfrontendcache/utils.py"}]}