Skip to content
This repository has been archived by the owner on Nov 25, 2017. It is now read-only.

Commit

Permalink
Sketched out a converter registry for model -> storymarket conversion.
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobian committed Jul 14, 2010
1 parent 6e80321 commit fa817e5
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 11 deletions.
8 changes: 6 additions & 2 deletions django_storymarket/admin.py
Expand Up @@ -10,6 +10,7 @@

import storymarket

from . import converters
from .models import SyncedObject

def attrs(**kwargs):
Expand Down Expand Up @@ -37,6 +38,9 @@ def __init__(self, model, admin_site):
self.list_display = list_display

self.storymarket = storymarket.Storymarket(settings.STORYMARKET_API_KEY)

# FIXME: this should be elsewhere.
converters.autodiscover()

@attrs(short_description='Upload selected %(verbose_name_plural)s to Storymarket')
def upload_to_storymarket(self, request, queryset):
Expand All @@ -46,7 +50,7 @@ def upload_to_storymarket(self, request, queryset):
# The user has confirumed the uploading.
num_uploaded = 0
for obj in queryset:
sm_data = self.to_storymarket(obj)
sm_data = converters.convert(self.storymarket, obj)
sm_type = sm_data.pop('type')
manager = getattr(self.storymarket, sm_type)
sm_obj = manager.create(sm_data)
Expand All @@ -61,7 +65,7 @@ def upload_to_storymarket(self, request, queryset):
# Generate a list of converted objects to "preview" as an upload.
# These is a list-of-dicts for template convienience
previewed_objects = [
{'object': obj, 'preview': self.to_storymarket(obj)}
{'object': obj, 'preview': converters.convert(self.storymarket, obj)}
for obj in queryset
]

Expand Down
127 changes: 127 additions & 0 deletions django_storymarket/converters.py
@@ -0,0 +1,127 @@
"""
Converters are the glue that converts a Django model into a dict of data to be
posted to Storymarket. This module implements a registry for them.
Converter functions are simple functions that take two arguments -- the
Storymarket API object and the model instance to convert -- and return
a dictionary of data to be used for the upload. This dict should be in
the simplified format accepted by the Storymarket API, and should have
one extra key: the type of data to be uploaded.
For example::
def story_to_storymarket(api, obj):
return {
"type": "text",
"title": obj.headline,
"author": "jacobian",
"org": api.orgs.get(MY_ORG_ID),
"category": api.subcategories.get(SOME_CATEGORY_ID),
"content": obj.body
}
For binary types, the returned dict should have a ``blob`` key; the value can
either be the binary data as a string or (more likely) a file-like object::
def image_to_storymarket(api, obj):
return {
"type": "photos",
...
"blob": obj.image_field.open(),
}
"""

from django.conf import settings
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule

_registry = {}
_FALLBACK_KEY = '*'
_CONVERTER_MODULE_NAME = 'storymarket_converters'

def convert(api, instance):
"""
Convert a model instance using its registered converter.
If it fails, this'll raise :exc:`CannotConvert`.
:param api: The storymarket API instance.
:param instance: The model instance to convert.
:rtype: dict
"""
registry_key = str(instance._meta)

# I'm using look-before-you-leap instead of better-to-ask-for-permission
# here because a try/except might accidentally catch a KeyError raised
# by the converter itself.
if registry_key in _registry:
return _registry[registry_key](api, instance)
elif _FALLBACK_KEY in _registry:
return _registry[_FALLBACK_KEY](api, instance)
else:
raise CannotConvert("Can't convert %s objects." % instance._meta)

class CannotConvert(Exception):
pass

def register(model, callback):
"""
Register a converter.
:param model: The model class to register the converter for.
:param callback: The conversion function.
"""
_registry[str(model._meta)] = callback

def register_fallback_converter(callback):
"""
Register a fallback converter to be used if a model-specific converter
doesn't already exist.
This converter should either convert an object or raise :exc:`CannotConvert`.
:param callback: The conversion function.
"""
_registry[_FALLBACK_KEY] = callback

def unregister(model):
"""
Remove a converter for a model, if registered.
:param model: The model class to unregister.
"""
try:
del _registry[str(model._meta)]
except KeyError:
pass

def unregister_fallback_converter():
"""
Remove a fallback converter, if registered.
"""
try:
del _registry[_FALLBACK_KEY]
except KeyError:
pass

_discovery_done = False
def autodiscover():
"""
Auto-discover converter modules from settings.INSTALLED_APPS.
This code is cribbed from admin.autodiscover.
"""
global _discovery_done
if _discovery_done: return

for app in settings.INSTALLED_APPS:
mod = import_module(app)
try:
import_module("%s.%s" % (app, _CONVERTER_MODULE_NAME))
except:
# Ignore the error if the app just doesn't have a converter
# module, but bubble it up if it was anything else.
if module_has_submodule(mod, _CONVERTER_MODULE_NAME):
raise

_discovery_done = True
10 changes: 1 addition & 9 deletions example/admin.py
Expand Up @@ -5,14 +5,6 @@
from .models import ExampleStory

class ExampleStoryAdmin(StorymarketAdmin, admin.ModelAdmin):
def to_storymarket(self, obj):
return {
"type": "text",
"title": obj.headline,
"author": "jacobian",
"org": self.storymarket.orgs.get(12),
"category": self.storymarket.subcategories.get(12),
"content": obj.body
}
pass

admin.site.register(ExampleStory, ExampleStoryAdmin)
16 changes: 16 additions & 0 deletions example/storymarket_converters.py
@@ -0,0 +1,16 @@
from __future__ import absolute_import

from .models import ExampleStory
import django_storymarket.converters

def story_to_storymarket(api, obj):
return {
"type": "text",
"title": obj.headline,
"author": "jacobian",
"org": api.orgs.get(12),
"category": api.subcategories.get(12),
"content": obj.body
}

django_storymarket.converters.register(ExampleStory, story_to_storymarket)

0 comments on commit fa817e5

Please sign in to comment.