Navigation Menu

Skip to content

Commit

Permalink
Tests for StaticGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
andrews committed Sep 14, 2009
1 parent 1d58eca commit b26eb72
Show file tree
Hide file tree
Showing 6 changed files with 718 additions and 71 deletions.
5 changes: 3 additions & 2 deletions Makefile
Expand Up @@ -8,6 +8,7 @@ clean:

unit: clean
@echo "Running unit tests..."
@nosetests -d -s --verbose --with-coverage --cover-inclusive --cover-package=staticgenerator \
staticgenerator/tests/unit
@export PYTHONPATH=`pwd`:`pwd`/staticgenerator::$$PYTHONPATH && \
nosetests -d -s --verbose --with-coverage --cover-inclusive --cover-package=staticgenerator \
staticgenerator/tests/unit

Empty file added __init__.py
Empty file.
219 changes: 150 additions & 69 deletions staticgenerator/__init__.py
@@ -1,26 +1,51 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-

"""Static file generator for Django."""
import os
import stat
import tempfile
from django.http import HttpRequest
from django.core.handlers.base import BaseHandler
from django.db.models.base import ModelBase
from django.db.models.manager import Manager
from django.db.models import Model
from django.db.models.query import QuerySet
from django.utils.functional import Promise
from django.conf import settings

class FileSystem(object):
def exists(self, path):
return os.path.exists(path)

def makedirs(self, path):
os.makedirs(path)

def tempfile(self, directory):
return tempfile.mkstemp(dir=directory)

def write(self, f, content):
os.write(f, content)

def close(self, f):
os.close(f)

def chmod(self, filename, flags):
os.chmod(filename, flags)

def rename(self, from_file, to_file):
os.rename(tmpname, filename)

def remove(self, path):
os.remove(path)

def rmdir(self, directory):
os.rmdir(directory)

class DummyHandler(BaseHandler):
"""Required to process request and response middleware"""

def __call__(self, request):
self.load_middleware()
response = self.get_response(request)
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
return response

return response

class StaticGeneratorException(Exception):
pass
Expand All @@ -29,156 +54,212 @@ class StaticGenerator(object):
"""
The StaticGenerator class is created for Django applications, like a blog,
that are not updated per request.
Usage is simple::
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
QuerySet.
As of v1.1, StaticGenerator includes file and path deletion::
from staticgenerator import quick_delete
quick_delete('/page-to-delete/')
The most effective usage is to associate a StaticGenerator with a model's
post_save and post_delete signal.
The reason for having all the optional parameters is to reduce coupling
with django in order for more effectively unit testing.
"""

def __init__(self, *resources):

def __init__(self, *resources, **kw):
self.parse_dependencies(kw)

self.resources = self.extract_resources(resources)
self.server_name = self.get_server_name()

try:
self.web_root = getattr(settings, 'WEB_ROOT')
self.web_root = getattr(self.settings, 'WEB_ROOT')
except AttributeError:
raise StaticGeneratorException('You must specify WEB_ROOT in settings.py')

def parse_dependencies(self, kw):
http_request = kw.get('http_request', None)
model_base = kw.get('model_base', None)
manager = kw.get('manager', None)
model = kw.get('model', None)
queryset = kw.get('queryset', None)
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
self.http_request = HttpRequest

self.model_base = model_base
if not model_base:
from django.db.models.base import ModelBase
self.model_base = ModelBase

self.manager = manager
if not manager:
from django.db.models.manager import Manager
self.manager = Manager

self.model = model
if not model:
from django.db.models import Model
self.model = Model

self.queryset = queryset
if not queryset:
from django.db.models.query import QuerySet
self.queryset = QuerySet

self.settings = settings
if not settings:
from django.conf import settings
self.settings = settings

self.fs = fs
if not fs:
self.fs = FileSystem()

self.site = site

def extract_resources(self, resources):
"""Takes a list of resources, and gets paths by type"""
extracted = []

for resource in resources:

# A URL string
if isinstance(resource, (str, unicode, Promise)):
extracted.append(str(resource))
continue

# A model instance; requires get_absolute_url method
if isinstance(resource, Model):
if isinstance(resource, self.model):
extracted.append(resource.get_absolute_url())
continue

