2011-05-13 20:19:12 +04:00
|
|
|
from django import forms
|
|
|
|
from django.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch
|
2011-05-10 19:01:58 +04:00
|
|
|
from django.db import models
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2011-05-16 17:11:36 +04:00
|
|
|
from djangorestframework.response import ErrorResponse
|
2011-06-25 20:13:32 +04:00
|
|
|
from djangorestframework.serializer import Serializer, _SkipField
|
2011-05-16 17:11:36 +04:00
|
|
|
|
|
|
|
|
2011-06-14 21:22:13 +04:00
|
|
|
class BaseResource(Serializer):
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
Base class for all Resource classes, which simply defines the interface
|
|
|
|
they provide.
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
2011-12-14 18:45:59 +04:00
|
|
|
fields = ()
|
|
|
|
include = ()
|
|
|
|
exclude = ()
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-06-15 17:09:01 +04:00
|
|
|
def __init__(self, view=None, depth=None, stack=[], **kwargs):
|
2011-06-14 21:22:13 +04:00
|
|
|
super(BaseResource, self).__init__(depth, stack, **kwargs)
|
2011-05-12 15:55:13 +04:00
|
|
|
self.view = view
|
|
|
|
|
2011-05-27 17:40:19 +04:00
|
|
|
def validate_request(self, data, files=None):
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
2011-05-19 21:36:30 +04:00
|
|
|
Given the request content return the cleaned, validated content.
|
2011-12-14 14:48:19 +04:00
|
|
|
Typically raises a :exc:`response.ErrorResponse` with status code 400
|
|
|
|
(Bad Request) on failure.
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
2011-05-12 15:55:13 +04:00
|
|
|
return data
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-13 20:19:12 +04:00
|
|
|
def filter_response(self, obj):
|
|
|
|
"""
|
|
|
|
Given the response content, filter it into a serializable object.
|
|
|
|
"""
|
2011-06-14 21:22:13 +04:00
|
|
|
return self.serialize(obj)
|
2011-05-10 19:01:58 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
|
|
|
|
class Resource(BaseResource):
|
2011-05-10 19:01:58 +04:00
|
|
|
"""
|
|
|
|
A Resource determines how a python object maps to some serializable data.
|
2011-12-14 14:48:19 +04:00
|
|
|
Objects that a resource can act on include plain Python object instances,
|
|
|
|
Django Models, and Django QuerySets.
|
2011-05-10 19:01:58 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
|
|
|
|
# The model attribute refers to the Django Model which this Resource maps
|
|
|
|
# to. (The Model's class, rather than an instance of the Model)
|
2011-06-15 17:09:01 +04:00
|
|
|
model = None
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-06-15 17:09:01 +04:00
|
|
|
# By default the set of returned fields will be the set of:
|
|
|
|
#
|
|
|
|
# 0. All the fields on the model, excluding 'id'.
|
|
|
|
# 1. All the properties on the model.
|
2011-12-14 14:48:19 +04:00
|
|
|
# 2. The absolute_url of the model, if a get_absolute_url method exists for
|
|
|
|
# the model.
|
2011-06-15 17:09:01 +04:00
|
|
|
#
|
|
|
|
# If you wish to override this behaviour,
|
|
|
|
# you should explicitly set the fields attribute on your class.
|
|
|
|
fields = None
|
2011-05-04 12:21:17 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
|
|
|
|
class FormResource(Resource):
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
|
|
|
Resource class that uses forms for validation.
|
2011-12-14 14:48:19 +04:00
|
|
|
Also provides a :meth:`get_bound_form` method which may be used by some
|
|
|
|
renderers.
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
On calling :meth:`validate_request` this validator may set a
|
|
|
|
:attr:`bound_form_instance` attribute on the view, which may be used by
|
|
|
|
some renderers.
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
2011-05-27 12:58:21 +04:00
|
|
|
|
2011-07-20 00:09:35 +04:00
|
|
|
form = None
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
|
|
|
The :class:`Form` class that should be used for request validation.
|
2011-12-14 14:48:19 +04:00
|
|
|
This can be overridden by a :attr:`form` attribute on the
|
|
|
|
:class:`views.View`.
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
2011-05-27 12:58:21 +04:00
|
|
|
|
2011-05-27 17:40:19 +04:00
|
|
|
def validate_request(self, data, files=None):
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
|
|
|
Given some content as input return some cleaned, validated content.
|
2011-12-29 17:31:12 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
Raises a :exc:`response.ErrorResponse` with status code 400
|
|
|
|
# (Bad Request) on failure.
|
|
|
|
|
|
|
|
Validation is standard form validation, with an additional constraint
|
|
|
|
that *no extra unknown fields* may be supplied.
|
|
|
|
|
|
|
|
On failure the :exc:`response.ErrorResponse` content is a dict which
|
|
|
|
may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
|
|
|
If the :obj:`'errors'` key exists it is a list of strings of non-field
|
|
|
|
errors.
|
|
|
|
If the :obj:`'field-errors'` key exists it is a dict of
|
|
|
|
``{'field name as string': ['errors as strings', ...]}``.
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
|
|
|
return self._validate(data, files)
|
|
|
|
|
2011-05-16 17:11:36 +04:00
|
|
|
def _validate(self, data, files, allowed_extra_fields=(), fake_data=None):
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
Wrapped by validate to hide the extra flags that are used in the
|
|
|
|
implementation.
|
|
|
|
|
|
|
|
allowed_extra_fields is a list of fields which are not defined by the
|
|
|
|
form, but which we still expect to see on the input.
|
2011-05-16 19:52:39 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
fake_data is a string that should be used as an extra key, as a kludge
|
|
|
|
to force `.errors` to be populated when an empty dict is supplied in
|
|
|
|
`data`
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-16 17:11:36 +04:00
|
|
|
# We'd like nice error messages even if no content is supplied.
|
|
|
|
# Typically if an empty dict is given to a form Django will
|
|
|
|
# return .is_valid() == False, but .errors == {}
|
|
|
|
#
|
2011-12-14 14:48:19 +04:00
|
|
|
# To get around this case we revalidate with some fake data.
|
2011-05-16 17:11:36 +04:00
|
|
|
if fake_data:
|
|
|
|
data[fake_data] = '_fake_data'
|
2011-07-27 19:32:19 +04:00
|
|
|
allowed_extra_fields = tuple(allowed_extra_fields) + ('_fake_data',)
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
bound_form = self.get_bound_form(data, files)
|
|
|
|
|
|
|
|
if bound_form is None:
|
|
|
|
return data
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
self.view.bound_form_instance = bound_form
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-16 17:11:36 +04:00
|
|
|
data = data and data or {}
|
|
|
|
files = files and files or {}
|
2011-05-12 15:55:13 +04:00
|
|
|
|
|
|
|
seen_fields_set = set(data.keys())
|
|
|
|
form_fields_set = set(bound_form.fields.keys())
|
|
|
|
allowed_extra_fields_set = set(allowed_extra_fields)
|
|
|
|
|
|
|
|
# In addition to regular validation we also ensure no additional fields are being passed in...
|
|
|
|
unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set)
|
2012-01-03 13:02:46 +04:00
|
|
|
unknown_fields = unknown_fields - set(('csrfmiddlewaretoken', '_accept', '_method')) # TODO: Ugh.
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
# Check using both regular validation, and our stricter no additional fields rule
|
|
|
|
if bound_form.is_valid() and not unknown_fields:
|
|
|
|
# Validation succeeded...
|
|
|
|
cleaned_data = bound_form.cleaned_data
|
|
|
|
|
|
|
|
# Add in any extra fields to the cleaned content...
|
2012-01-03 13:10:14 +04:00
|
|
|
for key in (allowed_extra_fields_set & seen_fields_set) - set(cleaned_data.keys()):
|
2011-05-12 15:55:13 +04:00
|
|
|
cleaned_data[key] = data[key]
|
|
|
|
|
|
|
|
return cleaned_data
|
|
|
|
|
|
|
|
# Validation failed...
|
|
|
|
detail = {}
|
|
|
|
|
|
|
|
if not bound_form.errors and not unknown_fields:
|
2011-05-16 17:11:36 +04:00
|
|
|
# is_valid() was False, but errors was empty.
|
|
|
|
# If we havn't already done so attempt revalidation with some fake data
|
|
|
|
# to force django to give us an errors dict.
|
|
|
|
if fake_data is None:
|
|
|
|
return self._validate(data, files, allowed_extra_fields, '_fake_data')
|
|
|
|
|
|
|
|
# If we've already set fake_dict and we're still here, fallback gracefully.
|
2011-05-12 15:55:13 +04:00
|
|
|
detail = {u'errors': [u'No content was supplied.']}
|
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
else:
|
2011-05-12 15:55:13 +04:00
|
|
|
# Add any non-field errors
|
|
|
|
if bound_form.non_field_errors():
|
|
|
|
detail[u'errors'] = bound_form.non_field_errors()
|
|
|
|
|
|
|
|
# Add standard field errors
|
2011-05-13 20:19:12 +04:00
|
|
|
field_errors = dict(
|
|
|
|
(key, map(unicode, val))
|
2011-05-12 15:55:13 +04:00
|
|
|
for (key, val)
|
|
|
|
in bound_form.errors.iteritems()
|
2011-05-13 20:19:12 +04:00
|
|
|
if not key.startswith('__')
|
|
|
|
)
|
2011-05-12 15:55:13 +04:00
|
|
|
|
|
|
|
# Add any unknown field errors
|
|
|
|
for key in unknown_fields:
|
|
|
|
field_errors[key] = [u'This field does not exist.']
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
if field_errors:
|
2011-12-30 18:52:01 +04:00
|
|
|
detail[u'field_errors'] = field_errors
|
2011-05-12 15:55:13 +04:00
|
|
|
|
|
|
|
# Return HTTP 400 response (BAD REQUEST)
|
|
|
|
raise ErrorResponse(400, detail)
|
|
|
|
|
2011-07-01 10:32:04 +04:00
|
|
|
def get_form_class(self, method=None):
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
2011-07-01 10:32:04 +04:00
|
|
|
Returns the form class used to validate this resource.
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
2011-05-27 17:40:19 +04:00
|
|
|
# A form on the view overrides a form on the resource.
|
2011-06-15 17:09:01 +04:00
|
|
|
form = getattr(self.view, 'form', None) or self.form
|
2011-05-27 17:40:19 +04:00
|
|
|
|
|
|
|
# Use the requested method or determine the request method
|
|
|
|
if method is None and hasattr(self.view, 'request') and hasattr(self.view, 'method'):
|
|
|
|
method = self.view.method
|
|
|
|
elif method is None and hasattr(self.view, 'request'):
|
|
|
|
method = self.view.request.method
|
|
|
|
|
|
|
|
# A method form on the view or resource overrides the general case.
|
|
|
|
# Method forms are attributes like `get_form` `post_form` `put_form`.
|
|
|
|
if method:
|
|
|
|
form = getattr(self, '%s_form' % method.lower(), form)
|
|
|
|
form = getattr(self.view, '%s_form' % method.lower(), form)
|
|
|
|
|
2011-07-01 10:32:04 +04:00
|
|
|
return form
|
|
|
|
|
|
|
|
|
|
|
|
def get_bound_form(self, data=None, files=None, method=None):
|
|
|
|
"""
|
|
|
|
Given some content return a Django form bound to that content.
|
|
|
|
If form validation is turned off (:attr:`form` class attribute is :const:`None`) then returns :const:`None`.
|
|
|
|
"""
|
|
|
|
form = self.get_form_class(method)
|
|
|
|
|
2011-05-27 17:40:19 +04:00
|
|
|
if not form:
|
2011-05-12 15:55:13 +04:00
|
|
|
return None
|
|
|
|
|
2011-06-12 23:23:40 +04:00
|
|
|
if data is not None or files is not None:
|
2011-05-27 17:40:19 +04:00
|
|
|
return form(data, files)
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-05-27 17:40:19 +04:00
|
|
|
return form()
|
2011-05-12 15:55:13 +04:00
|
|
|
|
|
|
|
|
2011-05-16 17:11:36 +04:00
|
|
|
#class _RegisterModelResource(type):
|
|
|
|
# """
|
|
|
|
# Auto register new ModelResource classes into ``_model_to_resource``
|
|
|
|
# """
|
|
|
|
# def __new__(cls, name, bases, dct):
|
|
|
|
# resource_cls = type.__new__(cls, name, bases, dct)
|
|
|
|
# model_cls = dct.get('model', None)
|
|
|
|
# if model_cls:
|
|
|
|
# _model_to_resource[model_cls] = resource_cls
|
|
|
|
# return resource_cls
|
|
|
|
|
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
class ModelResource(FormResource):
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
Resource class that uses forms for validation and otherwise falls back to a
|
|
|
|
model form if no form is set.
|
|
|
|
Also provides a :meth:`get_bound_form` method which may be used by some
|
|
|
|
renderers.
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
2011-05-16 17:11:36 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
# Auto-register new ModelResource classes into _model_to_resource
|
2011-05-16 17:11:36 +04:00
|
|
|
#__metaclass__ = _RegisterModelResource
|
|
|
|
|
2011-07-20 00:09:35 +04:00
|
|
|
form = None
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
|
|
|
The form class that should be used for request validation.
|
|
|
|
If set to :const:`None` then the default model form validation will be used.
|
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
This can be overridden by a :attr:`form` attribute on the
|
|
|
|
:class:`views.View`.
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
|
|
|
|
2011-07-20 00:09:35 +04:00
|
|
|
model = None
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
|
|
|
The model class which this resource maps to.
|
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
This can be overridden by a :attr:`model` attribute on the
|
|
|
|
:class:`views.View`.
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
|
|
|
|
2011-07-20 00:09:35 +04:00
|
|
|
fields = None
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
|
|
|
The list of fields to use on the output.
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-06-15 17:09:01 +04:00
|
|
|
May be any of:
|
2011-12-14 14:48:19 +04:00
|
|
|
|
|
|
|
The name of a model field. To view nested resources, give the field as a
|
|
|
|
tuple of ("fieldName", resource) where `resource` may be any of
|
|
|
|
ModelResource reference, the name of a ModelResourc reference as a string
|
|
|
|
or a tuple of strings representing fields on the nested model.
|
2011-06-15 17:09:01 +04:00
|
|
|
The name of an attribute on the model.
|
|
|
|
The name of an attribute on the resource.
|
|
|
|
The name of a method on the model, with a signature like ``func(self)``.
|
2011-12-14 14:48:19 +04:00
|
|
|
The name of a method on the resource, with a signature like
|
|
|
|
``func(self, instance)``.
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-07-20 00:09:35 +04:00
|
|
|
exclude = ('id', 'pk')
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
The list of fields to exclude. This is only used if :attr:`fields` is not
|
|
|
|
set.
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
2011-07-20 00:09:35 +04:00
|
|
|
|
|
|
|
include = ('url',)
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
The list of extra fields to include. This is only used if :attr:`fields`
|
|
|
|
is not set.
|
2011-06-15 17:09:01 +04:00
|
|
|
"""
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-07-19 02:07:00 +04:00
|
|
|
def __init__(self, view=None, depth=None, stack=[], **kwargs):
|
2011-05-27 12:58:21 +04:00
|
|
|
"""
|
|
|
|
Allow :attr:`form` and :attr:`model` attributes set on the
|
|
|
|
:class:`View` to override the :attr:`form` and :attr:`model`
|
|
|
|
attributes set on the :class:`Resource`.
|
|
|
|
"""
|
2011-07-19 02:07:00 +04:00
|
|
|
super(ModelResource, self).__init__(view, depth, stack, **kwargs)
|
2011-05-27 12:58:21 +04:00
|
|
|
|
2011-06-15 17:09:01 +04:00
|
|
|
self.model = getattr(view, 'model', None) or self.model
|
2011-06-14 21:22:13 +04:00
|
|
|
|
2011-05-27 17:40:19 +04:00
|
|
|
def validate_request(self, data, files=None):
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
|
|
|
Given some content as input return some cleaned, validated content.
|
2011-12-29 17:31:12 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
Raises a :exc:`response.ErrorResponse` with status code 400
|
|
|
|
(Bad Request) on failure.
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
Validation is standard form or model form validation,
|
|
|
|
with an additional constraint that no extra unknown fields may be
|
|
|
|
supplied, and that all fields specified by the fields class attribute
|
|
|
|
must be supplied, even if they are not validated by the Form/ModelForm.
|
|
|
|
|
|
|
|
On failure the ErrorResponse content is a dict which may contain
|
|
|
|
:obj:`'errors'` and :obj:`'field-errors'` keys.
|
|
|
|
If the :obj:`'errors'` key exists it is a list of strings of non-field
|
|
|
|
errors.
|
|
|
|
If the ''field-errors'` key exists it is a dict of
|
|
|
|
`{field name as string: list of errors as strings}`.
|
2011-05-12 15:55:13 +04:00
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
return self._validate(data, files,
|
|
|
|
allowed_extra_fields=self._property_fields_set)
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-05-27 17:40:19 +04:00
|
|
|
def get_bound_form(self, data=None, files=None, method=None):
|
2011-05-13 20:39:52 +04:00
|
|
|
"""
|
|
|
|
Given some content return a ``Form`` instance bound to that content.
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
If the :attr:`form` class attribute has been explicitly set then that
|
|
|
|
class will be used
|
|
|
|
to create the Form, otherwise the model will be used to create a
|
|
|
|
ModelForm.
|
2011-05-13 20:39:52 +04:00
|
|
|
"""
|
2011-07-01 10:32:04 +04:00
|
|
|
form = self.get_form_class(method)
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-07-01 10:32:04 +04:00
|
|
|
if not form and self.model:
|
2011-05-12 15:55:13 +04:00
|
|
|
# Fall back to ModelForm which we create on the fly
|
|
|
|
class OnTheFlyModelForm(forms.ModelForm):
|
|
|
|
class Meta:
|
2011-05-13 20:19:12 +04:00
|
|
|
model = self.model
|
2011-05-12 15:55:13 +04:00
|
|
|
#fields = tuple(self._model_fields_set)
|
|
|
|
|
2011-07-01 10:32:04 +04:00
|
|
|
form = OnTheFlyModelForm
|
2011-05-12 15:55:13 +04:00
|
|
|
|
|
|
|
# Both form and model not set? Okay bruv, whatevs...
|
2011-07-01 10:32:04 +04:00
|
|
|
if not form:
|
|
|
|
return None
|
|
|
|
|
|
|
|
# Instantiate the ModelForm as appropriate
|
|
|
|
if data is not None or files is not None:
|
|
|
|
if issubclass(form, forms.ModelForm) and hasattr(self.view, 'model_instance'):
|
|
|
|
# Bound to an existing model instance
|
|
|
|
return form(data, files, instance=self.view.model_instance)
|
|
|
|
else:
|
|
|
|
return form(data, files)
|
|
|
|
|
|
|
|
return form()
|
|
|
|
|
2011-05-13 20:19:12 +04:00
|
|
|
def url(self, instance):
|
|
|
|
"""
|
2011-12-14 14:48:19 +04:00
|
|
|
Attempts to reverse resolve the url of the given model *instance* for
|
|
|
|
this resource.
|
|
|
|
|
|
|
|
Requires a ``View`` with :class:`mixins.InstanceMixin` to have been
|
|
|
|
created for this resource.
|
2011-05-16 12:14:01 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
This method can be overridden if you need to set the resource url
|
|
|
|
reversing explicitly.
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
|
|
|
|
2011-06-02 18:22:14 +04:00
|
|
|
if not hasattr(self, 'view_callable'):
|
2011-12-14 14:48:19 +04:00
|
|
|
raise _SkipField
|
2011-06-02 18:22:14 +04:00
|
|
|
|
2011-05-13 20:19:12 +04:00
|
|
|
# dis does teh magicks...
|
|
|
|
urlconf = get_urlconf()
|
|
|
|
resolver = get_resolver(urlconf)
|
|
|
|
|
|
|
|
possibilities = resolver.reverse_dict.getlist(self.view_callable[0])
|
|
|
|
for tuple_item in possibilities:
|
|
|
|
possibility = tuple_item[0]
|
|
|
|
# pattern = tuple_item[1]
|
|
|
|
# Note: defaults = tuple_item[2] for django >= 1.3
|
|
|
|
for result, params in possibility:
|
2011-05-16 17:11:36 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
# instance_attrs = dict([ (param, getattr(instance, param))
|
|
|
|
# for param in params
|
|
|
|
# if hasattr(instance, param) ])
|
2011-05-16 17:11:36 +04:00
|
|
|
|
|
|
|
instance_attrs = {}
|
|
|
|
for param in params:
|
|
|
|
if not hasattr(instance, param):
|
|
|
|
continue
|
|
|
|
attr = getattr(instance, param)
|
|
|
|
if isinstance(attr, models.Model):
|
|
|
|
instance_attrs[param] = attr.pk
|
|
|
|
else:
|
2011-06-15 17:09:01 +04:00
|
|
|
instance_attrs[param] = attr
|
2011-05-16 17:11:36 +04:00
|
|
|
|
2011-05-13 20:19:12 +04:00
|
|
|
try:
|
|
|
|
return reverse(self.view_callable[0], kwargs=instance_attrs)
|
|
|
|
except NoReverseMatch:
|
|
|
|
pass
|
2011-06-25 20:13:32 +04:00
|
|
|
raise _SkipField
|
2011-05-13 20:19:12 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
@property
|
|
|
|
def _model_fields_set(self):
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
|
|
|
Return a set containing the names of validated fields on the model.
|
|
|
|
"""
|
|
|
|
model_fields = set(field.name for field in self.model._meta.fields)
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
if self.fields:
|
2011-12-14 18:45:59 +04:00
|
|
|
return model_fields & set(self.fields)
|
2011-05-12 15:55:13 +04:00
|
|
|
|
2011-05-13 20:19:12 +04:00
|
|
|
return model_fields - set(as_tuple(self.exclude))
|
2011-12-29 17:31:12 +04:00
|
|
|
|
2011-12-14 14:48:19 +04:00
|
|
|
|
2011-05-12 15:55:13 +04:00
|
|
|
@property
|
|
|
|
def _property_fields_set(self):
|
2011-05-13 20:19:12 +04:00
|
|
|
"""
|
|
|
|
Returns a set containing the names of validated properties on the model.
|
|
|
|
"""
|
|
|
|
property_fields = set(attr for attr in dir(self.model) if
|
|
|
|
isinstance(getattr(self.model, attr, None), property)
|
2011-05-12 15:55:13 +04:00
|
|
|
and not attr.startswith('_'))
|
|
|
|
|
2011-06-15 17:09:01 +04:00
|
|
|
if self.fields:
|
2011-12-14 18:45:59 +04:00
|
|
|
return property_fields & set(self.fields)
|
2011-04-27 21:07:28 +04:00
|
|
|
|
2011-12-14 18:45:59 +04:00
|
|
|
return property_fields.union(set(self.include)) - set(self.exclude)
|