diff --git a/README.markdown b/README.markdown index b748c56..594484a 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,7 @@ ## Introduction -How many CPU cycles do you suppose are wasted on blogs that are generated every request? Wouldn’t it make more sense to generate them only when they’re updated? StaticGenerator is a Python class for Django that makes it easy to create static files for lightning fast performance. +How many CPU cycles do you suppose are wasted on blogs that are generated every request? Wouldn’t it make more sense to generate them only when they’re updated? StaticGenerator is a Python class for Django that makes it easy to create static files for lightning fast performance. ## Fork @@ -11,14 +11,14 @@ This is a fork from the main branch in order to add patches from [bolhoed](https In short, this adds the ability to only cache for anonymous users and to add the ability to exclude urls: WEB_ROOT = os.path.join(os.path.dirname(__file__), 'generated') - + STATIC_GENERATOR_ANONYMOUS_ONLY = True - + STATIC_GENERATOR_URLS = ( r'^/$', r'^/(articles|projects|about)', ) - + STATIC_GENERATOR_EXCLUDE_URLS = ( r'\.xml$', r'^/articles/search', @@ -26,12 +26,14 @@ In short, this adds the ability to only cache for anonymous users and to add the r'^/articles/comments/posted', ) + + ## Download You can get StaticGenerator using `easy_install`: easy_install staticgenerator - + Or download from the [cheeseshop](http://pypi.python.org/pypi/staticgenerator/1.3). ## Usage @@ -51,7 +53,13 @@ First, add Regexes of URLs you want to cache to `settings.py` like so: r'^/blog', r'^/about', ) - + + STATIC_GENERATOR_QUERYSTRINGS = ( + r'^page$', + r'^print$', + r'^debug$', + ) + Second, add the Middleware to `MIDDLEWARE_CLASSES`: MIDDLEWARE_CLASSES = ( @@ -60,9 +68,9 @@ Second, add the Middleware to `MIDDLEWARE_CLASSES`: 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', ...snip... ) - + **Note**: You must place the StaticGeneratorMiddleware before FlatpageFallbackMiddleware if you use it. - + When the pages are accessed for the first time, the body of the page is saved into a static file. This is completely transparent to the end-user. When the page or an associated object has changed, simply delete the cached file (See notes on Signals). ### Method 2: Generate on Save @@ -111,7 +119,7 @@ Every time you save a Post or FlatPage it deletes the static file (notice that I dispatcher.connect(publish_comment, sender=Comment, signal=signals.post_save) dispatcher.connect(publish_comment, sender=FreeComment, signal=signals.post_save) - + ## Configure your front-end ### Sample Nginx configuration @@ -120,12 +128,12 @@ This configuration snippet shows how Nginx can automatically show the index.html # This example configuration only shows parts relevant to a Django app http { - + upstream django { # Apache/mod_python running on port 7000 server example.com:7000; } - + server { server_name example.com; root /var/www/; @@ -144,7 +152,7 @@ This configuration snippet shows how Nginx can automatically show the index.html } } } - + ## It’s not for Everything The beauty of the generator is that you choose when and what urls are made into static files. Obviously a contact form or search form won’t work this way, so we just leave them as regular Django requests. In your front-end http server (you are using a front-end web server, right?) just set the URLs you want to be served as static and they’re already being served. diff --git a/staticgenerator/__init__.py b/staticgenerator/__init__.py index 5b5d8ac..6565f39 100644 --- a/staticgenerator/__init__.py +++ b/staticgenerator/__init__.py @@ -27,8 +27,8 @@ class StaticGenerator(object): from staticgenerator import quick_publish quick_publish('/', Post.objects.live(), FlatPage) - The class accepts a list of 'resources' which can be any of the - following: URL path (string), Model (class or instance), Manager, or + The class accepts a list of 'resources' which can be any of the + following: URL path (string), Model (class or instance), Manager, or QuerySet. As of v1.1, StaticGenerator includes file and path deletion:: @@ -63,7 +63,7 @@ def parse_dependencies(self, kw): settings = kw.get('settings', None) site = kw.get('site', None) fs = kw.get('fs', None) - + self.http_request = http_request if not http_request: from django.http import HttpRequest @@ -203,7 +203,7 @@ def get_filename_from_path(self, path, query_string): def publish_from_path(self, path, query_string=None, content=None): """ - Gets filename and content for a path, attempts to create directory if + Gets filename and content for a path, attempts to create directory if necessary, writes to file. """ content_path = path diff --git a/staticgenerator/filesystem.py b/staticgenerator/filesystem.py index b22bbaa..dd94924 100644 --- a/staticgenerator/filesystem.py +++ b/staticgenerator/filesystem.py @@ -31,12 +31,11 @@ def remove(self, path): def rmdir(self, directory): os.rmdir(directory) - + def join(self, *paths): if not paths: return "" return os.path.join(paths[0], *[path.lstrip("/") for path in paths[1:]]) - + def dirname(self, path): return os.path.dirname(path) - diff --git a/staticgenerator/handlers.py b/staticgenerator/handlers.py index fb01416..b7824ff 100644 --- a/staticgenerator/handlers.py +++ b/staticgenerator/handlers.py @@ -3,6 +3,7 @@ from django.core.handlers.base import BaseHandler + class DummyHandler(BaseHandler): """Required to process request and response middleware""" diff --git a/staticgenerator/middleware.py b/staticgenerator/middleware.py index ca92d94..694f6bc 100644 --- a/staticgenerator/middleware.py +++ b/staticgenerator/middleware.py @@ -1,30 +1,49 @@ import re +import urllib from django.conf import settings from staticgenerator import StaticGenerator + class StaticGeneratorMiddleware(object): """ This requires settings.STATIC_GENERATOR_URLS tuple to match on URLs - + Example:: - + STATIC_GENERATOR_URLS = ( r'^/$', r'^/blog', ) - + + Sometimes people try stuff like this: + + ``index.html?page=../../../../../../../../../../proc/self`` + + STATIC_GENERATOR_QUERYSTRINGS = ( + 'page', + 'print', + ) + """ urls = tuple([re.compile(url) for url in settings.STATIC_GENERATOR_URLS]) + querystrings_allowed = tuple([q for q in settings.STATIC_GENERATOR_QUERYSTRINGS]) excluded_urls = tuple([re.compile(url) for url in getattr(settings, 'STATIC_GENERATOR_EXCLUDE_URLS', [])]) gen = StaticGenerator() - + def process_response(self, request, response): path = request.path_info - query_string = request.META.get('QUERY_STRING', '') + if response.status_code == 200: + query_string_dict = request.GET.copy() + if getattr(settings, 'STATIC_GENERATOR_ANONYMOUS_ONLY', False) and not request.user.is_anonymous(): return response + final_querystring = dict() + for key in query_string_dict.keys(): + if key in self.querystrings_allowed: + final_querystring[key] = query_string_dict[key] + excluded = False for url in self.excluded_urls: if url.match(path): @@ -34,7 +53,7 @@ def process_response(self, request, response): if not excluded: for url in self.urls: if url.match(path): - self.gen.publish_from_path(path, query_string, response.content) + self.gen.publish_from_path(path, urllib.urlencode(final_querystring, True), response.content) break return response