diff options
| author | Tom Christie | 2014-08-29 10:03:07 +0100 | 
|---|---|---|
| committer | Tom Christie | 2014-08-29 10:03:07 +0100 | 
| commit | 8f4ae06b3b3b9572d72529ffad1842f63ca67d91 (patch) | |
| tree | 2fc2c0ce267ae04e69213a810286c8b47b02826f | |
| parent | bb1604674f05c0fa5c62e7bb3e7b9d12fd9bf648 (diff) | |
| parent | 8b2052172cf7138203e683731c30bd279c6e722a (diff) | |
| download | django-rest-framework-8f4ae06b3b3b9572d72529ffad1842f63ca67d91.tar.bz2 | |
Merge pull request #1784 from tomchristie/remove-model-attribute
Deprecate `.model` attribute on views
| -rw-r--r-- | README.md | 74 | ||||
| -rwxr-xr-x | docs/api-guide/generic-views.md | 8 | ||||
| -rw-r--r-- | docs/api-guide/permissions.md | 10 | ||||
| -rw-r--r-- | docs/api-guide/routers.md | 4 | ||||
| -rw-r--r-- | docs/api-guide/settings.md | 6 | ||||
| -rw-r--r-- | docs/index.md | 36 | ||||
| -rw-r--r-- | docs/topics/2.4-accouncement.md | 10 | ||||
| -rw-r--r-- | docs/topics/release-notes.md | 1 | ||||
| -rw-r--r-- | docs/tutorial/quickstart.md | 43 | ||||
| -rw-r--r-- | rest_framework/generics.py | 11 | ||||
| -rw-r--r-- | rest_framework/permissions.py | 3 | ||||
| -rw-r--r-- | rest_framework/routers.py | 5 | 
12 files changed, 132 insertions, 79 deletions
| @@ -39,40 +39,51 @@ Add `'rest_framework'` to your `INSTALLED_APPS` setting.      INSTALLED_APPS = (          ... -        'rest_framework',         +        'rest_framework',      )  # Example  Let's take a look at a quick example of using REST framework to build a simple model-backed API for accessing users and groups. -Here's our project's root `urls.py` module: +Startup up a new project like so...  + +    pip install django +    pip install djangorestframework +    django-admin startproject example . +    ./manage.py syncdb + +Now edit the `example/urls.py` module in your project:  ```python -from django.conf.urls.defaults import url, patterns, include -from django.contrib.auth.models import User, Group -from rest_framework import viewsets, routers +from django.conf.urls import url, include +from django.contrib.auth.models import User +from rest_framework import serializers, viewsets, routers + +# Serializers define the API representation. +class UserSerializer(serializers.HyperlinkedModelSerializer): +    class Meta: +        model = User +        fields = ('url', 'username', 'email', 'is_staff') +  # ViewSets define the view behavior.  class UserViewSet(viewsets.ModelViewSet): -    model = User - -class GroupViewSet(viewsets.ModelViewSet): -    model = Group +    queryset = User.objects.all() +    serializer_class = UserSerializer -# Routers provide an easy way of automatically determining the URL conf +# Routers provide a way of automatically determining the URL conf.  router = routers.DefaultRouter()  router.register(r'users', UserViewSet) -router.register(r'groups', GroupViewSet)  # Wire up our API using automatic URL routing.  # Additionally, we include login URLs for the browseable API. -urlpatterns = patterns('', +urlpatterns = [      url(r'^', include(router.urls)),      url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) -) +]  ```  We'd also like to configure a couple of settings for our API. @@ -80,12 +91,12 @@ We'd also like to configure a couple of settings for our API.  Add the following to your `settings.py` module:  ```python -REST_FRAMEWORK = { -    # Use hyperlinked styles by default. -    # Only used if the `serializer_class` attribute is not set on a view. -    'DEFAULT_MODEL_SERIALIZER_CLASS': -        'rest_framework.serializers.HyperlinkedModelSerializer', +INSTALLED_APPS = ( +    ...  # Make sure to include the default installed apps here. +    'rest_framework',         +) +REST_FRAMEWORK = {      # Use Django's standard `django.contrib.auth` permissions,      # or allow read-only access for unauthenticated users.      'DEFAULT_PERMISSION_CLASSES': [ @@ -93,10 +104,35 @@ REST_FRAMEWORK = {      ]  }  ``` -Don't forget to make sure you've also added `rest_framework` to your `INSTALLED_APPS` setting.  That's it, we're done! +    ./manage.py runserver + +You can now open the API in your browser at `http://127.0.0.1:8000/`, and view your new 'users' API. If you use the `Login` control in the top right corner you'll also be able to add, create and delete users from the system. + +You can also interact with the API using command line tools such as [`curl`](http://curl.haxx.se/). For example, to list the users endpoint: + +    $ curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/ +	[ +	    { +	        "url": "http://127.0.0.1:8000/users/1/",  +	        "username": "admin",  +	        "email": "admin@example.com",  +	        "is_staff": true,  +	    } +	] + +Or to create a new user: + +    $ curl -X POST -d username=new -d email=new@example.com -d is_staff=false -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/ +    { +        "url": "http://127.0.0.1:8000/users/2/",  +        "username": "new",  +        "email": "new@example.com",  +        "is_staff": false,  +    } +  # Documentation & Support  Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 2ceb2d57..b1c4e65a 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -74,10 +74,6 @@ The following attributes control the basic view behavior.  * `lookup_field` - The model field that should be used to for performing object lookup of individual model instances.  Defaults to `'pk'`.  Note that when using hyperlinked APIs you'll need to ensure that *both* the API views *and* the serializer classes set the lookup fields if you need to use a custom value.  * `lookup_url_kwarg` - The URL keyword argument that should be used for object lookup.  The URL conf should include a keyword argument corresponding to this value.  If unset this defaults to using the same value as `lookup_field`. -**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 `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**:  The following attributes are used to control pagination when used with list views. @@ -91,6 +87,10 @@ The following attributes are used to control pagination when used with list view  * `filter_backends` - A list of filter backend classes that should be used for filtering the queryset.  Defaults to the same value as the `DEFAULT_FILTER_BACKENDS` setting. +**Deprecated attributes**: + +* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes. The explicit style is preferred over the `.model` shortcut, and usage of this attribute is now deprecated. +  ### Methods  **Base methods**: diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 38ae3d0a..e867a456 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -114,7 +114,7 @@ This permission is suitable if you want to your API to allow read permissions to  ## DjangoModelPermissions -This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth].  When applied to a view that has a `.model` property, authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. +This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth].  This permission must only be applied to views that has a `.queryset` property set. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned.  * `POST` requests require the user to have the `add` permission on the model.  * `PUT` and `PATCH` requests require the user to have the `change` permission on the model. @@ -124,6 +124,12 @@ The default behaviour can also be overridden to support custom model permissions  To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property.  Refer to the source code for details. +#### Using with views that do not include a `queryset` attribute. + +If you're using this permission with a view that uses an overridden `get_queryset()` method there may not be a `queryset` attribute on the view. In this case we suggest also marking the view with a sential queryset, so that this class can determine the required permissions. For example: + +    queryset = User.objects.none()  # Required for DjangoModelPermissions +  ## DjangoModelPermissionsOrAnonReadOnly  Similar to `DjangoModelPermissions`, but also allows unauthenticated users to have read-only access to the API. @@ -132,7 +138,7 @@ Similar to `DjangoModelPermissions`, but also allows unauthenticated users to ha  This permission class ties into Django's standard [object permissions framework][objectpermissions] that allows per-object permissions on models.  In order to use this permission class, you'll also need to add a permission backend that supports object-level permissions, such as [django-guardian][guardian]. -When applied to a view that has a `.model` property, authorization will only be granted if the user *is authenticated* and has the *relevant per-object permissions* and *relevant model permissions* assigned. +As with `DjangoModelPermissions`, this permission must only be applied to views that have a `.queryset` property. Authorization will only be granted if the user *is authenticated* and has the *relevant per-object permissions* and *relevant model permissions* assigned.  * `POST` requests require the user to have the `add` permission on the model instance.  * `PUT` and `PATCH` requests require the user to have the `change` permission on the model instance. diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 2d760ca4..61a476b8 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -41,9 +41,9 @@ The example above would generate the following URL patterns:  **Note**: The `base_name` argument is used to specify the initial part of the view name pattern.  In the example above, that's the `user` or `account` part. -Typically you won't *need* to specify the `base-name` argument, but if you have a viewset where you've defined a custom `get_queryset` method, then the viewset may not have any `.model` or `.queryset` attribute set.  If you try to register that viewset you'll see an error like this: +Typically you won't *need* to specify the `base-name` argument, but if you have a viewset where you've defined a custom `get_queryset` method, then the viewset may not have a `.queryset` attribute set.  If you try to register that viewset you'll see an error like this: -    'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.model' or '.queryset' attribute. +    'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.  This means you'll need to explicitly set the `base_name` argument when registering the viewset, as it could not be automatically determined from the model name. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 8bde4d87..27a09163 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -100,12 +100,6 @@ Default: `'rest_framework.negotiation.DefaultContentNegotiation'`  *The following settings control the behavior of the generic class based views.* -#### DEFAULT_MODEL_SERIALIZER_CLASS - -A class that determines the default type of model serializer that should be used by a generic view if `model` is specified, but `serializer_class` is not provided. - -Default: `'rest_framework.serializers.ModelSerializer'` -  #### DEFAULT_PAGINATION_SERIALIZER_CLASS  A class the determines the default serialization style for paginated responses. diff --git a/docs/index.md b/docs/index.md index 83e30a69..60219c95 100644 --- a/docs/index.md +++ b/docs/index.md @@ -96,16 +96,11 @@ Note that the URL path can be whatever you want, but you must include `'rest_fra  Let's take a look at a quick example of using REST framework to build a simple model-backed API. -We'll create a read-write API for accessing users and groups. +We'll create a read-write API for accessing information on the users of our project.  Any global settings for a REST framework API are kept in a single configuration dictionary named `REST_FRAMEWORK`.  Start off by adding the following to your `settings.py` module:      REST_FRAMEWORK = { -        # Use hyperlinked styles by default. -        # Only used if the `serializer_class` attribute is not set on a view. -        'DEFAULT_MODEL_SERIALIZER_CLASS': -            'rest_framework.serializers.HyperlinkedModelSerializer', -          # Use Django's standard `django.contrib.auth` permissions,          # or allow read-only access for unauthenticated users.          'DEFAULT_PERMISSION_CLASSES': [ @@ -118,34 +113,37 @@ Don't forget to make sure you've also added `rest_framework` to your `INSTALLED_  We're ready to create our API now.  Here's our project's root `urls.py` module: -    from django.conf.urls import url, patterns, include -    from django.contrib.auth.models import User, Group -    from rest_framework import viewsets, routers +    from django.conf.urls import url, include +    from django.contrib.auth.models import User +    from rest_framework import routers, serializers, viewsets + +	# Serializers define the API representation. +	class UserSerializer(serializers.HyperlinkedModelSerializer): +	    class Meta: +	        model = User +	        fields = ('url', 'username', 'email', 'is_staff')      # ViewSets define the view behavior.      class UserViewSet(viewsets.ModelViewSet): -        model = User - -    class GroupViewSet(viewsets.ModelViewSet): -        model = Group - +        queryset = User.objects.all() +        serializer_class = UserSerializer      # Routers provide an easy way of automatically determining the URL conf.      router = routers.DefaultRouter()      router.register(r'users', UserViewSet) -    router.register(r'groups', GroupViewSet) -      # Wire up our API using automatic URL routing.      # Additionally, we include login URLs for the browseable API. -    urlpatterns = patterns('', +    urlpatterns = [          url(r'^', include(router.urls)),          url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) -    ) +    ] + +You can now open the API in your browser at [http://127.0.0.1:8000/](http://127.0.0.1:8000/), and view your new 'users' API. If you use the login control in the top right corner you'll also be able to add, create and delete users from the system.  ## Quickstart -Can't wait to get started?  The [quickstart guide][quickstart] is the fastest way to get up and running, and building APIs with REST framework. +Can't wait to get started? The [quickstart guide][quickstart] is the fastest way to get up and running, and building APIs with REST framework.  ## Tutorial diff --git a/docs/topics/2.4-accouncement.md b/docs/topics/2.4-accouncement.md index d8e264ff..b6936d2a 100644 --- a/docs/topics/2.4-accouncement.md +++ b/docs/topics/2.4-accouncement.md @@ -15,6 +15,16 @@ The optional authtoken application now includes support for *both* Django 1.7 sc  **If you are using authtoken, and you want to continue using `south`, you must upgrade your `south` package to version 1.0.** +## Deprecation of `.model` view attribute + +The `.model` attribute on view classes is an optional shortcut for either or both of `.serializer_class` and `.queryset`. It's usage results in more implicit, less obvious behavior. + +The documentation has previously stated that usage of the more explict style is prefered, and we're now taking that one step further and deprecating the usage of the `.model` shortcut. + +Doing so will mean that there are cases of API code where you'll now need to include a serializer class where you previously were just using the `.model` shortcut. However we firmly believe that it is the right trade-off to make. Removing the shortcut takes away an unneccessary layer of abstraction, and makes your codebase more explict without any significant extra complexity. + +The `DEFAULT_MODEL_SERIALIZER_CLASS` API setting is now also deprecated. +  ## Updated test runner  We now have a new test runner for developing against the project,, that uses the excellent [py.test](http://pytest.org) library. diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index a2b4782f..c158c47b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -48,6 +48,7 @@ You can determine your currently installed version using `pip freeze`:  * Added compatibility with Django 1.7's database migration support.  * New test runner, using `py.test`. +* Deprecated `.model` view attribute in favor of explict `.queryset` and `.serializer_class` attributes. The `DEFAULT_MODEL_SERIALIZER_CLASS` setting is also deprecated.  * `@detail_route` and `@list_route` decorators replace `@action` and `@link`.  * Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings.  * Added `NUM_PROXIES` setting for smarter client IP identification. diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 98e5f439..813e9872 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -18,34 +18,23 @@ Create a new Django project named `tutorial`, then start a new app called `quick      pip install django      pip install djangorestframework -    # Set up a new project -    django-admin.py startproject tutorial - -    # Create a new app -    python manage.py startapp quickstart - -Next you'll need to get a database set up and synced.  If you just want to use SQLite for now, then you'll want to edit your `tutorial/settings.py` module to include something like this: - -    DATABASES = { -        'default': { -            'ENGINE': 'django.db.backends.sqlite3', -            'NAME': 'database.sql', -            'USER': '', -            'PASSWORD': '', -            'HOST': '', -            'PORT': '' -        } -    } +    # Set up a new project with a single application +    django-admin.py startproject tutorial . +    cd tutorial +    django-admin.py startapp quickstart +	cd .. -The run `syncdb` like so: +Now sync your database for the first time:      python manage.py syncdb +Make sure to create an initial user named `admin` with a password of `password`. We'll authenticate as that user later in our example. +  Once you've set up a database and got everything synced and ready to go, open up the app's directory and we'll get coding...  ## Serializers -First up we're going to define some serializers in `quickstart/serializers.py` that we'll use for our data representations. +First up we're going to define some serializers. Let's create a new module named `tutorial/quickstart/serializers.py` that we'll use for our data representations.      from django.contrib.auth.models import User, Group      from rest_framework import serializers @@ -66,11 +55,11 @@ Notice that we're using hyperlinked relations in this case, with `HyperlinkedMod  ## Views -Right, we'd better write some views then.  Open `quickstart/views.py` and get typing. +Right, we'd better write some views then.  Open `tutorial/quickstart/views.py` and get typing.      from django.contrib.auth.models import User, Group      from rest_framework import viewsets -    from quickstart.serializers import UserSerializer, GroupSerializer +    from tutorial.quickstart.serializers import UserSerializer, GroupSerializer      class UserViewSet(viewsets.ModelViewSet): @@ -100,9 +89,9 @@ For trivial cases you can simply set a `model` attribute on the `ViewSet` class  Okay, now let's wire up the API URLs.  On to `tutorial/urls.py`... -    from django.conf.urls import patterns, url, include +    from django.conf.urls import url, include      from rest_framework import routers -    from quickstart import views +    from tutorial.quickstart import views      router = routers.DefaultRouter()      router.register(r'users', views.UserViewSet) @@ -110,10 +99,10 @@ Okay, now let's wire up the API URLs.  On to `tutorial/urls.py`...      # Wire up our API using automatic URL routing.      # Additionally, we include login URLs for the browseable API. -    urlpatterns = patterns('', +    urlpatterns = [          url(r'^', include(router.urls)),          url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) -    ) +    ]  Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class. @@ -172,6 +161,8 @@ Or directly through the browser...  ![Quick start image][image] +If you're working through the browser, make sure to login using the control in the top right corner. +  Great, that was easy!  If you want to get a more in depth understanding of how REST framework fits together head on over to [the tutorial][tutorial], or start browsing the [API guide][guide]. diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 77deb8e4..a6f68657 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -252,6 +252,12 @@ class GenericAPIView(views.APIView):          if serializer_class is not None:              return serializer_class +        warnings.warn( +            'The `.model` attribute on view classes is now deprecated in favor ' +            'of the more explicit `serializer_class` and `queryset` attributes.', +            DeprecationWarning, stacklevel=2 +        ) +          assert self.model is not None, \              "'%s' should either include a 'serializer_class' attribute, " \              "or use the 'model' attribute as a shortcut for " \ @@ -282,6 +288,11 @@ class GenericAPIView(views.APIView):              return self.queryset._clone()          if self.model is not None: +            warnings.warn( +                'The `.model` attribute on view classes is now deprecated in favor ' +                'of the more explicit `serializer_class` and `queryset` attributes.', +                DeprecationWarning, stacklevel=2 +            )              return self.model._default_manager.all()          error_format = "'%s' must define 'queryset' or 'model'" diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 6a1a0077..29f60d6d 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -108,6 +108,9 @@ class DjangoModelPermissions(BasePermission):          return [perm % kwargs for perm in self.perms_map[method]]      def has_permission(self, request, view): +        # Note that `.model` attribute on views is deprecated, although we +        # enforce the deprecation on the view `get_serializer_class()` and +        # `get_queryset()` methods, rather than here.          model_cls = getattr(view, 'model', None)          queryset = getattr(view, 'queryset', None) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 406ebcf7..ae56673d 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -128,6 +128,9 @@ class SimpleRouter(BaseRouter):          If `base_name` is not specified, attempt to automatically determine          it from the viewset.          """ +        # Note that `.model` attribute on views is deprecated, although we +        # enforce the deprecation on the view `get_serializer_class()` and +        # `get_queryset()` methods, rather than here.          model_cls = getattr(viewset, 'model', None)          queryset = getattr(viewset, 'queryset', None)          if model_cls is None and queryset is not None: @@ -135,7 +138,7 @@ class SimpleRouter(BaseRouter):          assert model_cls, '`base_name` argument not specified, and could ' \              'not automatically determine the name from the viewset, as ' \ -            'it does not have a `.model` or `.queryset` attribute.' +            'it does not have a `.queryset` attribute.'          return model_cls._meta.object_name.lower() | 
