Commits

Rich Leland  committed f4c28d7

Added Mosso Cloud Files backend and example project.

  • Participants
  • Parent commits 3c8461f

Comments (0)

Files changed (11)

     * Wim Leers (SymlinkOrCopy + patches)
     * Michael Elsdörfer (Overwrite + PEP8 compatibility)
     * Christian Klein (CouchDB)
+    * Rich Leland (Mosso Cloud Files)
 
 Extra thanks to Marty for adding this in Django, 
 you can buy his very interesting book (Pro Django).

File backends/mosso.py

+"""
+Custom storage for django with Mosso Cloud Files backend.
+Created by Rich Leland <rich@richleland.com>.
+"""
+import re
+
+from django.conf import settings
+from django.core.files import File
+from django.core.files.storage import Storage
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.text import get_valid_filename
+
+
+try:
+    import cloudfiles
+    from cloudfiles.errors import NoSuchObject
+except ImportError:
+    raise ImproperlyConfigured, "Could not load cloudfiles dependency. See http://www.mosso.com/cloudfiles.jsp."
+
+try:
+    CLOUDFILES_USERNAME = settings.CLOUDFILES_USERNAME
+    CLOUDFILES_API_KEY = settings.CLOUDFILES_API_KEY
+    CLOUDFILES_CONTAINER = settings.CLOUDFILES_CONTAINER
+except AttributeError:
+    raise ImproperlyConfigured, "CLOUDFILES_USERNAME, CLOUDFILES_API_KEY, and CLOUDFILES_CONTAINER must be supplied in settings.py."
+
+# TODO: implement TTL into cloudfiles methods
+CLOUDFILES_TTL = getattr(settings, 'CLOUDFILES_TTL', 600)
+
+
+def cloudfiles_upload_to(self, filename):
+    """
+    Simple, custom upload_to because Cloud Files doesn't support
+    nested containers (directories).
+    
+    Actually found this out from @minter:
+    @richleland The Cloud Files APIs do support pseudo-subdirectories, by 
+    creating zero-byte files with type application/directory.
+    
+    May implement in a future version.
+    """
+    return get_valid_filename(filename)
+
+
+class CloudFilesStorage(Storage):
+    """
+    Custom storage for Mosso Cloud Files.
+    """
+    
+    def __init__(self):
+        """
+        Here we set up the connection and select the user-supplied container.
+        If the container isn't public (available on Limelight CDN), we make
+        it a publicly available container.
+        """
+        self.connection = cloudfiles.get_connection(CLOUDFILES_USERNAME,
+                                                    CLOUDFILES_API_KEY)
+        self.container = self.connection.get_container(CLOUDFILES_CONTAINER)
+        if not self.container.is_public():
+            self.container.make_public()
+    
+    def _get_cloud_obj(self, name):
+        """
+        Helper function to get retrieve the requested Cloud Files Object.
+        """
+        return self.container.get_object(name)
+
+    def _open(self, name, mode='rb'):
+        """
+        Not sure if this is the proper way to execute this. Would love input.
+        """
+        return File(self._get_cloud_obj(name).read())
+
+    def _save(self, name, content):
+        """
+        Here we're opening the content object and saving it to the Cloud Files
+        service. We have to set the content_type so it's delivered properly
+        when requested via public URI.
+        """
+        content.open()
+        if hasattr(content, 'chunks'):
+            content_str = ''.join(chunk for chunk in content.chunks())
+        else:
+            content_str = content.read()
+        cloud_obj = self.container.create_object(name)
+        cloud_obj.content_type = content.content_type
+        cloud_obj.send(content_str)
+        content.close()
+        return name
+    
+    def delete(self, name):
+        """
+        Deletes the specified file from the storage system.
+        """
+        self.container.delete_object(name)
+
+    def exists(self, name):
+        """
+        Returns True if a file referened by the given name already exists in the
+        storage system, or False if the name is available for a new file.
+        """
+        try:
+            self._get_cloud_obj(name)
+            return True
+        except NoSuchObject:
+            return False
+        
+    def listdir(self, path):
+        """
+        Lists the contents of the specified path, returning a 2-tuple of lists;
+        the first item being directories, the second item being files.
+        """
+        return ([], self.container.list_objects(path=path))
+
+    def size(self, name):
+        """
+        Returns the total size, in bytes, of the file specified by name.
+        """
+        return self._get_cloud_obj(name).size()
+
+    def url(self, name):
+        """
+        Returns an absolute URL where the file's contents can be accessed
+        directly by a web browser.
+        """
+        return self._get_cloud_obj(name).public_uri()

File examples/cloudfiles_project/__init__.py

Empty file added.

File examples/cloudfiles_project/manage.py

+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)

File examples/cloudfiles_project/photos/__init__.py

Empty file added.

File examples/cloudfiles_project/photos/admin.py

+from django.contrib import admin
+from cloudfiles_project.photos.models import Photo
+
+admin.site.register(Photo)

File examples/cloudfiles_project/photos/models.py

+from django.db import models
+from backends.mosso import cloudfiles_upload_to
+
+class Photo(models.Model):
+    title = models.CharField(max_length=50)
+    image = models.ImageField(upload_to=cloudfiles_upload_to)
+    
+    def __unicode__(self):
+        return self.title

File examples/cloudfiles_project/settings.py

+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+CUMULUS_USERNAME = 'yourusername'
+CUMULUS_API_KEY = 'yourapikey'
+CUMULUS_CONTAINER = 'test-container'
+CUMULUS_TTL = 600
+DEFAULT_FILE_STORAGE = 'backends.mosso.CloudFilesStorage'
+
+DATABASE_ENGINE = 'sqlite3'           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = 'local.db'             # Or path to database file if using sqlite3.
+DATABASE_USER = ''             # Not used with sqlite3.
+DATABASE_PASSWORD = ''         # Not used with sqlite3.
+DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/New_York'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/media/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/admin/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '5-7e5&o20#&@&h8t)7%n4a@)y7s3(jnv)qdd_azqzmo826d_u@'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'cloudfiles_project.urls'
+
+TEMPLATE_DIRS = (
+    'templates',
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.admin',
+    'cloudfiles_project.photos',
+)

File examples/cloudfiles_project/templates/base.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+	<title>django-cumulus example project</title>
+</head>
+
+<body>
+{% block content %}{% endblock %}
+
+</body>
+</html>

File examples/cloudfiles_project/templates/photos/photo_list.html

+{% extends "base.html" %}
+
+{% block content %}
+<h1>Photos in database</h1>
+{% if object_list %}
+<ul>{% spaceless %}
+    {% for object in object_list %}
+    <li>{{ object.image.url }}</li>
+    {% endfor %}
+{% endspaceless %}</ul>
+{% else %}
+<p>No photos exist.</p>
+{% endif %}
+{% endblock %}

File examples/cloudfiles_project/urls.py

+from django.conf.urls.defaults import *
+from django.conf import settings
+from photos.models import Photo
+
+photo_dict = {
+    'queryset': Photo.objects.all()
+}
+
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+    (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+    (r'^admin/(.*)', admin.site.root),
+    (r'^photos/$', 'django.views.generic.list_detail.object_list', photo_dict),
+)
+
+if settings.DEBUG:
+    urlpatterns += patterns('',
+        (r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
+    )