from django.core.handlers.base import BaseHandler
from django.test.client import RequestFactory
from django.conf import settings
import os
import io
from functional import LogicalView
from dirsync import sync
from django.http import HttpResponse
from django.core.urlresolvers import reverse
from url import AppUrl
try:
from htmlmin.minify import html_minify
except:
[docs] def html_minify(x): return x
import six
if six.PY2:
from inspect import getargspec
else:
from inspect import signature
[docs]def bake_static():
"""
syncs the static file location to the bake directory
"""
for d in settings.STATICFILES_DIRS:
print "syncing {0}".format(d)
sync(d, os.path.join(settings.BAKE_LOCATION, "static"), "sync")
[docs]class RequestMock(RequestFactory):
"""
Construct a generic request object to get results of view
"""
[docs] def request(self, **request):
# https://gist.github.com/tschellenbach/925270
request = RequestFactory.request(self, **request)
handler = BaseHandler()
handler.load_middleware()
for middleware_method in handler._request_middleware:
if middleware_method(request):
raise Exception("Couldn't create request mock object - "
"request middleware returned a response")
return request
[docs]class BakeView(LogicalView):
"""
Extends functional view with baking functions.
expects a bake_args() generator that returns a series
of different sets of arguments to bake into files.
expects a BAKE_LOCATION - in django settings
render_to_file() - render all possible versions of this view.
"""
bake_path = ""
[docs] @classmethod
def bake(cls, limit_query=None, **kwargs):
"""
render all versions of this view into a files
"""
print "baking {0}".format(cls.__name__)
cls._prepare_bake()
i = cls()
func = i.bake_args
if six.PY2:
l = len(getargspec(i.bake_args).args)
else:
l = len(signature(i.bake_args).parameters)
if l > 1:
generator = i.bake_args(limit_query)
else:
generator = i.bake_args()
for o in generator:
if o == None:
i.render_to_file(**kwargs)
else:
i.render_to_file(o, **kwargs)
@classmethod
def _prepare_bake(self):
"""
class method - store modifications for the class for class
- e.g. precache many objects for faster render
"""
pass
def _get_bake_path(self, *args):
"""
override to have a more clever way of specifying
the destination to write to
uses class.bake_path is present, if not constructs
from url of view
"""
if self.__class__.bake_path:
if args:
bake_path = self.__class__.bake_path.format(*args)
else:
bake_path = self.__class__.bake_path
else:
rev = reverse(self.__class__.url_name,args=args)
bake_path = rev.replace("/","\\")[1:]
if bake_path[-1] == "\\":
bake_path += "index.html"
else:
bake_path += ".html"
return os.path.join(settings.BAKE_LOCATION,
bake_path)
[docs] def render_to_file(self, args=None, only_absent=False):
"""
renders this set of arguments to a files
"""
if args == None:
args = []
file_path = self._get_bake_path(*args)
if only_absent and os.path.isfile(file_path):
return None
print u"saving {0}".format(file_path)
directory = os.path.dirname(file_path)
if os.path.isdir(directory) == False:
os.makedirs(directory)
request = RequestMock().request()
request.path = "/" + file_path.replace(settings.BAKE_LOCATION, "")
request.path = request.path.replace(
"\\", "/").replace("index.html", "").replace(".html", "")
context = self._get_view_context(request, *args)
if isinstance(context, HttpResponse):
html = html_minify(context.content).replace(
"<html><head></head><body>", "").replace("</body></html>", "")
else:
html = html_minify(self.context_to_html(request, context).content)
with io.open(file_path, "w", encoding="utf-8") as f:
f.write(html)
[docs] @classmethod
def write_file(cls, args, path, minimise=True):
"""
more multi-purpose writer - accepts path argument
"""
request = RequestMock().request()
content = cls.as_view(decorators=False, no_auth=True)(
request, *args).content
if "<html" in content and minimise:
content = html_minify(content)
if type(content) == str:
content = unicode(content, "utf-8", errors="ignore")
print u"writing {0}".format(path)
with io.open(path, "w", encoding="utf-8") as f:
f.write(content)
[docs] def bake_args(self, limit_query):
"""
subclass with a generator that feeds all possible arguments into the view
"""
return [None]
[docs]class BaseBakeManager(object):
"""
Manager for bake command function
Subclass as views.BakeManager to add more custom behaviour
"""
def __init__(self,views_module=None):
if views_module:
self.app_urls = AppUrl(views_module)
else:
self.app_urls = None
[docs] def create_bake_dir(self):
if not os.path.exists(settings.BAKE_LOCATION):
os.makedirs(settings.BAKE_LOCATION)
[docs] def get_static_destination(self):
if hasattr(settings,"BAKE_STATIC_LOCATION"):
return settings.BAKE_STATIC_LOCATION
else:
return os.path.join(settings.BAKE_LOCATION,"static")
[docs] def copy_static_files(self):
for d in [settings.STATIC_ROOT]:
dir_loc = self.get_static_destination()
print "syncing {0}".format(d)
if os.path.isdir(dir_loc) == False:
os.makedirs(dir_loc)
sync(d,dir_loc,"sync")
[docs] def amend_settings(self,**kwargs):
for k,v in kwargs.iteritems():
if v.lower() == "true":
rv = True
elif v.lower() == "false":
rv = False
else:
rv = v
setattr(settings,k,rv)
[docs] def bake_app(self):
self.app_urls.bake()
[docs] def bake(self,**kwargs):
"""
this is the main function
"""
if self.app_urls and self.app_urls.has_bakeable_views():
self.amend_settings(**kwargs)
self.create_bake_dir()
self.copy_static_files()
self.bake_app()