mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-24 00:04:16 +03:00
Merge remote-tracking branch 'upstream/master'
Conflicts: rest_framework/tests/models.py
This commit is contained in:
commit
f54399ea77
16
.travis.yml
16
.travis.yml
|
@ -7,10 +7,10 @@ python:
|
||||||
- "3.3"
|
- "3.3"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- DJANGO="https://www.djangoproject.com/download/1.7b1/tarball/"
|
- DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/"
|
||||||
- DJANGO="django==1.6.2"
|
- DJANGO="django==1.6.3"
|
||||||
- DJANGO="django==1.5.5"
|
- DJANGO="django==1.5.6"
|
||||||
- DJANGO="django==1.4.10"
|
- DJANGO="django==1.4.11"
|
||||||
- DJANGO="django==1.3.7"
|
- DJANGO="django==1.3.7"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
@ -23,7 +23,7 @@ install:
|
||||||
- "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4; fi"
|
- "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4; fi"
|
||||||
- "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.7; fi"
|
- "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.7; fi"
|
||||||
- "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi"
|
- "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi"
|
||||||
- "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b1/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi"
|
- "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b2/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi"
|
||||||
- export PYTHONPATH=.
|
- export PYTHONPATH=.
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
@ -32,13 +32,13 @@ script:
|
||||||
matrix:
|
matrix:
|
||||||
exclude:
|
exclude:
|
||||||
- python: "2.6"
|
- python: "2.6"
|
||||||
env: DJANGO="https://www.djangoproject.com/download/1.7b1/tarball/"
|
env: DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/"
|
||||||
- python: "3.2"
|
- python: "3.2"
|
||||||
env: DJANGO="django==1.4.10"
|
env: DJANGO="django==1.4.11"
|
||||||
- python: "3.2"
|
- python: "3.2"
|
||||||
env: DJANGO="django==1.3.7"
|
env: DJANGO="django==1.3.7"
|
||||||
- python: "3.3"
|
- python: "3.3"
|
||||||
env: DJANGO="django==1.4.10"
|
env: DJANGO="django==1.4.11"
|
||||||
- python: "3.3"
|
- python: "3.3"
|
||||||
env: DJANGO="django==1.3.7"
|
env: DJANGO="django==1.3.7"
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ For example:
|
||||||
from myapp.serializers import PurchaseSerializer
|
from myapp.serializers import PurchaseSerializer
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
class PurchaseList(generics.ListAPIView)
|
class PurchaseList(generics.ListAPIView):
|
||||||
serializer_class = PurchaseSerializer
|
serializer_class = PurchaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -46,7 +46,7 @@ For example if your URL config contained an entry like this:
|
||||||
|
|
||||||
You could then write a view that returned a purchase queryset filtered by the username portion of the URL:
|
You could then write a view that returned a purchase queryset filtered by the username portion of the URL:
|
||||||
|
|
||||||
class PurchaseList(generics.ListAPIView)
|
class PurchaseList(generics.ListAPIView):
|
||||||
serializer_class = PurchaseSerializer
|
serializer_class = PurchaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -63,7 +63,7 @@ A final example of filtering the initial queryset would be to determine the init
|
||||||
|
|
||||||
We can override `.get_queryset()` to deal with URLs such as `http://example.com/api/purchases?username=denvercoder9`, and filter the queryset only if the `username` parameter is included in the URL:
|
We can override `.get_queryset()` to deal with URLs such as `http://example.com/api/purchases?username=denvercoder9`, and filter the queryset only if the `username` parameter is included in the URL:
|
||||||
|
|
||||||
class PurchaseList(generics.ListAPIView)
|
class PurchaseList(generics.ListAPIView):
|
||||||
serializer_class = PurchaseSerializer
|
serializer_class = PurchaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
@ -70,7 +70,7 @@ The following attributes control the basic view behavior.
|
||||||
|
|
||||||
**Shortcuts**:
|
**Shortcuts**:
|
||||||
|
|
||||||
* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes, although using the explicit style is generally preferred. If used instead of `serializer_class`, then then `DEFAULT_MODEL_SERIALIZER_CLASS` setting will determine the base serializer class. Note that `model` is only ever used for generating a default queryset or serializer class - the `queryset` and `serializer_class` attributes are always preferred if provided.
|
* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes, although using the explicit style is generally preferred. If used instead of `serializer_class`, then `DEFAULT_MODEL_SERIALIZER_CLASS` setting will determine the base serializer class. Note that `model` is only ever used for generating a default queryset or serializer class - the `queryset` and `serializer_class` attributes are always preferred if provided.
|
||||||
|
|
||||||
**Pagination**:
|
**Pagination**:
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ You can also set the authentication policy on a per-view, or per-viewset basis,
|
||||||
using the `APIView` class based views.
|
using the `APIView` class based views.
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.responses import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
class ExampleView(APIView):
|
class ExampleView(APIView):
|
||||||
|
|
|
@ -138,6 +138,26 @@ Renders the request data into `YAML`.
|
||||||
|
|
||||||
Requires the `pyyaml` package to be installed.
|
Requires the `pyyaml` package to be installed.
|
||||||
|
|
||||||
|
Note that non-ascii characters will be rendered using `\uXXXX` character escape. For example:
|
||||||
|
|
||||||
|
unicode black star: "\u2605"
|
||||||
|
|
||||||
|
**.media_type**: `application/yaml`
|
||||||
|
|
||||||
|
**.format**: `'.yaml'`
|
||||||
|
|
||||||
|
**.charset**: `utf-8`
|
||||||
|
|
||||||
|
## UnicodeYAMLRenderer
|
||||||
|
|
||||||
|
Renders the request data into `YAML`.
|
||||||
|
|
||||||
|
Requires the `pyyaml` package to be installed.
|
||||||
|
|
||||||
|
Note that non-ascii characters will not be character escaped. For example:
|
||||||
|
|
||||||
|
unicode black star: ★
|
||||||
|
|
||||||
**.media_type**: `application/yaml`
|
**.media_type**: `application/yaml`
|
||||||
|
|
||||||
**.format**: `'.yaml'`
|
**.format**: `'.yaml'`
|
||||||
|
|
|
@ -40,6 +40,25 @@ You can determine your currently installed version using `pip freeze`:
|
||||||
|
|
||||||
## 2.3.x series
|
## 2.3.x series
|
||||||
|
|
||||||
|
### 2.3.x
|
||||||
|
|
||||||
|
**Date**: April 2014
|
||||||
|
|
||||||
|
* Fix nested serializers linked through a backward foreign key relation
|
||||||
|
* Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer`
|
||||||
|
* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode
|
||||||
|
* Fix `parse_header` argument convertion
|
||||||
|
* Fix mediatype detection under Python3
|
||||||
|
* Web browseable API now offers blank option on dropdown when the field is not required
|
||||||
|
* `APIException` representation improved for logging purposes
|
||||||
|
* Allow source="*" within nested serializers
|
||||||
|
* Better support for custom oauth2 provider backends
|
||||||
|
* Fix field validation if it's optional and has no value
|
||||||
|
* Add `SEARCH_PARAM` and `ORDERING_PARAM`
|
||||||
|
* Fix `APIRequestFactory` to support arguments within the url string for GET
|
||||||
|
* Allow three transport modes for access tokens when accessing a protected resource
|
||||||
|
* Fix `Request`'s `QueryDict` encoding
|
||||||
|
|
||||||
### 2.3.13
|
### 2.3.13
|
||||||
|
|
||||||
**Date**: 6th March 2014
|
**Date**: 6th March 2014
|
||||||
|
@ -112,11 +131,11 @@ You can determine your currently installed version using `pip freeze`:
|
||||||
* Bugfix: `client.force_authenticate(None)` should also clear session info if it exists.
|
* Bugfix: `client.force_authenticate(None)` should also clear session info if it exists.
|
||||||
* Bugfix: Client sending empty string instead of file now clears `FileField`.
|
* Bugfix: Client sending empty string instead of file now clears `FileField`.
|
||||||
* Bugfix: Empty values on ChoiceFields with `required=False` now consistently return `None`.
|
* Bugfix: Empty values on ChoiceFields with `required=False` now consistently return `None`.
|
||||||
* Bugfix: Clients setting `page=0` now simply returns the default page size, instead of disabling pagination. [*]
|
* Bugfix: Clients setting `page_size=0` now simply returns the default page size, instead of disabling pagination. [*]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[*] Note that the change in `page=0` behaviour fixes what is considered to be a bug in how clients can effect the pagination size. However if you were relying on this behavior you will need to add the following mixin to your list views in order to preserve the existing behavior.
|
[*] Note that the change in `page_size=0` behaviour fixes what is considered to be a bug in how clients can effect the pagination size. However if you were relying on this behavior you will need to add the following mixin to your list views in order to preserve the existing behavior.
|
||||||
|
|
||||||
class DisablePaginationMixin(object):
|
class DisablePaginationMixin(object):
|
||||||
def get_paginate_by(self, queryset=None):
|
def get_paginate_by(self, queryset=None):
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Token(models.Model):
|
||||||
return super(Token, self).save(*args, **kwargs)
|
return super(Token, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def generate_key(self):
|
def generate_key(self):
|
||||||
return binascii.hexlify(os.urandom(20))
|
return binascii.hexlify(os.urandom(20)).decode()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.key
|
return self.key
|
||||||
|
|
|
@ -20,6 +20,8 @@ class APIException(Exception):
|
||||||
def __init__(self, detail=None):
|
def __init__(self, detail=None):
|
||||||
self.detail = detail or self.default_detail
|
self.detail = detail or self.default_detail
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.detail
|
||||||
|
|
||||||
class ParseError(APIException):
|
class ParseError(APIException):
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
|
|
@ -164,7 +164,7 @@ class Field(object):
|
||||||
Called to set up a field prior to field_to_native or field_from_native.
|
Called to set up a field prior to field_to_native or field_from_native.
|
||||||
|
|
||||||
parent - The parent serializer.
|
parent - The parent serializer.
|
||||||
model_field - The model field this field corresponds to, if one exists.
|
field_name - The name of the field being initialized.
|
||||||
"""
|
"""
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.root = parent.root or parent
|
self.root = parent.root or parent
|
||||||
|
@ -289,7 +289,7 @@ class WritableField(Field):
|
||||||
self.validators = self.default_validators + validators
|
self.validators = self.default_validators + validators
|
||||||
self.default = default if default is not None else self.default
|
self.default = default if default is not None else self.default
|
||||||
|
|
||||||
# Widgets are ony used for HTML forms.
|
# Widgets are only used for HTML forms.
|
||||||
widget = widget or self.widget
|
widget = widget or self.widget
|
||||||
if isinstance(widget, type):
|
if isinstance(widget, type):
|
||||||
widget = widget()
|
widget = widget()
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.core.files.uploadhandler import StopFutureHandlers
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
||||||
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
|
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
|
||||||
from rest_framework.compat import etree, six, yaml
|
from rest_framework.compat import etree, six, yaml, force_text
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework import renderers
|
from rest_framework import renderers
|
||||||
import json
|
import json
|
||||||
|
@ -288,7 +288,7 @@ class FileUploadParser(BaseParser):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
meta = parser_context['request'].META
|
meta = parser_context['request'].META
|
||||||
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'])
|
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8'))
|
||||||
return disposition[1]['filename']
|
return force_text(disposition[1]['filename'])
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -59,6 +59,8 @@ class RelatedField(WritableField):
|
||||||
super(RelatedField, self).__init__(*args, **kwargs)
|
super(RelatedField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
if not self.required:
|
if not self.required:
|
||||||
|
# Accessed in ModelChoiceIterator django/forms/models.py:1034
|
||||||
|
# If set adds empty choice.
|
||||||
self.empty_label = BLANK_CHOICE_DASH[0][1]
|
self.empty_label = BLANK_CHOICE_DASH[0][1]
|
||||||
|
|
||||||
self.queryset = queryset
|
self.queryset = queryset
|
||||||
|
|
|
@ -193,6 +193,7 @@ class YAMLRenderer(BaseRenderer):
|
||||||
format = 'yaml'
|
format = 'yaml'
|
||||||
encoder = encoders.SafeDumper
|
encoder = encoders.SafeDumper
|
||||||
charset = 'utf-8'
|
charset = 'utf-8'
|
||||||
|
ensure_ascii = True
|
||||||
|
|
||||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
|
@ -203,7 +204,15 @@ class YAMLRenderer(BaseRenderer):
|
||||||
if data is None:
|
if data is None:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder)
|
return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder, allow_unicode=not self.ensure_ascii)
|
||||||
|
|
||||||
|
|
||||||
|
class UnicodeYAMLRenderer(YAMLRenderer):
|
||||||
|
"""
|
||||||
|
Renderer which serializes to YAML.
|
||||||
|
Does *not* apply character escaping for non-ascii characters.
|
||||||
|
"""
|
||||||
|
ensure_ascii = False
|
||||||
|
|
||||||
|
|
||||||
class TemplateHTMLRenderer(BaseRenderer):
|
class TemplateHTMLRenderer(BaseRenderer):
|
||||||
|
|
|
@ -16,6 +16,7 @@ import datetime
|
||||||
import inspect
|
import inspect
|
||||||
import types
|
import types
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from django.contrib.contenttypes.generic import GenericForeignKey
|
||||||
from django.core.paginator import Page
|
from django.core.paginator import Page
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
|
@ -438,6 +439,15 @@ class BaseSerializer(WritableField):
|
||||||
raise ValidationError(self.error_messages['required'])
|
raise ValidationError(self.error_messages['required'])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.source == '*':
|
||||||
|
if value:
|
||||||
|
reverted_data = self.restore_fields(value, {})
|
||||||
|
if not self._errors:
|
||||||
|
into.update(reverted_data)
|
||||||
|
else:
|
||||||
|
if value in (None, ''):
|
||||||
|
into[(self.source or field_name)] = None
|
||||||
|
else:
|
||||||
# Set the serializer object if it exists
|
# Set the serializer object if it exists
|
||||||
obj = get_component(self.parent.object, self.source or field_name) if self.parent.object else None
|
obj = get_component(self.parent.object, self.source or field_name) if self.parent.object else None
|
||||||
|
|
||||||
|
@ -448,15 +458,6 @@ class BaseSerializer(WritableField):
|
||||||
is_simple_callable(getattr(obj, 'all', None))):
|
is_simple_callable(getattr(obj, 'all', None))):
|
||||||
obj = obj.all()
|
obj = obj.all()
|
||||||
|
|
||||||
if self.source == '*':
|
|
||||||
if value:
|
|
||||||
reverted_data = self.restore_fields(value, {})
|
|
||||||
if not self._errors:
|
|
||||||
into.update(reverted_data)
|
|
||||||
else:
|
|
||||||
if value in (None, ''):
|
|
||||||
into[(self.source or field_name)] = None
|
|
||||||
else:
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'instance': obj,
|
'instance': obj,
|
||||||
'data': value,
|
'data': value,
|
||||||
|
@ -827,6 +828,10 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
if model_field:
|
if model_field:
|
||||||
kwargs['required'] = not(model_field.null or model_field.blank)
|
kwargs['required'] = not(model_field.null or model_field.blank)
|
||||||
|
if model_field.help_text is not None:
|
||||||
|
kwargs['help_text'] = model_field.help_text
|
||||||
|
if model_field.verbose_name is not None:
|
||||||
|
kwargs['label'] = model_field.verbose_name
|
||||||
|
|
||||||
if not model_field.editable:
|
if not model_field.editable:
|
||||||
kwargs['read_only'] = True
|
kwargs['read_only'] = True
|
||||||
|
@ -952,6 +957,8 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
# Forward m2m relations
|
# Forward m2m relations
|
||||||
for field in meta.many_to_many + meta.virtual_fields:
|
for field in meta.many_to_many + meta.virtual_fields:
|
||||||
|
if isinstance(field, GenericForeignKey):
|
||||||
|
continue
|
||||||
if field.name in attrs:
|
if field.name in attrs:
|
||||||
m2m_data[field.name] = attrs.pop(field.name)
|
m2m_data[field.name] = attrs.pop(field.name)
|
||||||
|
|
||||||
|
@ -961,18 +968,16 @@ class ModelSerializer(Serializer):
|
||||||
if isinstance(self.fields.get(field_name, None), Serializer):
|
if isinstance(self.fields.get(field_name, None), Serializer):
|
||||||
nested_forward_relations[field_name] = attrs[field_name]
|
nested_forward_relations[field_name] = attrs[field_name]
|
||||||
|
|
||||||
# Update an existing instance...
|
# Create an empty instance of the model
|
||||||
if instance is not None:
|
if instance is None:
|
||||||
|
instance = self.opts.model()
|
||||||
|
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
try:
|
try:
|
||||||
setattr(instance, key, val)
|
setattr(instance, key, val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._errors[key] = self.error_messages['required']
|
self._errors[key] = self.error_messages['required']
|
||||||
|
|
||||||
# ...or create a new instance
|
|
||||||
else:
|
|
||||||
instance = self.opts.model(**attrs)
|
|
||||||
|
|
||||||
# Any relations that cannot be set until we've
|
# Any relations that cannot be set until we've
|
||||||
# saved the model get hidden away on these
|
# saved the model get hidden away on these
|
||||||
# private attributes, so we can deal with them
|
# private attributes, so we can deal with them
|
||||||
|
@ -1096,6 +1101,10 @@ class HyperlinkedModelSerializer(ModelSerializer):
|
||||||
|
|
||||||
if model_field:
|
if model_field:
|
||||||
kwargs['required'] = not(model_field.null or model_field.blank)
|
kwargs['required'] = not(model_field.null or model_field.blank)
|
||||||
|
if model_field.help_text is not None:
|
||||||
|
kwargs['help_text'] = model_field.help_text
|
||||||
|
if model_field.verbose_name is not None:
|
||||||
|
kwargs['label'] = model_field.verbose_name
|
||||||
|
|
||||||
if self.opts.lookup_field:
|
if self.opts.lookup_field:
|
||||||
kwargs['lookup_field'] = self.opts.lookup_field
|
kwargs['lookup_field'] = self.opts.lookup_field
|
||||||
|
|
|
@ -180,7 +180,7 @@ def add_class(value, css_class):
|
||||||
|
|
||||||
|
|
||||||
# Bunch of stuff cloned from urlize
|
# Bunch of stuff cloned from urlize
|
||||||
TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)', '"', "'"]
|
TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)', '"', "']", "'}", "'"]
|
||||||
WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'),
|
WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'),
|
||||||
('"', '"'), ("'", "'")]
|
('"', '"'), ("'", "'")]
|
||||||
word_split_re = re.compile(r'(\s+)')
|
word_split_re = re.compile(r'(\s+)')
|
||||||
|
|
|
@ -144,7 +144,7 @@ class ForeignKeyTarget(RESTFrameworkModel):
|
||||||
class ForeignKeySource(RESTFrameworkModel):
|
class ForeignKeySource(RESTFrameworkModel):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
target = models.ForeignKey(ForeignKeyTarget, related_name='sources',
|
target = models.ForeignKey(ForeignKeyTarget, related_name='sources',
|
||||||
verbose_name='Target object')
|
help_text='Target', verbose_name='Target')
|
||||||
|
|
||||||
|
|
||||||
# Nullable ForeignKey
|
# Nullable ForeignKey
|
||||||
|
|
|
@ -19,7 +19,7 @@ from rest_framework.authentication import (
|
||||||
OAuth2Authentication
|
OAuth2Authentication
|
||||||
)
|
)
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.compat import patterns, url, include
|
from rest_framework.compat import patterns, url, include, six
|
||||||
from rest_framework.compat import oauth2_provider, oauth2_provider_scope
|
from rest_framework.compat import oauth2_provider, oauth2_provider_scope
|
||||||
from rest_framework.compat import oauth, oauth_provider
|
from rest_framework.compat import oauth, oauth_provider
|
||||||
from rest_framework.test import APIRequestFactory, APIClient
|
from rest_framework.test import APIRequestFactory, APIClient
|
||||||
|
@ -195,6 +195,12 @@ class TokenAuthTests(TestCase):
|
||||||
token = Token.objects.create(user=self.user)
|
token = Token.objects.create(user=self.user)
|
||||||
self.assertTrue(bool(token.key))
|
self.assertTrue(bool(token.key))
|
||||||
|
|
||||||
|
def test_generate_key_returns_string(self):
|
||||||
|
"""Ensure generate_key returns a string"""
|
||||||
|
token = Token()
|
||||||
|
key = token.generate_key()
|
||||||
|
self.assertTrue(isinstance(key, six.string_types))
|
||||||
|
|
||||||
def test_token_login_json(self):
|
def test_token_login_json(self):
|
||||||
"""Ensure token login view using JSON POST works."""
|
"""Ensure token login view using JSON POST works."""
|
||||||
client = APIClient(enforce_csrf_checks=True)
|
client = APIClient(enforce_csrf_checks=True)
|
||||||
|
|
|
@ -131,3 +131,21 @@ class TestGenericRelations(TestCase):
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
self.assertEqual(serializer.data, expected)
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
||||||
|
def test_restore_object_generic_fk(self):
|
||||||
|
"""
|
||||||
|
Ensure an object with a generic foreign key can be restored.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('content_type', 'object_id')
|
||||||
|
|
||||||
|
serializer = TagSerializer()
|
||||||
|
|
||||||
|
bookmark = Bookmark(url='http://example.com')
|
||||||
|
attrs = {'tagged_item': bookmark, 'tag': 'example'}
|
||||||
|
|
||||||
|
tag = serializer.restore_object(attrs)
|
||||||
|
self.assertEqual(tag.tagged_item, bookmark)
|
||||||
|
|
|
@ -96,7 +96,7 @@ class TestFileUploadParser(TestCase):
|
||||||
request = MockRequest()
|
request = MockRequest()
|
||||||
request.upload_handlers = (MemoryFileUploadHandler(),)
|
request.upload_handlers = (MemoryFileUploadHandler(),)
|
||||||
request.META = {
|
request.META = {
|
||||||
'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt'.encode('utf-8'),
|
'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt',
|
||||||
'HTTP_CONTENT_LENGTH': 14,
|
'HTTP_CONTENT_LENGTH': 14,
|
||||||
}
|
}
|
||||||
self.parser_context = {'request': request, 'kwargs': {}}
|
self.parser_context = {'request': request, 'kwargs': {}}
|
||||||
|
@ -112,4 +112,4 @@ class TestFileUploadParser(TestCase):
|
||||||
def test_get_filename(self):
|
def test_get_filename(self):
|
||||||
parser = FileUploadParser()
|
parser = FileUploadParser()
|
||||||
filename = parser.get_filename(self.stream, None, self.parser_context)
|
filename = parser.get_filename(self.stream, None, self.parser_context)
|
||||||
self.assertEqual(filename, 'file.txt'.encode('utf-8'))
|
self.assertEqual(filename, 'file.txt')
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
General tests for relational fields.
|
General tests for relational fields.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
from django import get_version
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils import unittest
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.tests.models import BlogPost
|
from rest_framework.tests.models import BlogPost
|
||||||
|
|
||||||
|
@ -118,3 +120,25 @@ class RelatedFieldSourceTests(TestCase):
|
||||||
(serializers.ModelSerializer,), attrs)
|
(serializers.ModelSerializer,), attrs)
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
TestSerializer(data={'name': 'foo'})
|
TestSerializer(data={'name': 'foo'})
|
||||||
|
|
||||||
|
@unittest.skipIf(get_version() < '1.6.0', 'Upstream behaviour changed in v1.6')
|
||||||
|
class RelatedFieldChoicesTests(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for #1408 "Web browseable API doesn't have blank option on drop down list box"
|
||||||
|
https://github.com/tomchristie/django-rest-framework/issues/1408
|
||||||
|
"""
|
||||||
|
def test_blank_option_is_added_to_choice_if_required_equals_false(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
post = BlogPost(title="Checking blank option is added")
|
||||||
|
post.save()
|
||||||
|
|
||||||
|
queryset = BlogPost.objects.all()
|
||||||
|
field = serializers.RelatedField(required=False, queryset=queryset)
|
||||||
|
|
||||||
|
choice_count = BlogPost.objects.count()
|
||||||
|
widget_count = len(field.widget.choices)
|
||||||
|
|
||||||
|
self.assertEqual(widget_count, choice_count + 1, 'BLANK_CHOICE_DASH option should have been added')
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from rest_framework.compat import yaml, etree, patterns, url, include, six, Stri
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||||
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer
|
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer, UnicodeYAMLRenderer
|
||||||
from rest_framework.parsers import YAMLParser, XMLParser
|
from rest_framework.parsers import YAMLParser, XMLParser
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
|
@ -467,6 +467,17 @@ if yaml:
|
||||||
self.assertTrue(string in content, '%r not in %r' % (string, content))
|
self.assertTrue(string in content, '%r not in %r' % (string, content))
|
||||||
|
|
||||||
|
|
||||||
|
class UnicodeYAMLRendererTests(TestCase):
|
||||||
|
"""
|
||||||
|
Tests specific for the Unicode YAML Renderer
|
||||||
|
"""
|
||||||
|
def test_proper_encoding(self):
|
||||||
|
obj = {'countries': ['United Kingdom', 'France', 'España']}
|
||||||
|
renderer = UnicodeYAMLRenderer()
|
||||||
|
content = renderer.render(obj, 'application/yaml')
|
||||||
|
self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
class XMLRendererTestCase(TestCase):
|
class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests specific to the XML Renderer
|
Tests specific to the XML Renderer
|
||||||
|
|
|
@ -9,7 +9,8 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers, fields, relations
|
from rest_framework import serializers, fields, relations
|
||||||
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
|
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
|
||||||
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
|
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
|
||||||
ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel)
|
ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel,
|
||||||
|
ForeignKeySource, ManyToManySource)
|
||||||
from rest_framework.tests.models import BasicModelSerializer
|
from rest_framework.tests.models import BasicModelSerializer
|
||||||
import datetime
|
import datetime
|
||||||
import pickle
|
import pickle
|
||||||
|
@ -176,6 +177,16 @@ class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
|
||||||
fields = ['some_integer']
|
fields = ['some_integer']
|
||||||
|
|
||||||
|
|
||||||
|
class ForeignKeySourceSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ForeignKeySource
|
||||||
|
|
||||||
|
|
||||||
|
class HyperlinkedForeignKeySourceSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ForeignKeySource
|
||||||
|
|
||||||
|
|
||||||
class BasicTests(TestCase):
|
class BasicTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.comment = Comment(
|
self.comment = Comment(
|
||||||
|
@ -508,6 +519,32 @@ class ValidationTests(TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(serializer.is_valid(), True)
|
self.assertEqual(serializer.is_valid(), True)
|
||||||
|
|
||||||
|
def test_writable_star_source_on_nested_serializer_with_parent_object(self):
|
||||||
|
class TitleSerializer(serializers.Serializer):
|
||||||
|
title = serializers.WritableField(source='title')
|
||||||
|
|
||||||
|
class AlbumSerializer(serializers.ModelSerializer):
|
||||||
|
nested = TitleSerializer(source='*')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Album
|
||||||
|
fields = ('nested',)
|
||||||
|
|
||||||
|
class PhotoSerializer(serializers.ModelSerializer):
|
||||||
|
album = AlbumSerializer(source='album')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Photo
|
||||||
|
fields = ('album', )
|
||||||
|
|
||||||
|
photo = Photo(album=Album())
|
||||||
|
|
||||||
|
data = {'album': {'nested': {'title': 'test'}}}
|
||||||
|
|
||||||
|
serializer = PhotoSerializer(photo, data=data)
|
||||||
|
self.assertEqual(serializer.is_valid(), True)
|
||||||
|
self.assertEqual(serializer.data, data)
|
||||||
|
|
||||||
def test_writable_star_source_with_inner_source_fields(self):
|
def test_writable_star_source_with_inner_source_fields(self):
|
||||||
"""
|
"""
|
||||||
Tests that a serializer with source="*" correctly expands the
|
Tests that a serializer with source="*" correctly expands the
|
||||||
|
@ -1574,6 +1611,19 @@ class ManyFieldHelpTextTest(TestCase):
|
||||||
self.assertEqual('Some help text.', rel_field.help_text)
|
self.assertEqual('Some help text.', rel_field.help_text)
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeMappingOnAutogeneratedRelatedFields(TestCase):
|
||||||
|
|
||||||
|
def test_primary_key_related_field(self):
|
||||||
|
serializer = ForeignKeySourceSerializer()
|
||||||
|
self.assertEqual(serializer.fields['target'].help_text, 'Target')
|
||||||
|
self.assertEqual(serializer.fields['target'].label, 'Target')
|
||||||
|
|
||||||
|
def test_hyperlinked_related_field(self):
|
||||||
|
serializer = HyperlinkedForeignKeySourceSerializer()
|
||||||
|
self.assertEqual(serializer.fields['target'].help_text, 'Target')
|
||||||
|
self.assertEqual(serializer.fields['target'].label, 'Target')
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(PIL is not None, 'PIL is not installed')
|
@unittest.skipUnless(PIL is not None, 'PIL is not installed')
|
||||||
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
|
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
|
||||||
|
|
||||||
|
|
38
rest_framework/tests/test_urlizer.py
Normal file
38
rest_framework/tests/test_urlizer.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from django.test import TestCase
|
||||||
|
from rest_framework.templatetags.rest_framework import urlize_quoted_links
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class URLizerTests(TestCase):
|
||||||
|
"""
|
||||||
|
Test if both JSON and YAML URLs are transformed into links well
|
||||||
|
"""
|
||||||
|
def _urlize_dict_check(self, data):
|
||||||
|
"""
|
||||||
|
For all items in dict test assert that the value is urlized key
|
||||||
|
"""
|
||||||
|
for original, urlized in data.items():
|
||||||
|
assert urlize_quoted_links(original, nofollow=False) == urlized
|
||||||
|
|
||||||
|
def test_json_with_url(self):
|
||||||
|
"""
|
||||||
|
Test if JSON URLs are transformed into links well
|
||||||
|
"""
|
||||||
|
data = {}
|
||||||
|
data['"url": "http://api/users/1/", '] = \
|
||||||
|
'"url": "<a href="http://api/users/1/">http://api/users/1/</a>", '
|
||||||
|
data['"foo_set": [\n "http://api/foos/1/"\n], '] = \
|
||||||
|
'"foo_set": [\n "<a href="http://api/foos/1/">http://api/foos/1/</a>"\n], '
|
||||||
|
self._urlize_dict_check(data)
|
||||||
|
|
||||||
|
def test_yaml_with_url(self):
|
||||||
|
"""
|
||||||
|
Test if YAML URLs are transformed into links well
|
||||||
|
"""
|
||||||
|
data = {}
|
||||||
|
data['''{users: 'http://api/users/'}'''] = \
|
||||||
|
'''{users: '<a href="http://api/users/">http://api/users/</a>'}'''
|
||||||
|
data['''foo_set: ['http://api/foos/1/']'''] = \
|
||||||
|
'''foo_set: ['<a href="http://api/foos/1/">http://api/foos/1/</a>']'''
|
||||||
|
self._urlize_dict_check(data)
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
from django.core.validators import MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from rest_framework import generics, serializers, status
|
from rest_framework import generics, serializers, status
|
||||||
|
@ -102,3 +103,46 @@ class TestAvoidValidation(TestCase):
|
||||||
self.assertFalse(serializer.is_valid())
|
self.assertFalse(serializer.is_valid())
|
||||||
self.assertDictEqual(serializer.errors,
|
self.assertDictEqual(serializer.errors,
|
||||||
{'non_field_errors': ['Invalid data']})
|
{'non_field_errors': ['Invalid data']})
|
||||||
|
|
||||||
|
|
||||||
|
# regression tests for issue: 1493
|
||||||
|
|
||||||
|
class ValidationMaxValueValidatorModel(models.Model):
|
||||||
|
number_value = models.PositiveIntegerField(validators=[MaxValueValidator(100)])
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationMaxValueValidatorModelSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ValidationMaxValueValidatorModel
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateMaxValueValidationModel(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
model = ValidationMaxValueValidatorModel
|
||||||
|
serializer_class = ValidationMaxValueValidatorModelSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaxValueValidatorValidation(TestCase):
|
||||||
|
|
||||||
|
def test_max_value_validation_serializer_success(self):
|
||||||
|
serializer = ValidationMaxValueValidatorModelSerializer(data={'number_value': 99})
|
||||||
|
self.assertTrue(serializer.is_valid())
|
||||||
|
|
||||||
|
def test_max_value_validation_serializer_fails(self):
|
||||||
|
serializer = ValidationMaxValueValidatorModelSerializer(data={'number_value': 101})
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
self.assertDictEqual({'number_value': ['Ensure this value is less than or equal to 100.']}, serializer.errors)
|
||||||
|
|
||||||
|
def test_max_value_validation_success(self):
|
||||||
|
obj = ValidationMaxValueValidatorModel.objects.create(number_value=100)
|
||||||
|
request = factory.patch('/{0}'.format(obj.pk), {'number_value': 98}, format='json')
|
||||||
|
view = UpdateMaxValueValidationModel().as_view()
|
||||||
|
response = view(request, pk=obj.pk).render()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_max_value_validation_fail(self):
|
||||||
|
obj = ValidationMaxValueValidatorModel.objects.create(number_value=100)
|
||||||
|
request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json')
|
||||||
|
view = UpdateMaxValueValidationModel().as_view()
|
||||||
|
response = view(request, pk=obj.pk).render()
|
||||||
|
self.assertEqual(response.content, b'{"number_value": ["Ensure this value is less than or equal to 100."]}')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
|
@ -74,7 +74,7 @@ class _MediaType(object):
|
||||||
return 0
|
return 0
|
||||||
elif self.sub_type == '*':
|
elif self.sub_type == '*':
|
||||||
return 1
|
return 1
|
||||||
elif not self.params or self.params.keys() == ['q']:
|
elif not self.params or list(self.params.keys()) == ['q']:
|
||||||
return 2
|
return 2
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
|
|
26
tox.ini
26
tox.ini
|
@ -7,21 +7,21 @@ commands = {envpython} rest_framework/runtests/runtests.py
|
||||||
|
|
||||||
[testenv:py3.3-django1.7]
|
[testenv:py3.3-django1.7]
|
||||||
basepython = python3.3
|
basepython = python3.3
|
||||||
deps = https://www.djangoproject.com/download/1.7b1/tarball/
|
deps = https://www.djangoproject.com/download/1.7b2/tarball/
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
Pillow==2.3.0
|
Pillow==2.3.0
|
||||||
|
|
||||||
[testenv:py3.2-django1.7]
|
[testenv:py3.2-django1.7]
|
||||||
basepython = python3.2
|
basepython = python3.2
|
||||||
deps = https://www.djangoproject.com/download/1.7b1/tarball/
|
deps = https://www.djangoproject.com/download/1.7b2/tarball/
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
Pillow==2.3.0
|
Pillow==2.3.0
|
||||||
|
|
||||||
[testenv:py2.7-django1.7]
|
[testenv:py2.7-django1.7]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = https://www.djangoproject.com/download/1.7b1/tarball/
|
deps = https://www.djangoproject.com/download/1.7b2/tarball/
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
django-oauth-plus==2.2.1
|
django-oauth-plus==2.2.1
|
||||||
|
@ -32,21 +32,21 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/
|
||||||
|
|
||||||
[testenv:py3.3-django1.6]
|
[testenv:py3.3-django1.6]
|
||||||
basepython = python3.3
|
basepython = python3.3
|
||||||
deps = Django==1.6
|
deps = Django==1.6.3
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
Pillow==2.3.0
|
Pillow==2.3.0
|
||||||
|
|
||||||
[testenv:py3.2-django1.6]
|
[testenv:py3.2-django1.6]
|
||||||
basepython = python3.2
|
basepython = python3.2
|
||||||
deps = Django==1.6
|
deps = Django==1.6.3
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
Pillow==2.3.0
|
Pillow==2.3.0
|
||||||
|
|
||||||
[testenv:py2.7-django1.6]
|
[testenv:py2.7-django1.6]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = Django==1.6
|
deps = Django==1.6.3
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
django-oauth-plus==2.2.1
|
django-oauth-plus==2.2.1
|
||||||
|
@ -57,7 +57,7 @@ deps = Django==1.6
|
||||||
|
|
||||||
[testenv:py2.6-django1.6]
|
[testenv:py2.6-django1.6]
|
||||||
basepython = python2.6
|
basepython = python2.6
|
||||||
deps = Django==1.6
|
deps = Django==1.6.3
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
django-oauth-plus==2.2.1
|
django-oauth-plus==2.2.1
|
||||||
|
@ -68,21 +68,21 @@ deps = Django==1.6
|
||||||
|
|
||||||
[testenv:py3.3-django1.5]
|
[testenv:py3.3-django1.5]
|
||||||
basepython = python3.3
|
basepython = python3.3
|
||||||
deps = django==1.5.5
|
deps = django==1.5.6
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
Pillow==2.3.0
|
Pillow==2.3.0
|
||||||
|
|
||||||
[testenv:py3.2-django1.5]
|
[testenv:py3.2-django1.5]
|
||||||
basepython = python3.2
|
basepython = python3.2
|
||||||
deps = django==1.5.5
|
deps = django==1.5.6
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
Pillow==2.3.0
|
Pillow==2.3.0
|
||||||
|
|
||||||
[testenv:py2.7-django1.5]
|
[testenv:py2.7-django1.5]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = django==1.5.5
|
deps = django==1.5.6
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
django-oauth-plus==2.2.1
|
django-oauth-plus==2.2.1
|
||||||
|
@ -93,7 +93,7 @@ deps = django==1.5.5
|
||||||
|
|
||||||
[testenv:py2.6-django1.5]
|
[testenv:py2.6-django1.5]
|
||||||
basepython = python2.6
|
basepython = python2.6
|
||||||
deps = django==1.5.5
|
deps = django==1.5.6
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
django-oauth-plus==2.2.1
|
django-oauth-plus==2.2.1
|
||||||
|
@ -104,7 +104,7 @@ deps = django==1.5.5
|
||||||
|
|
||||||
[testenv:py2.7-django1.4]
|
[testenv:py2.7-django1.4]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = django==1.4.10
|
deps = django==1.4.11
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
django-oauth-plus==2.2.1
|
django-oauth-plus==2.2.1
|
||||||
|
@ -115,7 +115,7 @@ deps = django==1.4.10
|
||||||
|
|
||||||
[testenv:py2.6-django1.4]
|
[testenv:py2.6-django1.4]
|
||||||
basepython = python2.6
|
basepython = python2.6
|
||||||
deps = django==1.4.10
|
deps = django==1.4.11
|
||||||
django-filter==0.7
|
django-filter==0.7
|
||||||
defusedxml==0.3
|
defusedxml==0.3
|
||||||
django-oauth-plus==2.2.1
|
django-oauth-plus==2.2.1
|
||||||
|
|
Loading…
Reference in New Issue
Block a user