Skip to content

Commit 91b8076

Browse files
committed
Introducing tastypie 0.12.1
- Legacy-Id: 8819
1 parent 39c87c3 commit 91b8076

43 files changed

Lines changed: 7068 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tastypie/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from __future__ import unicode_literals
2+
3+
4+
__author__ = 'Daniel Lindsley & the Tastypie core team'
5+
__version__ = (0, 12, 1)

tastypie/admin.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from __future__ import unicode_literals
2+
from django.conf import settings
3+
from django.contrib import admin
4+
5+
6+
if 'django.contrib.auth' in settings.INSTALLED_APPS:
7+
from tastypie.models import ApiKey
8+
9+
class ApiKeyInline(admin.StackedInline):
10+
model = ApiKey
11+
extra = 0
12+
13+
ABSTRACT_APIKEY = getattr(settings, 'TASTYPIE_ABSTRACT_APIKEY', False)
14+
15+
if ABSTRACT_APIKEY and not isinstance(ABSTRACT_APIKEY, bool):
16+
raise TypeError("'TASTYPIE_ABSTRACT_APIKEY' must be either 'True' "
17+
"or 'False'.")
18+
19+
if not ABSTRACT_APIKEY:
20+
admin.site.register(ApiKey)

tastypie/api.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
from __future__ import unicode_literals
2+
import warnings
3+
from django.conf.urls import url, patterns, include
4+
from django.core.exceptions import ImproperlyConfigured
5+
from django.core.urlresolvers import reverse
6+
from django.http import HttpResponse, HttpResponseBadRequest
7+
from tastypie.exceptions import NotRegistered, BadRequest
8+
from tastypie.serializers import Serializer
9+
from tastypie.utils import trailing_slash, is_valid_jsonp_callback_value
10+
from tastypie.utils.mime import determine_format, build_content_type
11+
12+
13+
class Api(object):
14+
"""
15+
Implements a registry to tie together the various resources that make up
16+
an API.
17+
18+
Especially useful for navigation, HATEOAS and for providing multiple
19+
versions of your API.
20+
21+
Optionally supplying ``api_name`` allows you to name the API. Generally,
22+
this is done with version numbers (i.e. ``v1``, ``v2``, etc.) but can
23+
be named any string.
24+
"""
25+
def __init__(self, api_name="v1", serializer_class=Serializer):
26+
self.api_name = api_name
27+
self._registry = {}
28+
self._canonicals = {}
29+
self.serializer = serializer_class()
30+
31+
def register(self, resource, canonical=True):
32+
"""
33+
Registers an instance of a ``Resource`` subclass with the API.
34+
35+
Optionally accept a ``canonical`` argument, which indicates that the
36+
resource being registered is the canonical variant. Defaults to
37+
``True``.
38+
"""
39+
resource_name = getattr(resource._meta, 'resource_name', None)
40+
41+
if resource_name is None:
42+
raise ImproperlyConfigured("Resource %r must define a 'resource_name'." % resource)
43+
44+
self._registry[resource_name] = resource
45+
46+
if canonical is True:
47+
if resource_name in self._canonicals:
48+
warnings.warn("A new resource '%r' is replacing the existing canonical URL for '%s'." % (resource, resource_name), Warning, stacklevel=2)
49+
50+
self._canonicals[resource_name] = resource
51+
# TODO: This is messy, but makes URI resolution on FK/M2M fields
52+
# work consistently.
53+
resource._meta.api_name = self.api_name
54+
resource.__class__.Meta.api_name = self.api_name
55+
56+
def unregister(self, resource_name):
57+
"""
58+
If present, unregisters a resource from the API.
59+
"""
60+
if resource_name in self._registry:
61+
del(self._registry[resource_name])
62+
63+
if resource_name in self._canonicals:
64+
del(self._canonicals[resource_name])
65+
66+
def canonical_resource_for(self, resource_name):
67+
"""
68+
Returns the canonical resource for a given ``resource_name``.
69+
"""
70+
if resource_name in self._canonicals:
71+
return self._canonicals[resource_name]
72+
73+
raise NotRegistered("No resource was registered as canonical for '%s'." % resource_name)
74+
75+
def wrap_view(self, view):
76+
def wrapper(request, *args, **kwargs):
77+
try:
78+
return getattr(self, view)(request, *args, **kwargs)
79+
except BadRequest:
80+
return HttpResponseBadRequest()
81+
return wrapper
82+
83+
def override_urls(self):
84+
"""
85+
Deprecated. Will be removed by v1.0.0. Please use ``prepend_urls`` instead.
86+
"""
87+
return []
88+
89+
def prepend_urls(self):
90+
"""
91+
A hook for adding your own URLs or matching before the default URLs.
92+
"""
93+
return []
94+
95+
@property
96+
def urls(self):
97+
"""
98+
Provides URLconf details for the ``Api`` and all registered
99+
``Resources`` beneath it.
100+
"""
101+
pattern_list = [
102+
url(r"^(?P<api_name>%s)%s$" % (self.api_name, trailing_slash()), self.wrap_view('top_level'), name="api_%s_top_level" % self.api_name),
103+
]
104+
105+
for name in sorted(self._registry.keys()):
106+
self._registry[name].api_name = self.api_name
107+
pattern_list.append((r"^(?P<api_name>%s)/" % self.api_name, include(self._registry[name].urls)))
108+
109+
urlpatterns = self.prepend_urls()
110+
111+
overridden_urls = self.override_urls()
112+
if overridden_urls:
113+
warnings.warn("'override_urls' is a deprecated method & will be removed by v1.0.0. Please rename your method to ``prepend_urls``.")
114+
urlpatterns += overridden_urls
115+
116+
urlpatterns += patterns('',
117+
*pattern_list
118+
)
119+
return urlpatterns
120+
121+
def top_level(self, request, api_name=None):
122+
"""
123+
A view that returns a serialized list of all resources registers
124+
to the ``Api``. Useful for discovery.
125+
"""
126+
available_resources = {}
127+
128+
if api_name is None:
129+
api_name = self.api_name
130+
131+
for name in sorted(self._registry.keys()):
132+
available_resources[name] = {
133+
'list_endpoint': self._build_reverse_url("api_dispatch_list", kwargs={
134+
'api_name': api_name,
135+
'resource_name': name,
136+
}),
137+
'schema': self._build_reverse_url("api_get_schema", kwargs={
138+
'api_name': api_name,
139+
'resource_name': name,
140+
}),
141+
}
142+
143+
desired_format = determine_format(request, self.serializer)
144+
145+
options = {}
146+
147+
if 'text/javascript' in desired_format:
148+
callback = request.GET.get('callback', 'callback')
149+
150+
if not is_valid_jsonp_callback_value(callback):
151+
raise BadRequest('JSONP callback name is invalid.')
152+
153+
options['callback'] = callback
154+
155+
serialized = self.serializer.serialize(available_resources, desired_format, options)
156+
return HttpResponse(content=serialized, content_type=build_content_type(desired_format))
157+
158+
def _build_reverse_url(self, name, args=None, kwargs=None):
159+
"""
160+
A convenience hook for overriding how URLs are built.
161+
162+
See ``NamespacedApi._build_reverse_url`` for an example.
163+
"""
164+
return reverse(name, args=args, kwargs=kwargs)
165+
166+
167+
class NamespacedApi(Api):
168+
"""
169+
An API subclass that respects Django namespaces.
170+
"""
171+
def __init__(self, api_name="v1", urlconf_namespace=None, **kwargs):
172+
super(NamespacedApi, self).__init__(api_name=api_name, **kwargs)
173+
self.urlconf_namespace = urlconf_namespace
174+
175+
def register(self, resource, canonical=True):
176+
super(NamespacedApi, self).register(resource, canonical=canonical)
177+
178+
if canonical is True:
179+
# Plop in the namespace here as well.
180+
resource._meta.urlconf_namespace = self.urlconf_namespace
181+
182+
def _build_reverse_url(self, name, args=None, kwargs=None):
183+
namespaced = "%s:%s" % (self.urlconf_namespace, name)
184+
return reverse(namespaced, args=args, kwargs=kwargs)

0 commit comments

Comments
 (0)