Skip to content

Commit

Permalink
Merge master
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie committed Feb 9, 2015
2 parents 407480b + 7b639c0 commit fbb21ca
Show file tree
Hide file tree
Showing 29 changed files with 125 additions and 66 deletions.
19 changes: 7 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
*~
.*

site/
htmlcov/
coverage/
build/
dist/
*.egg-info/
/site/
/htmlcov/
/coverage/
/build/
/dist/
/*.egg-info/
/env/
MANIFEST

bin/
include/
lib/
local/
env/

!.gitignore
!.travis.yml
12 changes: 0 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ env:
- TOX_ENV=py33-django18alpha
- TOX_ENV=py32-django18alpha
- TOX_ENV=py27-django18alpha
- TOX_ENV=py34-djangomaster
- TOX_ENV=py33-djangomaster
- TOX_ENV=py32-djangomaster
- TOX_ENV=py27-djangomaster

matrix:
fast_finish: true
allow_failures:
- env: TOX_ENV=py34-djangomaster
- env: TOX_ENV=py33-djangomaster
- env: TOX_ENV=py32-djangomaster
- env: TOX_ENV=py27-djangomaster

install:
- pip install tox
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements

* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-alpha)

# Installation

Expand Down
5 changes: 5 additions & 0 deletions docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,10 @@ The [drf-compound-fields][drf-compound-fields] package provides "compound" seria

The [drf-extra-fields][drf-extra-fields] package provides extra serializer fields for REST framework, including `Base64ImageField` and `PointField` classes.

## djangrestframework-recursive

the [djangorestframework-recursive][djangorestframework-recursive] package provides a `RecursiveField` for serializing and deserializing recursive structures

## django-rest-framework-gis

The [django-rest-framework-gis][django-rest-framework-gis] package provides geographic addons for django rest framework like a `GeometryField` field and a GeoJSON serializer.
Expand All @@ -607,6 +611,7 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide
[iso8601]: http://www.w3.org/TR/NOTE-datetime
[drf-compound-fields]: http://drf-compound-fields.readthedocs.org
[drf-extra-fields]: https://github.com/Hipo/drf-extra-fields
[djangorestframework-recursive]: https://github.com/heywbj/django-rest-framework-recursive
[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis
[django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
[django-hstore]: https://github.com/djangonauts/django-hstore
2 changes: 1 addition & 1 deletion docs/api-guide/routers.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ The [wq.db package][wq.db] provides an advanced [Router][wq.db-router] class (an
The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions-routers] for creating [nested viewsets][drf-extensions-nested-viewsets], [collection level controllers][drf-extensions-collection-level-controllers] with [customizable endpoint names][drf-extensions-customizable-endpoint-names].

[cite]: http://guides.rubyonrails.org/routing.html
[route-decorators]: viewsets.html#marking-extra-actions-for-routing
[route-decorators]: viewsets.md#marking-extra-actions-for-routing
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[wq.db]: http://wq.io/wq.db
[wq.db-router]: http://wq.io/docs/app.py
Expand Down
2 changes: 1 addition & 1 deletion docs/api-guide/viewsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ Note that you can use any of the standard attributes or method overrides provide
def get_queryset(self):
return self.request.user.accounts.all()

Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you you will have to specify the `base_name` kwarg as part of your [router registration][routers].
Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you will have to specify the `base_name` kwarg as part of your [router registration][routers].

Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes.

Expand Down
2 changes: 1 addition & 1 deletion docs/topics/3.0-announcement.md
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ The `style` keyword argument can be used to pass through additional information
For example, to use a `textarea` control instead of the default `input` control, you would use the following…

additional_notes = serializers.CharField(
style={'base_template': 'text_area.html'}
style={'base_template': 'textarea.html'}
)

Similarly, to use a radio button control instead of the default `select` control, you would use the following…
Expand Down
4 changes: 2 additions & 2 deletions docs/topics/third-party-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
* [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok
* [drf-extensions][drf-extensions] - A collection of custom extensions
* [ember-data-django-rest-adapter][ember-data-django-rest-adapter] - An ember-data adapter
* [ember-django-adapter][ember-django-adapter] - An adapter for working with Ember.js

## Other Resources

Expand Down Expand Up @@ -309,7 +309,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[django-rest-framework-proxy]: https://github.com/eofs/django-rest-framework-proxy
[gaiarestframework]: https://github.com/AppsFuel/gaiarestframework
[drf-extensions]: https://github.com/chibisov/drf-extensions
[ember-data-django-rest-adapter]: https://github.com/toranb/ember-data-django-rest-adapter
[ember-django-adapter]: https://github.com/dustinfarris/ember-django-adapter
[beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
[getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
[end-to-end-web-app-with-django-rest-framework-angularjs]: http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/1-serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ The first thing we need to get started on our Web API is to provide a way of ser
class SnippetSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'type': 'textarea'})
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/2-requests-and-responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Notice that we're no longer explicitly tying our requests or responses to a give

## Adding optional format suffixes to our URLs

To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4.json][json-url].
To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4/.json][json-url].

Start by adding a `format` keyword argument to both of the views, like so.

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/5-relationships-and-hyperlinked-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file

The list views for users and code snippets could end up returning quite a lot of instances, so really we'd like to make sure we paginate the results, and allow the API client to step through each of the individual pages.

We can change the default list style to use pagination, by modifying our `settings.py` file slightly. Add the following setting:
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:

REST_FRAMEWORK = {
'PAGINATE_BY': 10
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ django-filter>=0.9.2

# wheel for PyPI installs
wheel==0.24.0
# twine for secured PyPI uploads
twine==1.4.0

# MkDocs for documentation previews/deploys
mkdocs==0.11.1
2 changes: 1 addition & 1 deletion rest_framework/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def authenticate(self, request):

def authenticate_credentials(self, key):
try:
token = self.model.objects.get(key=key)
token = self.model.objects.select_related('user').get(key=key)
except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed(_('Invalid token.'))

Expand Down
11 changes: 7 additions & 4 deletions rest_framework/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def api_view(http_method_names=None):
Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument.
"""
if http_method_names is None:
http_method_names = ['GET']
http_method_names = ['GET'] if (http_method_names is None) else http_method_names

def decorator(func):

Expand Down Expand Up @@ -109,10 +108,12 @@ def decorator(func):
return decorator


def detail_route(methods=['get'], **kwargs):
def detail_route(methods=None, **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for detail requests.
"""
methods = ['get'] if (methods is None) else methods

def decorator(func):
func.bind_to_methods = methods
func.detail = True
Expand All @@ -121,10 +122,12 @@ def decorator(func):
return decorator


def list_route(methods=['get'], **kwargs):
def list_route(methods=None, **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for list requests.
"""
methods = ['get'] if (methods is None) else methods

def decorator(func):
func.bind_to_methods = methods
func.detail = False
Expand Down
4 changes: 3 additions & 1 deletion rest_framework/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ def _reverse_ordering(ordering_tuple):
Given an order_by tuple such as `('-created', 'uuid')` reverse the
ordering and return a new tuple, eg. `('created', '-uuid')`.
"""
invert = lambda x: x[1:] if (x.startswith('-')) else '-' + x
def invert(x):
return x[1:] if (x.startswith('-')) else '-' + x

return tuple([invert(item) for item in ordering_tuple])


Expand Down
21 changes: 15 additions & 6 deletions rest_framework/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
from django.conf import settings
from django.http import QueryDict
from django.http.multipartparser import parse_header
from django.utils import six
from django.utils.datastructures import MultiValueDict
from django.utils.datastructures import MergeDict as DjangoMergeDict
from django.utils.six import BytesIO
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import exceptions
from rest_framework.settings import api_settings
import sys
import warnings


Expand Down Expand Up @@ -366,7 +367,7 @@ def _load_stream(self):
elif hasattr(self._request, 'read'):
self._stream = self._request
else:
self._stream = BytesIO(self.raw_post_data)
self._stream = six.BytesIO(self.raw_post_data)

def _perform_form_overloading(self):
"""
Expand Down Expand Up @@ -408,7 +409,7 @@ def _perform_form_overloading(self):
self._CONTENTTYPE_PARAM in self._data
):
self._content_type = self._data[self._CONTENTTYPE_PARAM]
self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding']))
self._stream = six.BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding']))
self._data, self._files, self._full_data = (Empty, Empty, Empty)

def _parse(self):
Expand Down Expand Up @@ -489,8 +490,16 @@ def _not_authenticated(self):
else:
self.auth = None

def __getattr__(self, attr):
def __getattribute__(self, attr):
"""
Proxy other attributes to the underlying HttpRequest object.
If an attribute does not exist on this instance, then we also attempt
to proxy it to the underlying HttpRequest object.
"""
return getattr(self._request, attr)
try:
return super(Request, self).__getattribute__(attr)
except AttributeError:
info = sys.exc_info()
try:
return getattr(self._request, attr)
except AttributeError:
six.reraise(info[0], info[1], info[2].tb_next)
3 changes: 2 additions & 1 deletion rest_framework/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ def __getstate__(self):
state = super(Response, self).__getstate__()
for key in (
'accepted_renderer', 'renderer_context', 'resolver_match',
'client', 'request', 'wsgi_request', '_closable_objects'
'client', 'request', 'wsgi_request'
):
if key in state:
del state[key]
state['_closable_objects'] = []
return state
6 changes: 3 additions & 3 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def is_valid(self, raise_exception=False):
)

assert hasattr(self, 'initial_data'), (
'Cannot call `.is_valid()` as no `data=` keyword argument was'
'Cannot call `.is_valid()` as no `data=` keyword argument was '
'passed when instantiating the serializer instance.'
)

Expand Down Expand Up @@ -635,11 +635,11 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
If we don't do this explicitly they'd get a less helpful error when
calling `.save()` on the serializer.
We don't *automatically* support these sorts of nested writes brecause
We don't *automatically* support these sorts of nested writes because
there are too many ambiguities to define a default behavior.
Eg. Suppose we have a `UserSerializer` with a nested profile. How should
we handle the case of an update, where the `profile` realtionship does
we handle the case of an update, where the `profile` relationship does
not exist? Any of the following might be valid:
* Raise an application error.
Expand Down
4 changes: 3 additions & 1 deletion rest_framework/templatetags/rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
If autoescape is True, the link text and URLs will get autoescaped.
"""
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
def trim_url(x, limit=trim_url_limit):
return limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x

safe_input = isinstance(text, SafeData)
words = word_split_re.split(force_text(text))
for i, word in enumerate(words):
Expand Down
4 changes: 2 additions & 2 deletions rest_framework/throttling.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class UserRateThrottle(SimpleRateThrottle):

def get_cache_key(self, request, view):
if request.user.is_authenticated():
ident = request.user.id
ident = request.user.pk
else:
ident = self.get_ident(request)

Expand Down Expand Up @@ -239,7 +239,7 @@ def get_cache_key(self, request, view):
with the '.throttle_scope` property of the view.
"""
if request.user.is_authenticated():
ident = request.user.id
ident = request.user.pk
else:
ident = self.get_ident(request)

Expand Down
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ def get_package_data(package):
if os.system("pip freeze | grep wheel"):
print("wheel not installed.\nUse `pip install wheel`.\nExiting.")
sys.exit()
os.system("python setup.py sdist upload")
os.system("python setup.py bdist_wheel upload")
if os.system("pip freeze | grep twine"):
print("twine not installed.\nUse `pip install twine`.\nExiting.")
sys.exit()
os.system("python setup.py sdist bdist_wheel")
os.system("twine upload dist/*")
print("You probably want to also tag the version now:")
print(" git tag -a %s -m 'version %s'" % (version, version))
print(" git push --tags")
Expand Down
9 changes: 9 additions & 0 deletions tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ def test_post_json_passing_token_auth(self):
response = self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_post_json_makes_one_db_query(self):
"""Ensure that authenticating a user using a token performs only one DB query"""
auth = "Token " + self.key

def func_to_test():
return self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth)

self.assertNumQueries(1, func_to_test)

def test_post_form_failing_token_auth(self):
"""Ensure POSTing form over token auth without correct credentials fails"""
response = self.csrf_client.post('/token/', {'example': 'example'})
Expand Down
4 changes: 3 additions & 1 deletion tests/test_relations_hyperlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
request = factory.get('/') # Just to ensure we have a request in the serializer context


dummy_view = lambda request, pk: None
def dummy_view(request, pk):
pass


urlpatterns = [
url(r'^dummyurl/(?P<pk>[0-9]+)/$', dummy_view, name='dummy-url'),
Expand Down
9 changes: 7 additions & 2 deletions tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@
DUMMYSTATUS = status.HTTP_200_OK
DUMMYCONTENT = 'dummycontent'

RENDERER_A_SERIALIZER = lambda x: ('Renderer A: %s' % x).encode('ascii')
RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')

def RENDERER_A_SERIALIZER(x):
return ('Renderer A: %s' % x).encode('ascii')


def RENDERER_B_SERIALIZER(x):
return ('Renderer B: %s' % x).encode('ascii')


expected_results = [
Expand Down
Loading

0 comments on commit fbb21ca

Please sign in to comment.