Commit d373b3a0 by Tom Christie

Decouple views and resources

parent 8756664e
"""The :mod:`authentication` modules provides for pluggable authentication behaviour.
Authentication behaviour is provided by adding the mixin class :class:`AuthenticatorMixin` to a :class:`.Resource` or Django :class:`View` class.
Authentication behaviour is provided by adding the mixin class :class:`AuthenticatorMixin` to a :class:`.BaseView` or Django :class:`View` class.
The set of authentication which are use is then specified by setting the :attr:`authentication` attribute on the class, and listing a set of authentication classes.
"""
......@@ -25,10 +25,10 @@ class BaseAuthenticator(object):
be some more complicated token, for example authentication tokens which are signed
against a particular set of permissions for a given user, over a given timeframe.
The default permission checking on Resource will use the allowed_methods attribute
The default permission checking on View will use the allowed_methods attribute
for permissions if the authentication context is not None, and use anon_allowed_methods otherwise.
The authentication context is available to the method calls eg Resource.get(request)
The authentication context is available to the method calls eg View.get(request)
by accessing self.auth in order to allow them to apply any more fine grained permission
checking at the point the response is being generated.
......
""""""
from djangorestframework.utils.mediatypes import MediaType
from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX
from djangorestframework.response import ErrorResponse
......@@ -12,6 +13,14 @@ from decimal import Decimal
import re
__all__ = ['RequestMixin',
'ResponseMixin',
'AuthMixin',
'ReadModelMixin',
'CreateModelMixin',
'UpdateModelMixin',
'DeleteModelMixin',
'ListModelMixin']
########## Request Mixin ##########
......@@ -250,7 +259,7 @@ class RequestMixin(object):
########## ResponseMixin ##########
class ResponseMixin(object):
"""Adds behaviour for pluggable Renderers to a :class:`.Resource` or Django :class:`View`. class.
"""Adds behaviour for pluggable Renderers to a :class:`.BaseView` or Django :class:`View`. class.
Default behaviour is to use standard HTTP Accept header content negotiation.
Also supports overidding the content type by specifying an _accept= parameter in the URL.
......@@ -259,32 +268,8 @@ class ResponseMixin(object):
ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
REWRITE_IE_ACCEPT_HEADER = True
#request = None
#response = None
renderers = ()
#def render_to_response(self, obj):
# if isinstance(obj, Response):
# response = obj
# elif response_obj is not None:
# response = Response(status.HTTP_200_OK, obj)
# else:
# response = Response(status.HTTP_204_NO_CONTENT)
# response.cleaned_content = self._filter(response.raw_content)
# self._render(response)
#def filter(self, content):
# """
# Filter the response content.
# """
# for filterer_cls in self.filterers:
# filterer = filterer_cls(self)
# content = filterer.filter(content)
# return content
def render(self, response):
"""Takes a :class:`Response` object and returns a Django :class:`HttpResponse`."""
......@@ -318,7 +303,7 @@ class ResponseMixin(object):
def _determine_renderer(self, request):
"""Return the appropriate renderer for the output, given the client's 'Accept' header,
and the content types that this Resource knows how to serve.
and the content types that this mixin knows how to serve.
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html"""
......@@ -415,17 +400,6 @@ class AuthMixin(object):
return auth
return None
# TODO?
#@property
#def user(self):
# if not has_attr(self, '_user'):
# auth = self.auth
# if isinstance(auth, User...):
# self._user = auth
# else:
# self._user = getattr(auth, 'user', None)
# return self._user
def check_permissions(self):
if not self.permissions:
return
......@@ -443,14 +417,15 @@ class AuthMixin(object):
class ReadModelMixin(object):
"""Behaviour to read a model instance on GET requests"""
def get(self, request, *args, **kwargs):
model = self.resource.model
try:
if args:
# If we have any none kwargs then assume the last represents the primrary key
instance = self.model.objects.get(pk=args[-1], **kwargs)
instance = model.objects.get(pk=args[-1], **kwargs)
else:
# Otherwise assume the kwargs uniquely identify the model
instance = self.model.objects.get(**kwargs)
except self.model.DoesNotExist:
instance = model.objects.get(**kwargs)
except model.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
return instance
......@@ -459,17 +434,18 @@ class ReadModelMixin(object):
class CreateModelMixin(object):
"""Behaviour to create a model instance on POST requests"""
def post(self, request, *args, **kwargs):
model = self.resource.model
# translated 'related_field' kwargs into 'related_field_id'
for related_name in [field.name for field in self.model._meta.fields if isinstance(field, RelatedField)]:
for related_name in [field.name for field in model._meta.fields if isinstance(field, RelatedField)]:
if kwargs.has_key(related_name):
kwargs[related_name + '_id'] = kwargs[related_name]
del kwargs[related_name]
all_kw_args = dict(self.CONTENT.items() + kwargs.items())
if args:
instance = self.model(pk=args[-1], **all_kw_args)
instance = model(pk=args[-1], **all_kw_args)
else:
instance = self.model(**all_kw_args)
instance = model(**all_kw_args)
instance.save()
headers = {}
if hasattr(instance, 'get_absolute_url'):
......@@ -480,19 +456,20 @@ class CreateModelMixin(object):
class UpdateModelMixin(object):
"""Behaviour to update a model instance on PUT requests"""
def put(self, request, *args, **kwargs):
model = self.resource.model
# TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url
try:
if args:
# If we have any none kwargs then assume the last represents the primrary key
instance = self.model.objects.get(pk=args[-1], **kwargs)
instance = model.objects.get(pk=args[-1], **kwargs)
else:
# Otherwise assume the kwargs uniquely identify the model
instance = self.model.objects.get(**kwargs)
instance = model.objects.get(**kwargs)
for (key, val) in self.CONTENT.items():
setattr(instance, key, val)
except self.model.DoesNotExist:
instance = self.model(**self.CONTENT)
except model.DoesNotExist:
instance = model(**self.CONTENT)
instance.save()
instance.save()
......@@ -502,14 +479,15 @@ class UpdateModelMixin(object):
class DeleteModelMixin(object):
"""Behaviour to delete a model instance on DELETE requests"""
def delete(self, request, *args, **kwargs):
model = self.resource.model
try:
if args:
# If we have any none kwargs then assume the last represents the primrary key
instance = self.model.objects.get(pk=args[-1], **kwargs)
instance = model.objects.get(pk=args[-1], **kwargs)
else:
# Otherwise assume the kwargs uniquely identify the model
instance = self.model.objects.get(**kwargs)
except self.model.DoesNotExist:
instance = model.objects.get(**kwargs)
except model.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
instance.delete()
......
......@@ -11,6 +11,12 @@ class BasePermission(object):
def has_permission(self, auth):
return True
class FullAnonAccess(BasePermission):
""""""
def has_permission(self, auth):
return True
class IsAuthenticated(BasePermission):
""""""
def has_permission(self, auth):
......
"""Renderers are used to serialize a Resource's output into specific media types.
"""Renderers are used to serialize a View's output into specific media types.
django-rest-framework also provides HTML and PlainText renderers that help self-document the API,
by serializing the output along with documentation regarding the Resource, output status and headers,
and providing forms and links depending on the allowed methods, renderers and parsers on the Resource.
......
......@@ -21,6 +21,6 @@ class Response(object):
class ErrorResponse(BaseException):
"""An exception representing an HttpResponse that should be returned immediatley."""
"""An exception representing an HttpResponse that should be returned immediately."""
def __init__(self, status, content=None, headers={}):
self.response = Response(status, content=content, headers=headers)
......@@ -18,7 +18,7 @@
<div id="content" class="colM">
<div id="content-main">
<form method="post" action="{% url djangorestframework.views.api_login %}" id="login-form">
<form method="post" action="{% url djangorestframework.utils.staticviews.api_login %}" id="login-form">
{% csrf_token %}
<div class="form-row">
<label for="id_username">Username:</label> {{ form.username }}
......
from django.test import TestCase
from djangorestframework.compat import RequestFactory
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
# See: http://www.useragentstring.com/
......@@ -19,15 +19,15 @@ class UserAgentMungingTest(TestCase):
def setUp(self):
class MockResource(Resource):
class MockView(BaseView):
permissions = ()
def get(self, request):
return {'a':1, 'b':2, 'c':3}
self.req = RequestFactory()
self.MockResource = MockResource
self.view = MockResource.as_view()
self.MockView = MockView
self.view = MockView.as_view()
def test_munge_msie_accept_header(self):
"""Send MSIE user agent strings and ensure that we get an HTML response,
......@@ -42,7 +42,7 @@ class UserAgentMungingTest(TestCase):
def test_dont_rewrite_msie_accept_header(self):
"""Turn off REWRITE_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure
that we get a JSON response if we set a */* accept header."""
view = self.MockResource.as_view(REWRITE_IE_ACCEPT_HEADER=False)
view = self.MockView.as_view(REWRITE_IE_ACCEPT_HEADER=False)
for user_agent in (MSIE_9_USER_AGENT,
MSIE_8_USER_AGENT,
......
......@@ -6,19 +6,19 @@ from django.test import Client, TestCase
from django.utils import simplejson as json
from djangorestframework.compat import RequestFactory
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
from djangorestframework import permissions
import base64
class MockResource(Resource):
class MockView(BaseView):
permissions = ( permissions.IsAuthenticated, )
def post(self, request):
return {'a':1, 'b':2, 'c':3}
urlpatterns = patterns('',
(r'^$', MockResource.as_view()),
(r'^$', MockView.as_view()),
)
......
from django.conf.urls.defaults import patterns, url
from django.test import TestCase
from djangorestframework.utils.breadcrumbs import get_breadcrumbs
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
class Root(Resource):
class Root(BaseView):
pass
class ResourceRoot(Resource):
class ResourceRoot(BaseView):
pass
class ResourceInstance(Resource):
class ResourceInstance(BaseView):
pass
class NestedResourceRoot(Resource):
class NestedResourceRoot(BaseView):
pass
class NestedResourceInstance(Resource):
class NestedResourceInstance(BaseView):
pass
urlpatterns = patterns('',
......
from django.test import TestCase
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
from djangorestframework.compat import apply_markdown
from djangorestframework.utils.description import get_name, get_description
......@@ -32,23 +32,23 @@ MARKED_DOWN = """<h2>an example docstring</h2>
<h2 id="hash_style_header">hash style header</h2>"""
class TestResourceNamesAndDescriptions(TestCase):
class TestViewNamesAndDescriptions(TestCase):
def test_resource_name_uses_classname_by_default(self):
"""Ensure Resource names are based on the classname by default."""
class MockResource(Resource):
class MockView(BaseView):
pass
self.assertEquals(get_name(MockResource()), 'Mock Resource')
self.assertEquals(get_name(MockView()), 'Mock View')
def test_resource_name_can_be_set_explicitly(self):
"""Ensure Resource names can be set using the 'name' class attribute."""
example = 'Some Other Name'
class MockResource(Resource):
class MockView(BaseView):
name = example
self.assertEquals(get_name(MockResource()), example)
self.assertEquals(get_name(MockView()), example)
def test_resource_description_uses_docstring_by_default(self):
"""Ensure Resource names are based on the docstring by default."""
class MockResource(Resource):
class MockView(BaseView):
"""an example docstring
====================
......@@ -64,28 +64,28 @@ class TestResourceNamesAndDescriptions(TestCase):
# hash style header #"""
self.assertEquals(get_description(MockResource()), DESCRIPTION)
self.assertEquals(get_description(MockView()), DESCRIPTION)
def test_resource_description_can_be_set_explicitly(self):
"""Ensure Resource descriptions can be set using the 'description' class attribute."""
example = 'Some other description'
class MockResource(Resource):
class MockView(BaseView):
"""docstring"""
description = example
self.assertEquals(get_description(MockResource()), example)
self.assertEquals(get_description(MockView()), example)
def test_resource_description_does_not_require_docstring(self):
"""Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute."""
example = 'Some other description'
class MockResource(Resource):
class MockView(BaseView):
description = example
self.assertEquals(get_description(MockResource()), example)
self.assertEquals(get_description(MockView()), example)
def test_resource_description_can_be_empty(self):
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string"""
class MockResource(Resource):
class MockView(BaseView):
pass
self.assertEquals(get_description(MockResource()), '')
self.assertEquals(get_description(MockView()), '')
def test_markdown(self):
"""Ensure markdown to HTML works as expected"""
......
from django.test import TestCase
from django import forms
from djangorestframework.compat import RequestFactory
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
import StringIO
class UploadFilesTests(TestCase):
......@@ -15,7 +15,7 @@ class UploadFilesTests(TestCase):
class FileForm(forms.Form):
file = forms.FileField
class MockResource(Resource):
class MockView(BaseView):
permissions = ()
form = FileForm
......@@ -26,7 +26,7 @@ class UploadFilesTests(TestCase):
file = StringIO.StringIO('stuff')
file.name = 'stuff.txt'
request = self.factory.post('/', {'file': file})
view = MockResource.as_view()
view = MockView.as_view()
response = view(request)
self.assertEquals(response.content, '{"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"}')
......
......@@ -2,12 +2,12 @@
..
>>> from djangorestframework.parsers import FormParser
>>> from djangorestframework.compat import RequestFactory
>>> from djangorestframework.resource import Resource
>>> from djangorestframework.views import BaseView
>>> from StringIO import StringIO
>>> from urllib import urlencode
>>> req = RequestFactory().get('/')
>>> some_resource = Resource()
>>> some_resource.request = req # Make as if this request had been dispatched
>>> some_view = BaseView()
>>> some_view.request = req # Make as if this request had been dispatched
FormParser
============
......@@ -24,7 +24,7 @@ Here is some example data, which would eventually be sent along with a post requ
Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter :
>>> FormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': 'blo1'}
>>> FormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': 'blo1'}
True
However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` :
......@@ -36,7 +36,7 @@ However, you can customize this behaviour by subclassing :class:`parsers.FormPar
This new parser only flattens the lists of parameters that contain a single value.
>>> MyFormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
True
.. note:: The same functionality is available for :class:`parsers.MultipartParser`.
......@@ -61,7 +61,7 @@ The browsers usually strip the parameter completely. A hack to avoid this, and t
:class:`parsers.FormParser` strips the values ``_empty`` from all the lists.
>>> MyFormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'blo1'}
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1'}
True
Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it.
......@@ -71,7 +71,7 @@ Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a lis
... def is_a_list(self, key, val_list):
... return key == 'key2'
...
>>> MyFormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'blo1', 'key2': []}
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1', 'key2': []}
True
Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`.
......@@ -81,7 +81,7 @@ from tempfile import TemporaryFile
from django.test import TestCase
from djangorestframework.compat import RequestFactory
from djangorestframework.parsers import MultipartParser
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
from djangorestframework.utils.mediatypes import MediaType
from StringIO import StringIO
......@@ -122,9 +122,9 @@ class TestMultipartParser(TestCase):
def test_multipartparser(self):
"""Ensure that MultipartParser can parse multipart/form-data that contains a mix of several files and parameters."""
post_req = RequestFactory().post('/', self.body, content_type=self.content_type)
resource = Resource()
resource.request = post_req
parsed = MultipartParser(resource).parse(StringIO(self.body))
view = BaseView()
view.request = post_req
parsed = MultipartParser(view).parse(StringIO(self.body))
self.assertEqual(parsed['key1'], 'val1')
self.assertEqual(parsed.FILES['file1'].read(), 'blablabla')
......@@ -3,10 +3,10 @@ from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils import simplejson as json
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
class MockResource(Resource):
class MockView(BaseView):
"""Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified"""
permissions = ()
......@@ -14,8 +14,8 @@ class MockResource(Resource):
return reverse('another')
urlpatterns = patterns('',
url(r'^$', MockResource.as_view()),
url(r'^another$', MockResource.as_view(), name='another'),
url(r'^$', MockView.as_view()),
url(r'^another$', MockView.as_view(), name='another'),
)
......
......@@ -3,11 +3,11 @@ from django.test import TestCase
from django.utils import simplejson as json
from djangorestframework.compat import RequestFactory
from djangorestframework.resource import Resource
from djangorestframework.views import BaseView
from djangorestframework.permissions import Throttling
class MockResource(Resource):
class MockView(BaseView):
permissions = ( Throttling, )
throttle = (3, 1) # 3 requests per second
......@@ -15,7 +15,7 @@ class MockResource(Resource):
return 'foo'
urlpatterns = patterns('',
(r'^$', MockResource.as_view()),
(r'^$', MockView.as_view()),
)
......
......@@ -4,6 +4,8 @@ from django.test import TestCase
from djangorestframework.compat import RequestFactory
from djangorestframework.validators import BaseValidator, FormValidator, ModelFormValidator
from djangorestframework.response import ErrorResponse
from djangorestframework.views import BaseView
from djangorestframework.resource import Resource
class TestValidatorMixinInterfaces(TestCase):
......@@ -20,7 +22,7 @@ class TestDisabledValidations(TestCase):
def test_disabled_form_validator_returns_content_unchanged(self):
"""If the view's form attribute is None then FormValidator(view).validate(content)
should just return the content unmodified."""
class DisabledFormView(object):
class DisabledFormView(BaseView):
form = None
view = DisabledFormView()
......@@ -30,7 +32,7 @@ class TestDisabledValidations(TestCase):
def test_disabled_form_validator_get_bound_form_returns_none(self):
"""If the view's form attribute is None on then
FormValidator(view).get_bound_form(content) should just return None."""
class DisabledFormView(object):
class DisabledFormView(BaseView):
form = None
view = DisabledFormView()
......@@ -39,11 +41,10 @@ class TestDisabledValidations(TestCase):
def test_disabled_model_form_validator_returns_content_unchanged(self):
"""If the view's form and model attributes are None then
"""If the view's form is None and does not have a Resource with a model set then
ModelFormValidator(view).validate(content) should just return the content unmodified."""
class DisabledModelFormView(object):
class DisabledModelFormView(BaseView):
form = None
model = None
view = DisabledModelFormView()
content = {'qwerty':'uiop'}
......@@ -51,13 +52,12 @@ class TestDisabledValidations(TestCase):
def test_disabled_model_form_validator_get_bound_form_returns_none(self):
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
class DisabledModelFormView(object):
form = None
class DisabledModelFormView(BaseView):
model = None
view = DisabledModelFormView()
content = {'qwerty':'uiop'}
self.assertEqual(ModelFormValidator(view).get_bound_form(content), None)#
self.assertEqual(ModelFormValidator(view).get_bound_form(content), None)
class TestNonFieldErrors(TestCase):
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
......@@ -84,7 +84,7 @@ class TestNonFieldErrors(TestCase):
except ErrorResponse, exc:
self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
else:
self.fail('ResourceException was not raised') #pragma: no cover
self.fail('ErrorResponse was not raised') #pragma: no cover
class TestFormValidation(TestCase):
......@@ -95,11 +95,11 @@ class TestFormValidation(TestCase):
class MockForm(forms.Form):
qwerty = forms.CharField(required=True)
class MockFormView(object):
class MockFormView(BaseView):
form = MockForm
validators = (FormValidator,)
class MockModelFormView(object):
class MockModelFormView(BaseView):
form = MockForm
validators = (ModelFormValidator,)
......@@ -264,9 +264,12 @@ class TestModelFormValidator(TestCase):
@property
def readonly(self):
return 'read only'
class MockView(object):
class MockResource(Resource):
model = MockModel
class MockView(BaseView):
resource = MockResource
self.validator = ModelFormValidator(MockView)
......
......@@ -3,7 +3,7 @@ from django.test import TestCase
from django.test import Client
urlpatterns = patterns('djangorestframework.views',
urlpatterns = patterns('djangorestframework.utils.staticviews',
url(r'^robots.txt$', 'deny_robots'),
url(r'^favicon.ico$', 'favicon'),
url(r'^accounts/login$', 'api_login'),
......
from django.conf.urls.defaults import patterns
from django.conf import settings
urlpatterns = patterns('djangorestframework.utils.staticviews',
(r'robots.txt', 'deny_robots'),
(r'^accounts/login/$', 'api_login'),
(r'^accounts/logout/$', 'api_logout'),
)
# Only serve favicon in production because otherwise chrome users will pretty much
# permanantly have the django-rest-framework favicon whenever they navigate to
# 127.0.0.1:8000 or whatever, which gets annoying
if not settings.DEBUG:
urlpatterns += patterns('djangorestframework.utils.staticviews',
(r'favicon.ico', 'favicon'),
)
\ No newline at end of file
from django.contrib.auth.views import *
from django.conf import settings
from django.http import HttpResponse
import base64
def deny_robots(request):
return HttpResponse('User-agent: *\nDisallow: /', mimetype='text/plain')
def favicon(request):
data = 'AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLy8tLy8vL3svLy1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy8vLBsvLywkAAAAATkZFS1xUVPqhn57/y8vL0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmVlQ/GxcXiy8vL88vLy4FdVlXzTkZF/2RdXP/Ly8vty8vLtMvLy5DLy8vty8vLxgAAAAAAAAAAAAAAAAAAAABORkUJTkZF4lNMS/+Lh4f/cWtq/05GRf9ORkX/Vk9O/3JtbP+Ef3//Vk9O/2ljYv/Ly8v5y8vLCQAAAAAAAAAAAAAAAE5GRQlORkX2TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/UElI/8PDw5cAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRZZORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vLvQAAAAAAAAAAAAAAAAAAAADLy8tIaWNi805GRf9ORkX/YVpZ/396eV7Ly8t7qaen9lZOTu5ORkX/TkZF/25oZ//Ly8v/y8vLycvLy0gAAAAATkZFSGNcXPpORkX/TkZF/05GRf+ysLDzTkZFe1NLSv6Oior/raur805GRf9ORkX/TkZF/2hiYf+npaX/y8vL5wAAAABORkXnTkZF/05GRf9ORkX/VU1M/8vLy/9PR0b1TkZF/1VNTP/Ly8uQT0dG+E5GRf9ORkX/TkZF/1hRUP3Ly8tmAAAAAE5GRWBORkXkTkZF/05GRf9ORkX/t7a2/355eOpORkX/TkZFkISAf1BORkX/TkZF/05GRf9XT075TkZFZgAAAAAAAAAAAAAAAAAAAABORkXDTkZF/05GRf9lX17/ubi4/8vLy/+2tbT/Yltb/05GRf9ORkX/a2Vk/8vLy5MAAAAAAAAAAAAAAAAAAAAAAAAAAFNLSqNORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vL+cvLyw8AAAAAAAAAAAAAAABORkUSTkZF+U5GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/1BJSP/CwsLmy8vLDwAAAAAAAAAAAAAAAE5GRRJORkXtTkZF9FFJSJ1ORkXJTkZF/05GRf9ORkX/ZF5d9k5GRZ9ORkXtTkZF5HFsaxUAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRQxORkUJAAAAAAAAAABORkXhTkZF/2JbWv7Ly8tgAAAAAAAAAABORkUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRWBORkX2TkZFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+AAP9/gAD+P4AA4AOAAMADgADAA4AAwAOAAMMBgACCAIAAAAGAAIBDgADAA4AAwAOAAMADgADAB4AA/H+AAP7/gAA='
return HttpResponse(base64.b64decode(data), mimetype='image/vnd.microsoft.icon')
# BLERGH
# Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS
# to add ADMIN_MEDIA_PREFIX to the RequestContext. I don't like this but really really want users to not have to
# be making settings changes in order to accomodate django-rest-framework
@csrf_protect
@never_cache
def api_login(request, template_name='api_login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm):
"""Displays the login form and handles the login action."""
redirect_to = request.REQUEST.get(redirect_field_name, '')
if request.method == "POST":
form = authentication_form(data=request.POST)
if form.is_valid():
# Light security check -- make sure redirect_to isn't garbage.
if not redirect_to or ' ' in redirect_to:
redirect_to = settings.LOGIN_REDIRECT_URL
# Heavier security check -- redirects to http://example.com should
# not be allowed, but things like /view/?param=http://example.com
# should be allowed. This regex checks if there is a '//' *before* a
# question mark.
elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
redirect_to = settings.LOGIN_REDIRECT_URL
# Okay, security checks complete. Log the user in.
auth_login(request, form.get_user())
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponseRedirect(redirect_to)
else:
form = authentication_form(request)
request.session.set_test_cookie()
#current_site = get_current_site(request)
return render_to_response(template_name, {
'form': form,
redirect_field_name: redirect_to,
#'site': current_site,
#'site_name': current_site.name,
'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
}, context_instance=RequestContext(request))
def api_logout(request, next_page=None, template_name='api_login.html', redirect_field_name=REDIRECT_FIELD_NAME):
return logout(request, next_page, template_name, redirect_field_name)
......@@ -159,7 +159,7 @@ class ModelFormValidator(FormValidator):
otherwise if model is set use that class to create a ModelForm, otherwise return None."""
form_cls = getattr(self.view, 'form', None)
model_cls = getattr(self.view, 'model', None)
model_cls = getattr(self.view.resource, 'model', None)
if form_cls:
# Use explict Form
......@@ -189,9 +189,10 @@ class ModelFormValidator(FormValidator):
@property
def _model_fields_set(self):
"""Return a set containing the names of validated fields on the model."""
model = getattr(self.view, 'model', None)
fields = getattr(self.view, 'fields', self.fields)
exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields)
resource = self.view.resource
model = getattr(resource, 'model', None)
fields = getattr(resource, 'fields', self.fields)
exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields)
model_fields = set(field.name for field in model._meta.fields)
......@@ -203,9 +204,10 @@ class ModelFormValidator(FormValidator):
@property
def _property_fields_set(self):
"""Returns a set containing the names of validated properties on the model."""
model = getattr(self.view, 'model', None)
fields = getattr(self.view, 'fields', self.fields)
exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields)
resource = self.view.resource
model = getattr(resource, 'model', None)
fields = getattr(resource, 'fields', self.fields)
exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields)
property_fields = set(attr for attr in dir(model) if
isinstance(getattr(model, attr, None), property)
......
from djangorestframework.modelresource import ModelResource, RootModelResource
from djangorestframework.modelresource import InstanceModelResource, ListOrCreateModelResource
from modelresourceexample.models import MyModel
FIELDS = ('foo', 'bar', 'baz', 'absolute_url')
class MyModelRootResource(RootModelResource):
class MyModelRootResource(ListOrCreateModelResource):
"""A create/list resource for MyModel.
Available for both authenticated and anonymous access for the purposes of the sandbox."""
model = MyModel
fields = FIELDS
class MyModelResource(ModelResource):
class MyModelResource(InstanceModelResource):
"""A read/update/delete resource for MyModel.
Available for both authenticated and anonymous access for the purposes of the sandbox."""
model = MyModel
......
......@@ -2,11 +2,8 @@ from django.conf.urls.defaults import patterns, include, url
from django.conf import settings
from sandbox.views import Sandbox
urlpatterns = patterns('djangorestframework.views',
(r'robots.txt', 'deny_robots'),
urlpatterns = patterns('',
(r'^$', Sandbox.as_view()),
(r'^resource-example/', include('resourceexample.urls')),
(r'^model-resource-example/', include('modelresourceexample.urls')),
(r'^mixin/', include('mixin.urls')),
......@@ -14,14 +11,6 @@ urlpatterns = patterns('djangorestframework.views',
(r'^pygments/', include('pygments_api.urls')),
(r'^blog-post/', include('blogpost.urls')),
(r'^accounts/login/$', 'api_login'),
(r'^accounts/logout/$', 'api_logout'),
(r'^', include('djangorestframework.urls')),
)
# Only serve favicon in production because otherwise chrome users will pretty much
# permanantly have the django-rest-framework favicon whenever they navigate to
# 127.0.0.1:8000 or whatever, which gets annoying
if not settings.DEBUG:
urlpatterns += patterns('djangorestframework.views',
(r'favicon.ico', 'favicon'),
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment