mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-09 06:44:47 +03:00
Merge branch 'master' into test/multipart_nested_serializer
This commit is contained in:
commit
a75c80d509
13
.travis.yml
13
.travis.yml
|
@ -1,5 +1,8 @@
|
|||
language: python
|
||||
|
||||
python:
|
||||
- "3.5"
|
||||
|
||||
sudo: false
|
||||
|
||||
env:
|
||||
|
@ -12,19 +15,13 @@ env:
|
|||
- TOX_ENV=py33-django18
|
||||
- TOX_ENV=py32-django18
|
||||
- TOX_ENV=py27-django18
|
||||
- TOX_ENV=py34-django17
|
||||
- TOX_ENV=py33-django17
|
||||
- TOX_ENV=py32-django17
|
||||
- TOX_ENV=py27-django17
|
||||
|
||||
matrix:
|
||||
# Python 3.5 not yet available on travis, watch this to see when it is.
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: TOX_ENV=py35-django19
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
# Virtualenv < 14 is required to keep the Python 3.2 builds running.
|
||||
- pip install tox "virtualenv<14"
|
||||
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
||||
|
|
|
@ -33,7 +33,7 @@ Some tips on good issue reporting:
|
|||
* When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.
|
||||
* Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue.
|
||||
* If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one.
|
||||
* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation.
|
||||
* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bug fixes, and great documentation.
|
||||
* Closing an issue doesn't necessarily mean the end of a discussion. If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened.
|
||||
|
||||
## Triaging issues
|
||||
|
@ -118,10 +118,6 @@ Always run the tests before submitting pull requests, and ideally run `tox` in o
|
|||
|
||||
Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect.
|
||||
|
||||
![Travis status][travis-status]
|
||||
|
||||
*Above: Travis build notifications*
|
||||
|
||||
## Managing compatibility issues
|
||||
|
||||
Sometimes, in order to ensure your code works on various different versions of Django, Python or third party libraries, you'll need to run slightly different code depending on the environment. Any code that branches in this way should be isolated into the `compat.py` module, and should provide a single common interface that the rest of the codebase can use.
|
||||
|
@ -203,7 +199,6 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
|
|||
[so-filter]: http://stackexchange.com/filters/66475/rest-framework
|
||||
[issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open
|
||||
[pep-8]: http://www.python.org/dev/peps/pep-0008/
|
||||
[travis-status]: ../img/travis-status.png
|
||||
[pull-requests]: https://help.github.com/articles/using-pull-requests
|
||||
[tox]: http://tox.readthedocs.org/en/latest/
|
||||
[markdown]: http://daringfireball.net/projects/markdown/basics
|
||||
|
|
14
ISSUE_TEMPLATE.md
Normal file
14
ISSUE_TEMPLATE.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
## Checklist
|
||||
|
||||
- [ ] I have verified that that issue exists against the `master` branch of Django REST framework.
|
||||
- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
|
||||
- [ ] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
|
||||
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
|
||||
- [ ] I have reduced the issue to the simplest possible case.
|
||||
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
## Expected behavior
|
||||
|
||||
## Actual behavior
|
|
@ -1,6 +1,6 @@
|
|||
# License
|
||||
|
||||
Copyright (c) 2011-2015, Tom Christie
|
||||
Copyright (c) 2011-2016, Tom Christie
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
5
PULL_REQUEST_TEMPLATE.md
Normal file
5
PULL_REQUEST_TEMPLATE.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
*Note*: Before submitting this pull request, please review our [contributing guidelines](https://github.com/tomchristie/django-rest-framework/blob/master/CONTRIBUTING.md#pull-requests).
|
||||
|
||||
## Description
|
||||
|
||||
Please describe your pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. When linking to an issue, please use `refs #...` in the description of the pull request.
|
|
@ -61,7 +61,9 @@ Startup up a new project like so...
|
|||
pip install django
|
||||
pip install djangorestframework
|
||||
django-admin.py startproject example .
|
||||
./manage.py syncdb
|
||||
./manage.py migrate
|
||||
./manage.py createsuperuser
|
||||
|
||||
|
||||
Now edit the `example/urls.py` module in your project:
|
||||
|
||||
|
|
|
@ -81,7 +81,13 @@ A text string that may be used as a description of the field in HTML form fields
|
|||
|
||||
### `initial`
|
||||
|
||||
A value that should be used for pre-populating the value of HTML form fields.
|
||||
A value that should be used for pre-populating the value of HTML form fields. You may pass a callable to it, just as
|
||||
you may do with any regular Django `Field`:
|
||||
|
||||
import datetime
|
||||
from rest_framework import serializers
|
||||
class ExampleSerializer(serializers.Serializer):
|
||||
day = serializers.DateField(initial=datetime.date.today)
|
||||
|
||||
### `style`
|
||||
|
||||
|
@ -96,9 +102,9 @@ Two examples here are `'input_type'` and `'base_template'`:
|
|||
|
||||
# Use a radio input instead of a select input.
|
||||
color_channel = serializers.ChoiceField(
|
||||
choices=['red', 'green', 'blue']
|
||||
style = {'base_template': 'radio.html'}
|
||||
}
|
||||
choices=['red', 'green', 'blue'],
|
||||
style={'base_template': 'radio.html'}
|
||||
)
|
||||
|
||||
For more details see the [HTML & Forms][html-and-forms] documentation.
|
||||
|
||||
|
|
|
@ -317,4 +317,4 @@ The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin`
|
|||
[link-header]: ../img/link-header-pagination.png
|
||||
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
|
||||
[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
|
||||
[disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/
|
||||
[disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api
|
||||
|
|
|
@ -330,6 +330,8 @@ To implement a custom relational field, you should override `RelatedField`, and
|
|||
|
||||
If you want to implement a read-write relational field, you must also implement the `.to_internal_value(self, data)` method.
|
||||
|
||||
To provide a dynamic queryset based on the `context`, you can also override `.get_queryset(self)` instead of specifying `.queryset` on the class or when initializing the field.
|
||||
|
||||
## Example
|
||||
|
||||
For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration.
|
||||
|
@ -505,7 +507,7 @@ For example, given the following model for a tag, which has a generic relationsh
|
|||
def __unicode__(self):
|
||||
return self.tag_name
|
||||
|
||||
And the following two models, which may be have associated tags:
|
||||
And the following two models, which may have associated tags:
|
||||
|
||||
class Bookmark(models.Model):
|
||||
"""
|
||||
|
@ -578,9 +580,14 @@ The following third party packages are also available.
|
|||
|
||||
The [drf-nested-routers package][drf-nested-routers] provides routers and relationship fields for working with nested resources.
|
||||
|
||||
## Rest Framework Generic Relations
|
||||
|
||||
The [rest-framework-generic-relations][drf-nested-relations] library provides read/write serialization for generic foreign keys.
|
||||
|
||||
[cite]: http://lwn.net/Articles/193245/
|
||||
[reverse-relationships]: https://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward
|
||||
[routers]: http://www.django-rest-framework.org/api-guide/routers#defaultrouter
|
||||
[generic-relations]: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#id1
|
||||
[2.2-announcement]: ../topics/2.2-announcement.md
|
||||
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
||||
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations
|
||||
|
|
|
@ -43,7 +43,7 @@ We can now use `CommentSerializer` to serialize a comment, or list of comments.
|
|||
|
||||
serializer = CommentSerializer(comment)
|
||||
serializer.data
|
||||
# {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
|
||||
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
|
||||
|
||||
At this point we've translated the model instance into Python native datatypes. To finalise the serialization process we render the data into `json`.
|
||||
|
||||
|
@ -51,7 +51,7 @@ At this point we've translated the model instance into Python native datatypes.
|
|||
|
||||
json = JSONRenderer().render(serializer.data)
|
||||
json
|
||||
# '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}'
|
||||
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
|
||||
|
||||
## Deserializing objects
|
||||
|
||||
|
@ -384,7 +384,7 @@ This manager class now more nicely encapsulates that user instances and profile
|
|||
has_support_contract=validated_data['profile']['has_support_contract']
|
||||
)
|
||||
|
||||
For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manager classes](encapsulation-blogpost).
|
||||
For more details on this approach see the Django documentation on [model managers][model-managers], and [this blogpost on using model and manager classes][encapsulation-blogpost].
|
||||
|
||||
## Dealing with multiple objects
|
||||
|
||||
|
@ -775,6 +775,8 @@ To support multiple updates you'll need to do so explicitly. When writing your m
|
|||
* How should removals be handled? Do they imply object deletion, or removing a relationship? Should they be silently ignored, or are they invalid?
|
||||
* How should ordering be handled? Does changing the position of two items imply any state change or is it ignored?
|
||||
|
||||
You will need to add an explicit `id` field to the instance serializer. The default implicitly-generated `id` field is marked as `read_only`. This causes it to be removed on updates. Once you declare it explicitly, it will be available in the list serializer's `update` method.
|
||||
|
||||
Here's an example of how you might choose to implement multiple updates:
|
||||
|
||||
class BookListSerializer(serializers.ListSerializer):
|
||||
|
@ -786,7 +788,7 @@ Here's an example of how you might choose to implement multiple updates:
|
|||
# Perform creations and updates.
|
||||
ret = []
|
||||
for book_id, data in data_mapping.items():
|
||||
book = book_mapping.get(book_id, None):
|
||||
book = book_mapping.get(book_id, None)
|
||||
if book is None:
|
||||
ret.append(self.child.create(data))
|
||||
else:
|
||||
|
@ -805,6 +807,8 @@ Here's an example of how you might choose to implement multiple updates:
|
|||
id = serializers.IntegerField()
|
||||
|
||||
...
|
||||
id = serializers.IntegerField(required=False)
|
||||
|
||||
class Meta:
|
||||
list_serializer_class = BookListSerializer
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ Your URL conf must include a pattern that matches the version with a `'version'`
|
|||
|
||||
## NamespaceVersioning
|
||||
|
||||
To the client, this scheme is the same as `URLParameterVersioning`. The only difference is how it is configured in your Django application, as it uses URL namespacing, instead of URL keyword arguments.
|
||||
To the client, this scheme is the same as `URLPathVersioning`. The only difference is how it is configured in your Django application, as it uses URL namespacing, instead of URL keyword arguments.
|
||||
|
||||
GET /v1/something/ HTTP/1.1
|
||||
Host: example.com
|
||||
|
@ -165,7 +165,7 @@ In the following example we're giving a set of views two different possible URL
|
|||
url(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
|
||||
]
|
||||
|
||||
Both `URLParameterVersioning` and `NamespaceVersioning` are reasonable if you just need a simple versioning scheme. The `URLParameterVersioning` approach might be better suitable for small ad-hoc projects, and the `NamespaceVersioning` is probably easier to manage for larger projects.
|
||||
Both `URLPathVersioning` and `NamespaceVersioning` are reasonable if you just need a simple versioning scheme. The `URLPathVersioning` approach might be better suitable for small ad-hoc projects, and the `NamespaceVersioning` is probably easier to manage for larger projects.
|
||||
|
||||
## HostNameVersioning
|
||||
|
||||
|
@ -214,7 +214,7 @@ If your versioning scheme is based on the request URL, you will also want to alt
|
|||
[cite]: http://www.slideshare.net/evolve_conference/201308-fielding-evolve/31
|
||||
[roy-fielding-on-versioning]: http://www.infoq.com/articles/roy-fielding-on-versioning
|
||||
[klabnik-guidelines]: http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned
|
||||
[heroku-guidelines]: https://github.com/interagent/http-api-design#version-with-accepts-header
|
||||
[heroku-guidelines]: https://github.com/interagent/http-api-design/blob/master/en/foundations/require-versioning-in-the-accepts-header.md
|
||||
[json-parameters]: http://tools.ietf.org/html/rfc4627#section-6
|
||||
[vendor-media-type]: http://en.wikipedia.org/wiki/Internet_media_type#Vendor_tree
|
||||
[lvh]: https://reinteractive.net/posts/199-developing-and-testing-rails-applications-with-subdomains
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 567 KiB |
|
@ -14,7 +14,7 @@ The most common way to document Web APIs today is to produce documentation that
|
|||
|
||||
#### DRF Docs
|
||||
|
||||
[DRF Docs][drfdocs-repo] allows you to document Web APIs made with Django REST Framework and it is authored by Emmanouil Konstantinidis. It's made to work out of the box and its setup should not take more than a couple of minutes. Complete documentation can be found on the [website][drfdocs-website] while there is also a [demo][drfdocs-demo] available for people to see what it looks like.
|
||||
[DRF Docs][drfdocs-repo] allows you to document Web APIs made with Django REST Framework and it is authored by Emmanouil Konstantinidis. It's made to work out of the box and its setup should not take more than a couple of minutes. Complete documentation can be found on the [website][drfdocs-website] while there is also a [demo][drfdocs-demo] available for people to see what it looks like. **Live API Endpoints** allow you to utilize the endpoints from within the documentation in a neat way.
|
||||
|
||||
Features include customizing the template with your branding, settings for hiding the docs depending on the environment and more.
|
||||
|
||||
|
|
|
@ -66,9 +66,10 @@ The following view demonstrates an example of using a serializer in a template f
|
|||
|
||||
def post(self, request, pk):
|
||||
profile = get_object_or_404(Profile, pk=pk)
|
||||
serializer = ProfileSerializer(profile)
|
||||
serializer = ProfileSerializer(profile, data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response({'serializer': serializer, 'profile': profile})
|
||||
serializer.save()
|
||||
return redirect('profile-list')
|
||||
|
||||
**profile_detail.html**:
|
||||
|
|
|
@ -40,6 +40,12 @@ You can determine your currently installed version using `pip freeze`:
|
|||
|
||||
## 3.3.x series
|
||||
|
||||
### 3.4
|
||||
|
||||
**Unreleased**
|
||||
|
||||
* Dropped support for EOL Django 1.7 ([#3933][gh3933])
|
||||
|
||||
### 3.3.2
|
||||
|
||||
**Date**: [14th December 2015][3.3.2-milestone].
|
||||
|
|
|
@ -45,7 +45,7 @@ We'll need to add our new `snippets` app and the `rest_framework` app to `INSTAL
|
|||
INSTALLED_APPS = (
|
||||
...
|
||||
'rest_framework',
|
||||
'snippets',
|
||||
'snippets.apps.SnippetsConfig',
|
||||
)
|
||||
|
||||
Okay, we're ready to roll.
|
||||
|
@ -159,7 +159,7 @@ Deserialization is similar. First we parse a stream into Python native datatype
|
|||
stream = BytesIO(content)
|
||||
data = JSONParser().parse(stream)
|
||||
|
||||
...then we restore those native datatypes into to a fully populated object instance.
|
||||
...then we restore those native datatypes into a fully populated object instance.
|
||||
|
||||
serializer = SnippetSerializer(data=data)
|
||||
serializer.is_valid()
|
||||
|
@ -325,7 +325,7 @@ Quit out of the shell...
|
|||
|
||||
In another terminal window, we can test the server.
|
||||
|
||||
We can test our API using using [curl][curl] or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
|
||||
We can test our API using [curl][curl] or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
|
||||
|
||||
You can install httpie using pip:
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ If we try to create a snippet without authenticating, we'll get an error:
|
|||
|
||||
We can make a successful request by including the username and password of one of the users we created earlier.
|
||||
|
||||
http -a tom:password POST http://127.0.0.1:8000/snippets/ code="print 789"
|
||||
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
|
||||
|
||||
{
|
||||
"id": 5,
|
||||
|
|
|
@ -11,7 +11,7 @@ Right now we have endpoints for 'snippets' and 'users', but we don't have a sing
|
|||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
@api_view(('GET',))
|
||||
@api_view(['GET'])
|
||||
def api_root(request, format=None):
|
||||
return Response({
|
||||
'users': reverse('user-list', request=request, format=format),
|
||||
|
|
|
@ -28,7 +28,7 @@ Now sync your database for the first time:
|
|||
|
||||
python manage.py migrate
|
||||
|
||||
We'll also create an initial user named `admin` with a password of `password`. We'll authenticate as that user later in our example.
|
||||
We'll also create an initial user named `admin` with a password of `password123`. We'll authenticate as that user later in our example.
|
||||
|
||||
python manage.py createsuperuser
|
||||
|
||||
|
@ -134,7 +134,7 @@ We're now ready to test the API we've built. Let's fire up the server from the
|
|||
|
||||
We can now access our API, both from the command-line, using tools like `curl`...
|
||||
|
||||
bash: curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
|
||||
bash: curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
|
||||
{
|
||||
"count": 2,
|
||||
"next": null,
|
||||
|
@ -157,7 +157,7 @@ We can now access our API, both from the command-line, using tools like `curl`..
|
|||
|
||||
Or using the [httpie][httpie], command line tool...
|
||||
|
||||
bash: http -a username:password http://127.0.0.1:8000/users/
|
||||
bash: http -a admin:password123 http://127.0.0.1:8000/users/
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
...
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Optional packages which may be used with REST framework.
|
||||
markdown==2.5.2
|
||||
markdown==2.6.4
|
||||
django-guardian==1.3.2
|
||||
django-filter==0.10.0
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig'
|
7
rest_framework/authtoken/apps.py
Normal file
7
rest_framework/authtoken/apps.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class AuthTokenConfig(AppConfig):
|
||||
name = 'rest_framework.authtoken'
|
||||
verbose_name = _("Auth Token")
|
|
@ -4,6 +4,7 @@ import os
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# Prior to Django 1.5, the AUTH_USER_MODEL setting does not exist.
|
||||
# Note that we don't perform this code in the compat module due to
|
||||
|
@ -17,10 +18,20 @@ class Token(models.Model):
|
|||
"""
|
||||
The default authorization token model.
|
||||
"""
|
||||
key = models.CharField(max_length=40, primary_key=True)
|
||||
key = models.CharField(_("Key"), max_length=40, primary_key=True)
|
||||
user = models.OneToOneField(AUTH_USER_MODEL, related_name='auth_token',
|
||||
on_delete=models.CASCADE)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
on_delete=models.CASCADE, verbose_name=_("User"))
|
||||
created = models.DateTimeField(_("Created"), auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
# Work around for a bug in Django:
|
||||
# https://code.djangoproject.com/ticket/19422
|
||||
#
|
||||
# Also see corresponding ticket:
|
||||
# https://github.com/tomchristie/django-rest-framework/issues/705
|
||||
abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
|
||||
verbose_name = _("Token")
|
||||
verbose_name_plural = _("Tokens")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.key:
|
||||
|
|
|
@ -5,8 +5,8 @@ from rest_framework import serializers
|
|||
|
||||
|
||||
class AuthTokenSerializer(serializers.Serializer):
|
||||
username = serializers.CharField()
|
||||
password = serializers.CharField(style={'input_type': 'password'})
|
||||
username = serializers.CharField(label=_("Username"))
|
||||
password = serializers.CharField(label=_("Password"), style={'input_type': 'password'})
|
||||
|
||||
def validate(self, attrs):
|
||||
username = attrs.get('username')
|
||||
|
|
|
@ -86,18 +86,6 @@ except ImportError:
|
|||
crispy_forms = None
|
||||
|
||||
|
||||
if django.VERSION >= (1, 6):
|
||||
def clean_manytomany_helptext(text):
|
||||
return text
|
||||
else:
|
||||
# Up to version 1.5 many to many fields automatically suffix
|
||||
# the `help_text` attribute with hardcoded text.
|
||||
def clean_manytomany_helptext(text):
|
||||
if text.endswith(' Hold down "Control", or "Command" on a Mac, to select more than one.'):
|
||||
text = text[:-69]
|
||||
return text
|
||||
|
||||
|
||||
# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS
|
||||
# Fixes (#1712). We keep the try/except for the test suite.
|
||||
guardian = None
|
||||
|
@ -109,41 +97,6 @@ except ImportError:
|
|||
pass
|
||||
|
||||
|
||||
# MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.core.validators import MinLengthValidator, MaxLengthValidator
|
||||
else:
|
||||
from django.core.validators import MinValueValidator as DjangoMinValueValidator
|
||||
from django.core.validators import MaxValueValidator as DjangoMaxValueValidator
|
||||
from django.core.validators import MinLengthValidator as DjangoMinLengthValidator
|
||||
from django.core.validators import MaxLengthValidator as DjangoMaxLengthValidator
|
||||
|
||||
|
||||
class MinValueValidator(DjangoMinValueValidator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.message = kwargs.pop('message', self.message)
|
||||
super(MinValueValidator, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class MaxValueValidator(DjangoMaxValueValidator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.message = kwargs.pop('message', self.message)
|
||||
super(MaxValueValidator, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class MinLengthValidator(DjangoMinLengthValidator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.message = kwargs.pop('message', self.message)
|
||||
super(MinLengthValidator, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class MaxLengthValidator(DjangoMaxLengthValidator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.message = kwargs.pop('message', self.message)
|
||||
super(MaxLengthValidator, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
# PATCH method is not implemented by Django
|
||||
if 'patch' not in View.http_method_names:
|
||||
View.http_method_names = View.http_method_names + ['patch']
|
||||
|
@ -153,16 +106,25 @@ if 'patch' not in View.http_method_names:
|
|||
try:
|
||||
import markdown
|
||||
|
||||
if markdown.version <= '2.2':
|
||||
HEADERID_EXT_PATH = 'headerid'
|
||||
else:
|
||||
HEADERID_EXT_PATH = 'markdown.extensions.headerid'
|
||||
|
||||
def apply_markdown(text):
|
||||
"""
|
||||
Simple wrapper around :func:`markdown.markdown` to set the base level
|
||||
of '#' style headers to <h2>.
|
||||
"""
|
||||
|
||||
extensions = ['headerid(level=2)']
|
||||
safe_mode = False
|
||||
md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode)
|
||||
extensions = [HEADERID_EXT_PATH]
|
||||
extension_configs = {
|
||||
HEADERID_EXT_PATH: {
|
||||
'level': '2'
|
||||
}
|
||||
}
|
||||
md = markdown.Markdown(
|
||||
extensions=extensions, extension_configs=extension_configs
|
||||
)
|
||||
return md.convert(text)
|
||||
except ImportError:
|
||||
apply_markdown = None
|
||||
|
@ -179,13 +141,6 @@ else:
|
|||
LONG_SEPARATORS = (b', ', b': ')
|
||||
INDENT_SEPARATORS = (b',', b': ')
|
||||
|
||||
if django.VERSION >= (1, 8):
|
||||
from django.db.models import DurationField
|
||||
from django.utils.dateparse import parse_duration
|
||||
from django.utils.duration import duration_string
|
||||
else:
|
||||
DurationField = duration_string = parse_duration = None
|
||||
|
||||
try:
|
||||
# DecimalValidator is unavailable in Django < 1.9
|
||||
from django.core.validators import DecimalValidator
|
||||
|
@ -214,14 +169,14 @@ def template_render(template, context=None, request=None):
|
|||
"""
|
||||
Passing Context or RequestContext to Template.render is deprecated in 1.9+,
|
||||
see https://github.com/django/django/pull/3883 and
|
||||
https://github.com/django/django/blob/1.9rc1/django/template/backends/django.py#L82-L84
|
||||
https://github.com/django/django/blob/1.9/django/template/backends/django.py#L82-L84
|
||||
|
||||
:param template: Template instance
|
||||
:param context: dict
|
||||
:param request: Request instance
|
||||
:return: rendered template as SafeText instance
|
||||
"""
|
||||
if django.VERSION < (1, 8) or isinstance(template, Template):
|
||||
if isinstance(template, Template):
|
||||
if request:
|
||||
context = RequestContext(request, context)
|
||||
else:
|
||||
|
@ -230,32 +185,3 @@ def template_render(template, context=None, request=None):
|
|||
# backends template, e.g. django.template.backends.django.Template
|
||||
else:
|
||||
return template.render(context, request=request)
|
||||
|
||||
|
||||
def get_all_related_objects(opts):
|
||||
"""
|
||||
Django 1.8 changed meta api, see
|
||||
https://docs.djangoproject.com/en/1.8/ref/models/meta/#migrating-old-meta-api
|
||||
https://code.djangoproject.com/ticket/12663
|
||||
https://github.com/django/django/pull/3848
|
||||
|
||||
:param opts: Options instance
|
||||
:return: list of relations except many-to-many ones
|
||||
"""
|
||||
if django.VERSION < (1, 9):
|
||||
return opts.get_all_related_objects()
|
||||
else:
|
||||
return [r for r in opts.related_objects if not r.field.many_to_many]
|
||||
|
||||
|
||||
def get_all_related_many_to_many_objects(opts):
|
||||
"""
|
||||
Django 1.8 changed meta api, see docstr in compat.get_all_related_objects()
|
||||
|
||||
:param opts: Options instance
|
||||
:return: list of many-to-many relations
|
||||
"""
|
||||
if django.VERSION < (1, 9):
|
||||
return opts.get_all_related_many_to_many_objects()
|
||||
else:
|
||||
return [r for r in opts.related_objects if r.field.many_to_many]
|
||||
|
|
|
@ -28,7 +28,7 @@ def _force_text_recursive(data):
|
|||
]
|
||||
if isinstance(data, ReturnList):
|
||||
return ReturnList(ret, serializer=data.serializer)
|
||||
return data
|
||||
return ret
|
||||
elif isinstance(data, dict):
|
||||
ret = {
|
||||
key: _force_text_recursive(value)
|
||||
|
@ -36,7 +36,7 @@ def _force_text_recursive(data):
|
|||
}
|
||||
if isinstance(data, ReturnDict):
|
||||
return ReturnDict(ret, serializer=data.serializer)
|
||||
return data
|
||||
return ret
|
||||
return force_text(data)
|
||||
|
||||
|
||||
|
|
|
@ -14,23 +14,23 @@ from django.conf import settings
|
|||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.validators import (
|
||||
EmailValidator, RegexValidator, URLValidator, ip_address_validators
|
||||
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
|
||||
MinValueValidator, RegexValidator, URLValidator, ip_address_validators
|
||||
)
|
||||
from django.forms import FilePathField as DjangoFilePathField
|
||||
from django.forms import ImageField as DjangoImageField
|
||||
from django.utils import six, timezone
|
||||
from django.utils.dateparse import parse_date, parse_datetime, parse_time
|
||||
from django.utils.dateparse import (
|
||||
parse_date, parse_datetime, parse_duration, parse_time
|
||||
)
|
||||
from django.utils.duration import duration_string
|
||||
from django.utils.encoding import is_protected_type, smart_text
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.ipv6 import clean_ipv6_address
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import ISO_8601
|
||||
from rest_framework.compat import (
|
||||
MaxLengthValidator, MaxValueValidator, MinLengthValidator,
|
||||
MinValueValidator, duration_string, parse_duration, unicode_repr,
|
||||
unicode_to_repr
|
||||
)
|
||||
from rest_framework.compat import unicode_repr, unicode_to_repr
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils import html, humanize_datetime, representation
|
||||
|
@ -117,9 +117,9 @@ def to_choices_dict(choices):
|
|||
"""
|
||||
Convert choices into key/value dicts.
|
||||
|
||||
pairwise_choices([1]) -> {1: 1}
|
||||
pairwise_choices([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
|
||||
pairwise_choices([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2nd'}}
|
||||
to_choices_dict([1]) -> {1: 1}
|
||||
to_choices_dict([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
|
||||
to_choices_dict([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2nd'}}
|
||||
"""
|
||||
# Allow single, paired or grouped choices style:
|
||||
# choices = [1, 2, 3]
|
||||
|
@ -145,8 +145,8 @@ def flatten_choices_dict(choices):
|
|||
"""
|
||||
Convert a group choices dict into a flat dict of choices.
|
||||
|
||||
flatten_choices({1: '1st', 2: '2nd'}) -> {1: '1st', 2: '2nd'}
|
||||
flatten_choices({'Group': {1: '1st', 2: '2nd'}}) -> {1: '1st', 2: '2nd'}
|
||||
flatten_choices_dict({1: '1st', 2: '2nd'}) -> {1: '1st', 2: '2nd'}
|
||||
flatten_choices_dict({'Group': {1: '1st', 2: '2nd'}}) -> {1: '1st', 2: '2nd'}
|
||||
"""
|
||||
ret = OrderedDict()
|
||||
for key, value in choices.items():
|
||||
|
@ -370,6 +370,8 @@ class Field(object):
|
|||
Return a value to use when the field is being returned as a primitive
|
||||
value, without any object instance.
|
||||
"""
|
||||
if callable(self.initial):
|
||||
return self.initial()
|
||||
return self.initial
|
||||
|
||||
def get_value(self, dictionary):
|
||||
|
@ -1215,12 +1217,6 @@ class DurationField(Field):
|
|||
'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if parse_duration is None:
|
||||
raise NotImplementedError(
|
||||
'DurationField not supported for django versions prior to 1.8')
|
||||
return super(DurationField, self).__init__(*args, **kwargs)
|
||||
|
||||
def to_internal_value(self, value):
|
||||
if isinstance(value, datetime.timedelta):
|
||||
return value
|
||||
|
|
|
@ -21,6 +21,15 @@ from rest_framework.reverse import reverse
|
|||
from rest_framework.utils import html
|
||||
|
||||
|
||||
def method_overridden(method_name, klass, instance):
|
||||
"""
|
||||
Determine if a method has been overridden.
|
||||
"""
|
||||
method = getattr(klass, method_name)
|
||||
default_method = getattr(method, '__func__', method) # Python 3 compat
|
||||
return default_method is not getattr(instance, method_name).__func__
|
||||
|
||||
|
||||
class Hyperlink(six.text_type):
|
||||
"""
|
||||
A string like object that additionally has an associated name.
|
||||
|
@ -65,9 +74,11 @@ class RelatedField(Field):
|
|||
self.queryset = kwargs.pop('queryset', self.queryset)
|
||||
self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff)
|
||||
self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text)
|
||||
|
||||
if not method_overridden('get_queryset', RelatedField, self):
|
||||
assert self.queryset is not None or kwargs.get('read_only', None), (
|
||||
'Relational field must provide a `queryset` argument, '
|
||||
'or set read_only=`True`.'
|
||||
'override `get_queryset`, or set read_only=`True`.'
|
||||
)
|
||||
assert not (self.queryset is not None and kwargs.get('read_only', None)), (
|
||||
'Relational fields should not provide a `queryset` argument, '
|
||||
|
@ -269,7 +280,7 @@ class HyperlinkedRelatedField(RelatedField):
|
|||
attributes are not configured to correctly match the URL conf.
|
||||
"""
|
||||
# Unsaved objects will not yet have a valid URL.
|
||||
if hasattr(obj, 'pk') and obj.pk is None:
|
||||
if hasattr(obj, 'pk') and obj.pk in (None, ''):
|
||||
return None
|
||||
|
||||
lookup_value = getattr(obj, self.lookup_field)
|
||||
|
|
|
@ -11,7 +11,6 @@ from __future__ import unicode_literals
|
|||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
import django
|
||||
from django import forms
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.paginator import Page
|
||||
|
@ -341,6 +340,7 @@ class HTMLFormRenderer(BaseRenderer):
|
|||
"""
|
||||
Render serializer data and return an HTML form, as a string.
|
||||
"""
|
||||
renderer_context = renderer_context or {}
|
||||
form = data.serializer
|
||||
|
||||
style = renderer_context.get('style', {})
|
||||
|
@ -772,7 +772,7 @@ class MultiPartRenderer(BaseRenderer):
|
|||
media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
|
||||
format = 'multipart'
|
||||
charset = 'utf-8'
|
||||
BOUNDARY = 'BoUnDaRyStRiNg' if django.VERSION >= (1, 5) else b'BoUnDaRyStRiNg'
|
||||
BOUNDARY = 'BoUnDaRyStRiNg'
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
if hasattr(data, 'items'):
|
||||
|
|
|
@ -15,12 +15,12 @@ from __future__ import unicode_literals
|
|||
import warnings
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import DurationField as ModelDurationField
|
||||
from django.db.models.fields import Field as DjangoModelField
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework.compat import DurationField as ModelDurationField
|
||||
from rest_framework.compat import JSONField as ModelJSONField
|
||||
from rest_framework.compat import postgres_fields, unicode_to_repr
|
||||
from rest_framework.utils import model_meta
|
||||
|
@ -715,7 +715,7 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
|
|||
isinstance(validated_data[key], (list, dict))
|
||||
for key, field in serializer.fields.items()
|
||||
), (
|
||||
'The `.{method_name}()` method does not support writable nested'
|
||||
'The `.{method_name}()` method does not support writable nested '
|
||||
'fields by default.\nWrite an explicit `.{method_name}()` method for '
|
||||
'serializer `{module}.{class_name}`, or set `read_only=True` on '
|
||||
'nested serializer fields.'.format(
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<span>
|
||||
{% block branding %}
|
||||
<a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
|
||||
Django REST framework <span class="version">{{ version }}</span>
|
||||
Django REST framework
|
||||
</a>
|
||||
{% endblock %}
|
||||
</span>
|
||||
|
@ -166,6 +166,7 @@
|
|||
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<div class="modal-body">
|
||||
<fieldset>
|
||||
{% csrf_token %}
|
||||
{{ post_form }}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<span>
|
||||
{% block branding %}
|
||||
<a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
|
||||
Django REST framework <span class="version">{{ version }}</span>
|
||||
Django REST framework
|
||||
</a>
|
||||
{% endblock %}
|
||||
</span>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<input type="text" name="username" maxlength="100"
|
||||
autocapitalize="off"
|
||||
autocorrect="off" class="form-control textinput textInput"
|
||||
id="id_username" required
|
||||
id="id_username" required autofocus
|
||||
{% if form.username.value %}value="{{ form.username.value }}"{% endif %}>
|
||||
{% if form.username.errors %}
|
||||
<p class="text-error">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="{{ field.name }}" value="true" {% if value %}checked{% endif %}>
|
||||
<input type="checkbox" name="{{ field.name }}" value="true" {% if field.value %}checked{% endif %}>
|
||||
{% if field.label %}{{ field.label }}{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
# to make it harder for the user to import the wrong thing without realizing.
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.test import testcases
|
||||
from django.test.client import Client as DjangoClient
|
||||
|
@ -223,9 +222,9 @@ class APITestCase(testcases.TestCase):
|
|||
client_class = APIClient
|
||||
|
||||
|
||||
if django.VERSION >= (1, 4):
|
||||
class APISimpleTestCase(testcases.SimpleTestCase):
|
||||
class APISimpleTestCase(testcases.SimpleTestCase):
|
||||
client_class = APIClient
|
||||
|
||||
class APILiveServerTestCase(testcases.LiveServerTestCase):
|
||||
|
||||
class APILiveServerTestCase(testcases.LiveServerTestCase):
|
||||
client_class = APIClient
|
||||
|
|
|
@ -13,10 +13,6 @@ from django.core.exceptions import ImproperlyConfigured
|
|||
from django.db import models
|
||||
from django.utils import six
|
||||
|
||||
from rest_framework.compat import (
|
||||
get_all_related_many_to_many_objects, get_all_related_objects
|
||||
)
|
||||
|
||||
FieldInfo = namedtuple('FieldResult', [
|
||||
'pk', # Model field instance
|
||||
'fields', # Dict of field name -> model field instance
|
||||
|
@ -138,7 +134,8 @@ def _get_reverse_relationships(opts):
|
|||
# See: https://code.djangoproject.com/ticket/24208
|
||||
|
||||
reverse_relations = OrderedDict()
|
||||
for relation in get_all_related_objects(opts):
|
||||
all_related_objects = [r for r in opts.related_objects if not r.field.many_to_many]
|
||||
for relation in all_related_objects:
|
||||
accessor_name = relation.get_accessor_name()
|
||||
related = getattr(relation, 'related_model', relation.model)
|
||||
reverse_relations[accessor_name] = RelationInfo(
|
||||
|
@ -150,7 +147,8 @@ def _get_reverse_relationships(opts):
|
|||
)
|
||||
|
||||
# Deal with reverse many-to-many relationships.
|
||||
for relation in get_all_related_many_to_many_objects(opts):
|
||||
all_related_many_to_many_objects = [r for r in opts.related_objects if r.field.many_to_many]
|
||||
for relation in all_related_many_to_many_objects:
|
||||
accessor_name = relation.get_accessor_name()
|
||||
related = getattr(relation, 'related_model', relation.model)
|
||||
reverse_relations[accessor_name] = RelationInfo(
|
||||
|
|
21
tests/test_exceptions.py
Normal file
21
tests/test_exceptions.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework.exceptions import _force_text_recursive
|
||||
|
||||
|
||||
class ExceptionTestCase(TestCase):
|
||||
|
||||
def test_force_text_recursive(self):
|
||||
|
||||
s = "sfdsfggiuytraetfdlklj"
|
||||
self.assertEqual(_force_text_recursive(_(s)), s)
|
||||
self.assertEqual(type(_force_text_recursive(_(s))), type(s))
|
||||
|
||||
self.assertEqual(_force_text_recursive({'a': _(s)})['a'], s)
|
||||
self.assertEqual(type(_force_text_recursive({'a': _(s)})['a']), type(s))
|
||||
|
||||
self.assertEqual(_force_text_recursive([[_(s)]])[0][0], s)
|
||||
self.assertEqual(type(_force_text_recursive([[_(s)]])[0][0]), type(s))
|
|
@ -3,7 +3,6 @@ import os
|
|||
import uuid
|
||||
from decimal import Decimal
|
||||
|
||||
import django
|
||||
import pytest
|
||||
from django.http import QueryDict
|
||||
from django.utils import six, timezone
|
||||
|
@ -191,6 +190,24 @@ class TestInitial:
|
|||
}
|
||||
|
||||
|
||||
class TestInitialWithCallable:
|
||||
def setup(self):
|
||||
def initial_value():
|
||||
return 123
|
||||
|
||||
class TestSerializer(serializers.Serializer):
|
||||
initial_field = serializers.IntegerField(initial=initial_value)
|
||||
self.serializer = TestSerializer()
|
||||
|
||||
def test_initial_should_accept_callable(self):
|
||||
"""
|
||||
Follows the default ``Field.initial`` behaviour where they accept a
|
||||
callable to produce the initial value"""
|
||||
assert self.serializer.data == {
|
||||
'initial_field': 123,
|
||||
}
|
||||
|
||||
|
||||
class TestLabel:
|
||||
def setup(self):
|
||||
class TestSerializer(serializers.Serializer):
|
||||
|
@ -951,7 +968,7 @@ class TestDateTimeField(FieldValues):
|
|||
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
# Django 1.4 does not support timezone string parsing.
|
||||
'2001-01-01T14:00+01:00' if (django.VERSION > (1, 4)) else '2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
|
||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
|
||||
}
|
||||
invalid_inputs = {
|
||||
'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
|
||||
|
@ -1077,8 +1094,6 @@ class TestNoOutputFormatTimeField(FieldValues):
|
|||
field = serializers.TimeField(format=None)
|
||||
|
||||
|
||||
@pytest.mark.skipif(django.VERSION < (1, 8),
|
||||
reason='DurationField is only available for django1.8+')
|
||||
class TestDurationField(FieldValues):
|
||||
"""
|
||||
Valid and invalid values for `DurationField`.
|
||||
|
@ -1097,7 +1112,6 @@ class TestDurationField(FieldValues):
|
|||
outputs = {
|
||||
datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): '3 08:32:01.000123',
|
||||
}
|
||||
if django.VERSION >= (1, 8):
|
||||
field = serializers.DurationField()
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import django
|
||||
import pytest
|
||||
from django.db import models
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
@ -158,7 +157,7 @@ class TestRootView(TestCase):
|
|||
self.assertIn(expected_error, response.rendered_content.decode('utf-8'))
|
||||
|
||||
|
||||
EXPECTED_QUERIES_FOR_PUT = 3 if django.VERSION < (1, 6) else 2
|
||||
EXPECTED_QUERIES_FOR_PUT = 2
|
||||
|
||||
|
||||
class TestInstanceView(TestCase):
|
||||
|
|
|
@ -10,18 +10,16 @@ from __future__ import unicode_literals
|
|||
import decimal
|
||||
from collections import OrderedDict
|
||||
|
||||
import django
|
||||
import pytest
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.validators import (
|
||||
MaxValueValidator, MinLengthValidator, MinValueValidator
|
||||
)
|
||||
from django.db import models
|
||||
from django.db.models import DurationField as ModelDurationField
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.compat import DurationField as ModelDurationField
|
||||
from rest_framework.compat import unicode_repr
|
||||
|
||||
|
||||
|
@ -341,8 +339,6 @@ class TestRegularFieldMappings(TestCase):
|
|||
assert implicit.data == explicit.data
|
||||
|
||||
|
||||
@pytest.mark.skipif(django.VERSION < (1, 8),
|
||||
reason='DurationField is only available for django1.8+')
|
||||
class TestDurationFieldMapping(TestCase):
|
||||
def test_duration_field(self):
|
||||
class DurationFieldModel(models.Model):
|
||||
|
|
|
@ -87,6 +87,18 @@ class TestProxiedPrimaryKeyRelatedField(APISimpleTestCase):
|
|||
assert representation == self.instance.pk.int
|
||||
|
||||
|
||||
class TestHyperlinkedRelatedField(APISimpleTestCase):
|
||||
def setUp(self):
|
||||
self.field = serializers.HyperlinkedRelatedField(
|
||||
view_name='example', read_only=True)
|
||||
self.field.reverse = mock_reverse
|
||||
self.field._context = {'request': True}
|
||||
|
||||
def test_representation_unsaved_object_with_non_nullable_pk(self):
|
||||
representation = self.field.to_representation(MockObject(pk=''))
|
||||
assert representation is None
|
||||
|
||||
|
||||
class TestHyperlinkedIdentityField(APISimpleTestCase):
|
||||
def setUp(self):
|
||||
self.instance = MockObject(pk=1, name='foo')
|
||||
|
@ -176,6 +188,16 @@ class TestSlugRelatedField(APISimpleTestCase):
|
|||
representation = self.field.to_representation(self.instance)
|
||||
assert representation == self.instance.name
|
||||
|
||||
def test_overriding_get_queryset(self):
|
||||
qs = self.queryset
|
||||
|
||||
class NoQuerySetSlugRelatedField(serializers.SlugRelatedField):
|
||||
def get_queryset(self):
|
||||
return qs
|
||||
|
||||
field = NoQuerySetSlugRelatedField(slug_field='name')
|
||||
field.to_internal_value(self.instance.name)
|
||||
|
||||
|
||||
class TestManyRelatedField(APISimpleTestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.core.cache import cache
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
from django.utils.safestring import SafeText
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import permissions, serializers, status
|
||||
|
@ -459,3 +460,28 @@ class TestHiddenFieldHTMLFormRenderer(TestCase):
|
|||
field = serializer['published']
|
||||
rendered = renderer.render_field(field, {})
|
||||
assert rendered == ''
|
||||
|
||||
|
||||
class TestHTMLFormRenderer(TestCase):
|
||||
def setUp(self):
|
||||
class TestSerializer(serializers.Serializer):
|
||||
test_field = serializers.CharField()
|
||||
|
||||
self.renderer = HTMLFormRenderer()
|
||||
self.serializer = TestSerializer(data={})
|
||||
|
||||
def test_render_with_default_args(self):
|
||||
self.serializer.is_valid()
|
||||
renderer = HTMLFormRenderer()
|
||||
|
||||
result = renderer.render(self.serializer.data)
|
||||
|
||||
self.assertIsInstance(result, SafeText)
|
||||
|
||||
def test_render_with_provided_args(self):
|
||||
self.serializer.is_valid()
|
||||
renderer = HTMLFormRenderer()
|
||||
|
||||
result = renderer.render(self.serializer.data, None, {})
|
||||
|
||||
self.assertIsInstance(result, SafeText)
|
||||
|
|
|
@ -3,8 +3,6 @@ Tests for content parsing, and form-overloaded content parsing.
|
|||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django
|
||||
import pytest
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -201,8 +199,6 @@ class TestAuthSetter(TestCase):
|
|||
self.assertEqual(request.auth, 'DUMMY')
|
||||
|
||||
|
||||
@pytest.mark.skipif(django.VERSION < (1, 7),
|
||||
reason='secure argument is only available for django1.7+')
|
||||
class TestSecure(TestCase):
|
||||
|
||||
def test_default_secure_false(self):
|
||||
|
|
9
tox.ini
9
tox.ini
|
@ -4,17 +4,17 @@ addopts=--tb=short
|
|||
[tox]
|
||||
envlist =
|
||||
py27-{lint,docs},
|
||||
{py27,py32,py33,py34}-django{17,18},
|
||||
{py27,py32,py33,py34}-django18,
|
||||
{py27,py34,py35}-django{19}
|
||||
|
||||
[testenv]
|
||||
commands = ./runtests.py --fast {posargs} --coverage -rw
|
||||
setenv =
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
PYTHONWARNINGS=once
|
||||
deps =
|
||||
django17: Django==1.7.11
|
||||
django18: Django==1.8.7
|
||||
django19: Django==1.9
|
||||
django18: Django==1.8.10
|
||||
django19: Django==1.9.3
|
||||
-rrequirements/requirements-testing.txt
|
||||
-rrequirements/requirements-optionals.txt
|
||||
|
||||
|
@ -29,4 +29,3 @@ commands = mkdocs build
|
|||
deps =
|
||||
-rrequirements/requirements-testing.txt
|
||||
-rrequirements/requirements-documentation.txt
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user