# If it's a Model, we get the base Manager
if isinstance(resource, ModelBase):
if isinstance(resource, self.model_base):
resource = resource._default_manager

# If it's a Manager, we get the QuerySet
if isinstance(resource, Manager):
if isinstance(resource, self.manager):
resource = resource.all()

# Append all paths from obj.get_absolute_url() to list
if isinstance(resource, QuerySet):
if isinstance(resource, self.queryset):
extracted += [obj.get_absolute_url() for obj in resource]

return extracted

def get_server_name(self):
'''Tries to get the server name.
First we look in the django settings.
If it's not found we try to get it from the current Site.
Otherwise, return "localhost".
'''
try:
return getattr(settings, 'SERVER_NAME')
return getattr(self.settings, 'SERVER_NAME')
except:
pass

try:
from django.contrib.sites.models import Site
return Site.objects.get_current().domain
if not self.site:
from django.contrib.sites.models import Site
self.site = Site
return self.site.objects.get_current().domain
except:
print '*** Warning ***: Using "localhost" for domain name. Use django.contrib.sites or set settings.SERVER_NAME to disable this warning.'
return 'localhost'

def get_content_from_path(self, path):
"""
Imitates a basic http request using DummyHandler to retrieve
resulting output (HTML, XML, whatever)
"""
request = HttpRequest()
request = self.http_request()
request.path_info = path
request.META.setdefault('SERVER_PORT', 80)
request.META.setdefault('SERVER_NAME', self.server_name)

handler = DummyHandler()
response = handler(request)

return response.content

def get_filename_from_path(self, path):
"""
Returns (filename, directory)
Creates index.html for path if necessary
"""
if path.endswith('/'):
path = '%sindex.html' % path
fn = os.path.join(self.web_root, path.lstrip('/')).encode('utf-8')
return fn, os.path.dirname(fn)

filename = os.path.join(self.web_root, path.lstrip('/')).encode('utf-8')
return filename, os.path.dirname(filename)

def publish_from_path(self, path, content=None):
"""
Gets filename and content for a path, attempts to create directory if
necessary, writes to file.
"""
fn, directory = self.get_filename_from_path(path)
filename, directory = self.get_filename_from_path(path)
if not content:
content = self.get_content_from_path(path)
if not os.path.exists(directory):

if not self.fs.exists(directory):
try:
os.makedirs(directory)
self.fs.makedirs(directory)
except:
raise StaticGeneratorException('Could not create the directory: %s' % directory)

try:
f, tmpname = tempfile.mkstemp(dir=directory)
os.write(f, content)
os.close(f)
os.chmod(tmpname, stat.S_IREAD | stat.S_IWRITE | stat.S_IWUSR | stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
os.rename(tmpname, fn)

f, tmpname = self.fs.tempfile(dir=directory)
self.fs.write(f, content)
self.fs.close(f)
self.fs.chmod(tmpname, stat.S_IREAD | stat.S_IWRITE | stat.S_IWUSR | stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
self.fs.rename(tmpname, filename)
except:
raise StaticGeneratorException('Could not create the file: %s' % fn)
raise StaticGeneratorException('Could not create the file: %s' % filename)

def delete_from_path(self, path):
"""Deletes file, attempts to delete directory"""
fn, directory = self.get_filename_from_path(path)
filename, directory = self.get_filename_from_path(path)
try:
if os.path.exists(fn):
os.remove(fn)
if self.fs.exists(filename):
self.fs.remove(filename)
except:
raise StaticGeneratorException('Could not delete file: %s' % fn)
raise StaticGeneratorException('Could not delete file: %s' % filename)

try:
os.rmdir(directory)
self.fs.rmdir(directory)
except OSError:
# Will fail if a directory is not empty, in which case we don't
# want to delete it anyway
pass
pass

def do_all(self, func):
return [func(path) for path in self.resources]

def delete(self):
return self.do_all(self.delete_from_path)

def publish(self):
return self.do_all(self.publish_from_path)

def quick_publish(*resources):
return StaticGenerator(*resources).publish()

def quick_delete(*resources):
return StaticGenerator(*resources).delete()
Empty file.
Empty file.

0 comments on commit b26eb72

Please sign in to comment.