This repository has been archived by the owner on Jan 18, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add optional support for object set resources and endpoints
This uses a settings-based approach to defining which object set models to define resources and endpoints for. In addition to the functionality provided by django-objectset directly, resources support creating sets based on a DataContext literal, an existing instance or the user's current session. Requires django-objectset 0.2.3+
- Loading branch information
Showing
13 changed files
with
255 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
from django import forms | ||
from django.db.models import get_model | ||
from django.conf.urls import url, patterns | ||
from django.core.urlresolvers import reverse | ||
from django.core.exceptions import ImproperlyConfigured | ||
from objectset import resources | ||
from objectset.models import ObjectSet | ||
from objectset.forms import objectset_form_factory | ||
from serrano.conf import settings | ||
from .base import BaseResource | ||
|
||
|
||
URL_REVERSE_NAME = 'serrano:sets:{0}' | ||
|
||
|
||
def configure_object_set(config): | ||
model_label = config['model'] | ||
app_name, model_name = model_label.split('.', 1) | ||
model_name = model_name.lower() | ||
|
||
model = get_model(app_name, model_name) | ||
|
||
if not issubclass(model, ObjectSet): | ||
raise ImproperlyConfigured('Only models that subclass ObjectSet ' | ||
'are supported, not {0}' | ||
.format(model_label)) | ||
|
||
default_name = unicode(model._meta.verbose_name_plural) | ||
name = config.get('name', default_name.lower().replace(' ', '')) | ||
label = config.get('label', default_name.title()) | ||
|
||
options = { | ||
'label': label, | ||
} | ||
|
||
url_names = { | ||
'sets': model_name, | ||
'set': model_name, | ||
'objects': '{0}-objects'.format(model_name), | ||
} | ||
|
||
url_reverse_names = { | ||
'sets': URL_REVERSE_NAME.format(model_name), | ||
'set': URL_REVERSE_NAME.format(model_name), | ||
'objects': URL_REVERSE_NAME.format('{0}-objects'.format(model_name)), | ||
} | ||
|
||
class ObjectSetForm(objectset_form_factory(model)): | ||
context = forms.Field(required=False) | ||
|
||
def __init__(self, *args, **kwargs): | ||
super(ObjectSetForm, self).__init__(*args, **kwargs) | ||
|
||
def clean_context(self): | ||
self._context_applied = False | ||
# Extract is from the request data. See | ||
# ``serrano.resources.base.get_request_context`` for parsing | ||
# details | ||
context = self.resource.get_context(self.request) | ||
return context | ||
|
||
def save(self, commit=True): | ||
instance = super(ObjectSetForm, self).save(commit=False) | ||
|
||
# Prevents reapplying the context to the pending objects | ||
if not self._context_applied: | ||
self._context_applied = True | ||
context = self.cleaned_data.get('context') | ||
if context: | ||
instance._pending |= context.apply() | ||
|
||
if commit: | ||
instance.save() | ||
self.save_m2m() | ||
|
||
return instance | ||
|
||
bases = (resources.BaseSetResource, BaseResource) | ||
|
||
BaseSetResource = type('BaseSetResource', bases, { | ||
'model': model, | ||
'form_class': ObjectSetForm, | ||
'url_names': url_names, | ||
'url_reverse_names': url_reverse_names, | ||
'user_support': config.get('user_support'), | ||
'session_support': config.get('session_support'), | ||
}) | ||
|
||
options['url_reverse_names'] = url_reverse_names | ||
options['url_patterns'] = resources.get_url_patterns(model, { | ||
'base': BaseSetResource | ||
}, prefix=name) | ||
|
||
return options | ||
|
||
|
||
urlpatterns = patterns('') | ||
object_set_options = [] | ||
|
||
for config in settings.OBJECT_SETS: | ||
options = configure_object_set(config) | ||
object_set_options.append(options) | ||
urlpatterns += options['url_patterns'] | ||
|
||
|
||
class SetsRootResource(BaseResource): | ||
object_set_options = tuple(object_set_options) | ||
|
||
def get(self, request): | ||
uri = request.build_absolute_uri | ||
data = [] | ||
|
||
for options in self.object_set_options: | ||
reverses = options['url_reverse_names'] | ||
|
||
data.append({ | ||
'label': options['label'], | ||
'_links': { | ||
'self': uri(reverse(reverses['sets'])), | ||
} | ||
}) | ||
|
||
return data | ||
|
||
|
||
sets_root_resource = SetsRootResource() | ||
|
||
urlpatterns += patterns('', url(r'^$', sets_root_resource, name='root')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,53 @@ | ||
from django.conf.urls import patterns, url, include | ||
from serrano.conf import dep_supported | ||
|
||
|
||
urlpatterns = patterns( | ||
# Patterns for the data namespace | ||
data_patterns = patterns( | ||
'', | ||
|
||
url(r'^export/', include('serrano.resources.exporter')), | ||
|
||
url(r'^preview/', include('serrano.resources.preview')), | ||
) | ||
|
||
# Patterns for the serrano namespace | ||
serrano_patterns = patterns( | ||
'', | ||
url(r'', include(patterns('', | ||
url(r'^$', | ||
include('serrano.resources')), | ||
|
||
url(r'^categories/', | ||
include('serrano.resources.category')), | ||
url(r'^$', | ||
include('serrano.resources')), | ||
|
||
url(r'^fields/', | ||
include('serrano.resources.field')), | ||
url(r'^categories/', | ||
include('serrano.resources.category')), | ||
|
||
url(r'^concepts/', | ||
include('serrano.resources.concept')), | ||
url(r'^fields/', | ||
include('serrano.resources.field')), | ||
|
||
url(r'^contexts/', | ||
include('serrano.resources.context', namespace='contexts')), | ||
url(r'^concepts/', | ||
include('serrano.resources.concept')), | ||
|
||
url(r'^queries/', | ||
include('serrano.resources.query', namespace='queries')), | ||
url(r'^contexts/', | ||
include('serrano.resources.context', namespace='contexts')), | ||
|
||
url(r'^views/', | ||
include('serrano.resources.view', namespace='views')), | ||
url(r'^queries/', | ||
include('serrano.resources.query', namespace='queries')), | ||
|
||
url(r'^data/', include(patterns( | ||
'', | ||
url(r'^export/', include('serrano.resources.exporter')), | ||
url(r'^preview/', include('serrano.resources.preview')), | ||
), namespace='data')), | ||
url(r'^views/', | ||
include('serrano.resources.view', namespace='views')), | ||
|
||
), namespace='serrano')), | ||
url(r'^data/', include(data_patterns, namespace='data')), | ||
) | ||
|
||
if dep_supported('objectset'): | ||
# Patterns for the 'sets' namespace | ||
serrano_patterns += patterns( | ||
'', | ||
url(r'^sets/', include('serrano.resources.sets', namespace='sets')) | ||
) | ||
|
||
# Exported patterns | ||
urlpatterns = patterns( | ||
'', | ||
url(r'^', include(serrano_patterns, namespace='serrano')) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import json | ||
from django.test import TestCase | ||
from django.core import management | ||
from ...models import Team, Employee | ||
|
||
|
||
class SetResourcesTest(TestCase): | ||
fixtures = ['test_data.json'] | ||
|
||
def setUp(self): | ||
management.call_command('avocado', 'init', 'tests', quiet=True) | ||
|
||
def test_root(self): | ||
response = self.client.get('/api/sets/', | ||
HTTP_ACCEPT='application/json') | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual(len(json.loads(response.content)), 1) | ||
|
||
def test_type(self): | ||
response = self.client.get('/api/sets/teams/', | ||
HTTP_ACCEPT='application/json') | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual(len(json.loads(response.content)), 0) | ||
|
||
def test_type_instance(self): | ||
Team(Employee.objects.all(), save=True) | ||
response = self.client.get('/api/sets/teams/', | ||
HTTP_ACCEPT='application/json') | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual(len(json.loads(response.content)), 1) | ||
|
||
def test_context(self): | ||
response = self.client.post('/api/sets/teams/', json.dumps({ | ||
'context': { | ||
'field': 'tests.title.salary', | ||
'operator': 'gt', | ||
'value': 15000, | ||
} | ||
}), content_type='application/json', | ||
HTTP_ACCEPT='application/json') | ||
|
||
self.assertEqual(Employee.objects.filter(title__salary__gt=15000) | ||
.count(), 2) | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual(json.loads(response.content)['count'], 2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters