diff options
| -rw-r--r-- | djangorestframework.egg-info/PKG-INFO | 19 | ||||
| -rw-r--r-- | djangorestframework.egg-info/SOURCES.txt | 86 | ||||
| -rw-r--r-- | djangorestframework.egg-info/dependency_links.txt | 1 | ||||
| -rw-r--r-- | djangorestframework.egg-info/top_level.txt | 7 | ||||
| -rw-r--r-- | docs/index.md | 2 | ||||
| -rw-r--r-- | docs/template.html | 2 | ||||
| -rw-r--r-- | docs/tutorial/quickstart.md | 9 | ||||
| -rw-r--r-- | rest_framework/fields.py | 107 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 25 | ||||
| -rw-r--r-- | rest_framework/static/rest_framework/css/default.css | 7 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/base.html | 4 |
11 files changed, 244 insertions, 25 deletions
diff --git a/djangorestframework.egg-info/PKG-INFO b/djangorestframework.egg-info/PKG-INFO new file mode 100644 index 00000000..04eafbde --- /dev/null +++ b/djangorestframework.egg-info/PKG-INFO @@ -0,0 +1,19 @@ +Metadata-Version: 1.0 +Name: djangorestframework +Version: 2.0.0 +Summary: A lightweight REST framework for Django. +Home-page: http://django-rest-framework.org +Author: Tom Christie +Author-email: tom@tomchristie.com +License: BSD +Download-URL: http://pypi.python.org/pypi/rest_framework/ +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: Django +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP diff --git a/djangorestframework.egg-info/SOURCES.txt b/djangorestframework.egg-info/SOURCES.txt new file mode 100644 index 00000000..298d1c33 --- /dev/null +++ b/djangorestframework.egg-info/SOURCES.txt @@ -0,0 +1,86 @@ +MANIFEST.in +setup.py +djangorestframework.egg-info/PKG-INFO +djangorestframework.egg-info/SOURCES.txt +djangorestframework.egg-info/dependency_links.txt +djangorestframework.egg-info/top_level.txt +rest_framework/__init__.py +rest_framework/authentication.py +rest_framework/compat.py +rest_framework/decorators.py +rest_framework/exceptions.py +rest_framework/fields.py +rest_framework/generics.py +rest_framework/mixins.py +rest_framework/models.py +rest_framework/negotiation.py +rest_framework/pagination.py +rest_framework/parsers.py +rest_framework/permissions.py +rest_framework/renderers.py +rest_framework/request.py +rest_framework/response.py +rest_framework/reverse.py +rest_framework/serializers.py +rest_framework/settings.py +rest_framework/status.py +rest_framework/throttling.py +rest_framework/urlpatterns.py +rest_framework/urls.py +rest_framework/views.py +rest_framework/authtoken/__init__.py +rest_framework/authtoken/models.py +rest_framework/authtoken/views.py +rest_framework/authtoken/migrations/0001_initial.py +rest_framework/authtoken/migrations/__init__.py +rest_framework/runtests/__init__.py +rest_framework/runtests/runcoverage.py +rest_framework/runtests/runtests.py +rest_framework/runtests/settings.py +rest_framework/runtests/urls.py +rest_framework/static/rest_framework/css/bootstrap-tweaks.css +rest_framework/static/rest_framework/css/bootstrap.min.css +rest_framework/static/rest_framework/css/default.css +rest_framework/static/rest_framework/css/prettify.css +rest_framework/static/rest_framework/img/glyphicons-halflings-white.png +rest_framework/static/rest_framework/img/glyphicons-halflings.png +rest_framework/static/rest_framework/img/grid.png +rest_framework/static/rest_framework/js/bootstrap.min.js +rest_framework/static/rest_framework/js/default.js +rest_framework/static/rest_framework/js/jquery-1.8.1-min.js +rest_framework/static/rest_framework/js/prettify-min.js +rest_framework/templates/rest_framework/api.html +rest_framework/templates/rest_framework/base.html +rest_framework/templates/rest_framework/login.html +rest_framework/templatetags/__init__.py +rest_framework/templatetags/rest_framework.py +rest_framework/tests/__init__.py +rest_framework/tests/authentication.py +rest_framework/tests/breadcrumbs.py +rest_framework/tests/decorators.py +rest_framework/tests/description.py +rest_framework/tests/files.py +rest_framework/tests/genericrelations.py +rest_framework/tests/generics.py +rest_framework/tests/htmlrenderer.py +rest_framework/tests/hyperlinkedserializers.py +rest_framework/tests/models.py +rest_framework/tests/modelviews.py +rest_framework/tests/negotiation.py +rest_framework/tests/pagination.py +rest_framework/tests/parsers.py +rest_framework/tests/renderers.py +rest_framework/tests/request.py +rest_framework/tests/response.py +rest_framework/tests/reverse.py +rest_framework/tests/serializer.py +rest_framework/tests/status.py +rest_framework/tests/testcases.py +rest_framework/tests/tests.py +rest_framework/tests/throttling.py +rest_framework/tests/validators.py +rest_framework/tests/views.py +rest_framework/utils/__init__.py +rest_framework/utils/breadcrumbs.py +rest_framework/utils/encoders.py +rest_framework/utils/mediatypes.py
\ No newline at end of file diff --git a/djangorestframework.egg-info/dependency_links.txt b/djangorestframework.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/djangorestframework.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/djangorestframework.egg-info/top_level.txt b/djangorestframework.egg-info/top_level.txt new file mode 100644 index 00000000..7e18534d --- /dev/null +++ b/djangorestframework.egg-info/top_level.txt @@ -0,0 +1,7 @@ +rest_framework/authtoken +rest_framework/utils +rest_framework/tests +rest_framework/runtests +rest_framework/templatetags +rest_framework +rest_framework/authtoken/migrations diff --git a/docs/index.md b/docs/index.md index 75a1cf6e..5e086872 100644 --- a/docs/index.md +++ b/docs/index.md @@ -66,11 +66,9 @@ If you're intending to use the browseable API you'll want to add REST framework' Note that the URL path can be whatever you want, but you must include `rest_framework.urls` with the `rest_framework` namespace. -<!-- ## Quickstart Can't wait to get started? The [quickstart guide][quickstart] is the fastest way to get up and running with REST framework. ---> ## Tutorial diff --git a/docs/template.html b/docs/template.html index 94fc269f..c428dff3 100644 --- a/docs/template.html +++ b/docs/template.html @@ -53,7 +53,7 @@ <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">Tutorial <b class="caret"></b></a> <ul class="dropdown-menu"> - <!--<li><a href="{{ base_url }}/tutorial/quickstart{{ suffix }}">Quickstart</a></li>--> + <li><a href="{{ base_url }}/tutorial/quickstart{{ suffix }}">Quickstart</a></li> <li><a href="{{ base_url }}/tutorial/1-serialization{{ suffix }}">1 - Serialization</a></li> <li><a href="{{ base_url }}/tutorial/2-requests-and-responses{{ suffix }}">2 - Requests and responses</a></li> <li><a href="{{ base_url }}/tutorial/3-class-based-views{{ suffix }}">3 - Class based views</a></li> diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 6bde725b..93da1a59 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -19,12 +19,19 @@ First up we're going to define some serializers in `quickstart/serializers.py` t class GroupSerializer(serializers.HyperlinkedModelSerializer): + permissions = serializers.ManySlugRelatedField( + slug_field='codename', + queryset=Permission.objects.all() + ) + class Meta: model = Group fields = ('url', 'name', 'permissions') Notice that we're using hyperlinked relations in this case, with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design. +We've also overridden the `permission` field on the `GroupSerializer`. In this case we don't want to use a hyperlinked representation, but instead use the list of permission codenames associated with the group, so we've used a `ManySlugRelatedField`, using the `codename` field for the representation. + ## Views Right, we'd better write some views then. Open `quickstart/views.py` and get typing. @@ -152,7 +159,7 @@ We can now access our API, both from the command-line, using tools like `curl`.. }, { "email": "tom@example.com", - "groups": [], + "groups": [ ], "url": "http://127.0.0.1:8000/users/2/", "username": "tom" } diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1d6d760e..6d858087 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -8,6 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.urlresolvers import resolve, get_script_prefix from django.conf import settings from django.forms import widgets +from django.forms.models import ModelChoiceIterator from django.utils.encoding import is_protected_type, smart_unicode from django.utils.translation import ugettext_lazy as _ from rest_framework.reverse import reverse @@ -232,11 +233,72 @@ class ModelField(WritableField): class RelatedField(WritableField): """ Base class for related model fields. + + If not overridden, this represents a to-one relatinship, using the unicode + representation of the target. """ + widget = widgets.Select + cache_choices = False + empty_label = None + def __init__(self, *args, **kwargs): self.queryset = kwargs.pop('queryset', None) super(RelatedField, self).__init__(*args, **kwargs) + ### We need this stuff to make form choices work... + + # def __deepcopy__(self, memo): + # result = super(RelatedField, self).__deepcopy__(memo) + # result.queryset = result.queryset + # return result + + def prepare_value(self, obj): + return self.to_native(obj) + + def label_from_instance(self, obj): + """ + Return a readable representation for use with eg. select widgets. + """ + desc = smart_unicode(obj) + ident = self.to_native(obj) + if desc == ident: + return desc + return "%s - %s" % (desc, ident) + + def _get_queryset(self): + return self._queryset + + def _set_queryset(self, queryset): + self._queryset = queryset + self.widget.choices = self.choices + + queryset = property(_get_queryset, _set_queryset) + + def _get_choices(self): + # If self._choices is set, then somebody must have manually set + # the property self.choices. In this case, just return self._choices. + if hasattr(self, '_choices'): + return self._choices + + # Otherwise, execute the QuerySet in self.queryset to determine the + # choices dynamically. Return a fresh ModelChoiceIterator that has not been + # consumed. Note that we're instantiating a new ModelChoiceIterator *each* + # time _get_choices() is called (and, thus, each time self.choices is + # accessed) so that we can ensure the QuerySet has not been consumed. This + # construct might look complicated but it allows for lazy evaluation of + # the queryset. + return ModelChoiceIterator(self) + + def _set_choices(self, value): + # Setting choices also sets the choices on the widget. + # choices can be any iterable, but we call list() on it because + # it will be consumed more than once. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + ### Regular serializier stuff... + def field_to_native(self, obj, field_name): value = getattr(obj, self.source or field_name) return self.to_native(value) @@ -253,6 +315,8 @@ class ManyRelatedMixin(object): """ Mixin to convert a related field to a many related field. """ + widget = widgets.SelectMultiple + def field_to_native(self, obj, field_name): value = getattr(obj, self.source or field_name) return [self.to_native(item) for item in value.all()] @@ -276,6 +340,9 @@ class ManyRelatedMixin(object): class ManyRelatedField(ManyRelatedMixin, RelatedField): """ Base class for related model managers. + + If not overridden, this represents a to-many relatinship, using the unicode + representations of the target, and is read-only. """ pass @@ -284,7 +351,7 @@ class ManyRelatedField(ManyRelatedMixin, RelatedField): class PrimaryKeyRelatedField(RelatedField): """ - Serializes a related field or related object to a pk value. + Represents a to-one relationship as a pk value. """ def to_native(self, pk): @@ -313,7 +380,7 @@ class PrimaryKeyRelatedField(RelatedField): class ManyPrimaryKeyRelatedField(ManyRelatedField): """ - Serializes a to-many related field or related manager to a pk value. + Represents a to-many relationship as a pk value. """ def to_native(self, pk): return pk @@ -329,10 +396,36 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): # Forward relationship return [self.to_native(item.pk) for item in queryset.all()] +### Slug relationships + + +class SlugRelatedField(RelatedField): + def __init__(self, *args, **kwargs): + self.slug_field = kwargs.pop('slug_field', None) + assert self.slug_field, 'slug_field is required' + super(SlugRelatedField, self).__init__(*args, **kwargs) + + def to_native(self, obj): + return getattr(obj, self.slug_field) + + def from_native(self, data): + try: + return self.queryset.get(**{self.slug_field: data}) + except ObjectDoesNotExist: + raise ValidationError('Object with %s=%s does not exist.' % + (self.slug_field, unicode(data))) + + +class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField): + pass + ### Hyperlinked relationships class HyperlinkedRelatedField(RelatedField): + """ + Represents a to-one relationship, using hyperlinking. + """ pk_url_kwarg = 'pk' slug_url_kwarg = 'slug' slug_field = 'slug' @@ -417,16 +510,20 @@ class HyperlinkedRelatedField(RelatedField): class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField): + """ + Represents a to-many relationship, using hyperlinking. + """ pass class HyperlinkedIdentityField(Field): """ - A field that represents the model's identity using a hyperlink. + Represents the instance, or a property on the instance, using hyperlinking. """ + def __init__(self, *args, **kwargs): - # TODO: Make this mandatory, and have the HyperlinkedModelSerializer - # set it on-the-fly + # TODO: Make view_name mandatory, and have the + # HyperlinkedModelSerializer set it on-the-fly self.view_name = kwargs.pop('view_name', None) self.format = kwargs.pop('format', None) super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 8dff0c77..63578b14 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -282,10 +282,12 @@ class BrowsableAPIRenderer(BaseRenderer): serializers.EmailField: forms.EmailField, serializers.CharField: forms.CharField, serializers.BooleanField: forms.BooleanField, - serializers.PrimaryKeyRelatedField: forms.ModelChoiceField, - serializers.ManyPrimaryKeyRelatedField: forms.ModelMultipleChoiceField, - serializers.HyperlinkedRelatedField: forms.ModelChoiceField, - serializers.ManyHyperlinkedRelatedField: forms.ModelMultipleChoiceField + serializers.PrimaryKeyRelatedField: forms.ChoiceField, + serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, + serializers.SlugRelatedField: forms.ChoiceField, + serializers.ManySlugRelatedField: forms.MultipleChoiceField, + serializers.HyperlinkedRelatedField: forms.ChoiceField, + serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField } fields = {} @@ -296,19 +298,14 @@ class BrowsableAPIRenderer(BaseRenderer): kwargs = {} kwargs['required'] = v.required - if getattr(v, 'queryset', None): - kwargs['queryset'] = v.queryset + #if getattr(v, 'queryset', None): + # kwargs['queryset'] = v.queryset + + if getattr(v, 'choices', None) is not None: + kwargs['choices'] = v.choices if getattr(v, 'widget', None): widget = copy.deepcopy(v.widget) - # If choices have friendly readable names, - # then add in the identities too - if getattr(widget, 'choices', None): - choices = widget.choices - if any([ident != desc for (ident, desc) in choices]): - choices = [(ident, "%s (%s)" % (desc, ident)) - for (ident, desc) in choices] - widget.choices = choices kwargs['widget'] = widget if getattr(v, 'default', None) is not None: diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css index e29da395..fdf45659 100644 --- a/rest_framework/static/rest_framework/css/default.css +++ b/rest_framework/static/rest_framework/css/default.css @@ -36,6 +36,13 @@ ul.breadcrumb { margin: 58px 0 0 0; } +form select, form input { + width: 90%; +} + +form select[multiple] { + height: 150px; +} /* To allow tooltips to work on disabled elements */ .disabled-tooltip-shield { position: absolute; diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index e0f79481..9b0a0dca 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -134,7 +134,7 @@ <div class="control-group {% if field.errors %}error{% endif %}"> {{ field.label_tag|add_class:"control-label" }} <div class="controls"> - {{ field|add_class:"input-xlarge" }} + {{ field }} <span class="help-inline">{{ field.help_text }}</span> {{ field.errors|add_class:"help-block" }} </div> @@ -159,7 +159,7 @@ <div class="control-group {% if field.errors %}error{% endif %}"> {{ field.label_tag|add_class:"control-label" }} <div class="controls"> - {{ field|add_class:"input-xlarge" }} + {{ field }} <span class='help-inline'>{{ field.help_text }}</span> {{ field.errors|add_class:"help-block" }} </div> |
