Matthew Schinckel avatar Matthew Schinckel committed 5f4ada8 Merge

Merge

Comments (0)

Files changed (19)

hypermedia/base.py

 from django.core.serializers.json import DjangoJSONEncoder
+from django.core.exceptions import ImproperlyConfigured
 from django.utils import simplejson as json
 from django.utils.translation import ugettext as _
 from django.views import generic
 from django import forms
+from django import http
 
-from hypermedia import http
 from hypermedia.models import log_action, ADDITION, CHANGE, DELETION
+from hypermedia.models import RelList
 
 import mimeparse
 
-class RelList(list):
-    """
-    A list subclass that allows us to use dot notation to search for elements
-    that match the reltype.
-    
-    
-    """
-    def __getattr__(self, name):
-        for item in self:
-            if item['rel'] == name:
-                return item
-
 class AcceptResponseMixin(object):
     CONTENT_TYPES = [
         "application/vnd.Collection+JSON", 
     def render_to_response(self, context):
         content_type = self.get_content_type()
         if not content_type:
-            return http.NotAcceptable(self.CONTENT_TYPES)
+            return http.HttpResponse(self.CONTENT_TYPES, status=406) # Not Acceptable
         if content_type == "text/html":
             for k in ["links", "queries"]:
                 if k in context:
             content = json.dumps(data)
         
         # TODO Handle other response codes?
-        return http.OK(content, content_type)
+        return http.HttpResponse(content, content_type)
 
 class ConditionalAccessMixin(object):
     etag_function = None
         if not new_etag:
             return
         if self.request.META.get('HTTP_IF_NONE_MATCH', None) == new_etag:
-            raise http.NotModified()
+            raise http.HttpResponseNotModified()
         if self.request.META.get('HTTP_IF_MATCH', new_etag) != new_etag:
-            raise http.PreconditionFailed()
+            raise http.HttpResponse(status=412) # Precondition Failed
+        
+    # def dispatch(self, request, *args, **kwargs):
+    #     return super(ConditionalAccessMixin, self).dispatch(request, *args, **kwargs)
+
     
 class ResourceMixin(AcceptResponseMixin, ConditionalAccessMixin):
     def get_links(self, **kwargs):
         return getattr((self.model or self.queryset.model)(), 'get_queries', lambda x:None)(self.request)
             
     def convert_model(self, instance=None):
+        """
+        Given a model instance, generate the code that is expected by the 'items' list objects,
+        and the 'template' object for Collection+JSON.
+        """
         form = self.get_form_class()(instance=instance)
         result = {
             "data":[
     success_url = None
     
     def get_items(self, **kwargs):
-        return [self.convert_model(self.get_object())]
+        return [self.convert_model(self.object)]
     
     def get_links(self, **kwargs):
-        return getattr(self.get_object(), 'get_links', lambda x:None)(self.request)
+        return getattr(self.object, 'get_links', lambda x:None)(self.request)
 
     def get_queries(self, **kwargs):
-        return getattr(self.get_object(), 'get_queries', lambda x:None)(self.request)
+        return getattr(self.object, 'get_queries', lambda x:None)(self.request)
 
     def get_template_names(self):
         return super(DetailResource, self).get_template_names() + ['hypermedia/detail.html']
     def delete(self, request, *args, **kwargs):
         self.object = self.get_object()
         if not self.can_delete():
-            return http.Forbidden(_(u"You may not delete that object"))
+            return http.HttpResponseForbidden(_(u"You may not delete that object"))
         
         with log_action(request.user, self.object, DELETION, request=request):
             self.object.delete()
         
-        return http.SeeOther(self.get_success_url())
+        response = http.HttpResponse(self.get_success_url(), status=303)
+        response['Location'] = self.get_success_url()
+        return response

hypermedia/http.py

-"""
-The classes in here are combinations of django's HttpResponse, but mixin
-the base Exception class, to enable us to catch them.
-
-Technically, perhaps we should only have the actual error classes inherit
-from Exception, as they are the only ones that are truly errors, but having
-HttpResponse as something we can raise, and drop right out of the stack then
-and there is useful, if dirty.
-
-Even request types that are probably not going to be used have been included,
-just because.
-
-Docstrings are from http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html,
-unless otherwised noted.
-"""
-
-import logging
-
-import django.http
-from django.conf import settings
-from django.db.models.query import QuerySet
-from django.utils.translation import ugettext as _
-
-logger = logging.getLogger('repose.http')
-
-class BaseHttpResponse(django.http.HttpResponse, Exception):
-    """
-    A BaseHttpResponse object can be returned to django's handler, and will be used
-    for the response data and meta-data.
-    
-    By also making it a subclass of Exception, we can raise one of them anywhere in our
-    view code (or things called by the view code, but that is probably not a good idea)
-    and that object will be returned, unprocessed.
-    
-    For instance, you may have a view that is wrapped in a decorator that checks for
-    permission to do something (or have a helper function that does the same). This
-    code can raise an exception, and you don't have to worry about returning back up
-    the call chain: the exception is propagated, and the dispatcher can catch all
-    Exceptions that are subclasses of this class, and return those.
-    """
-    pass
-
-class LocationHeaderMixin(object):
-    """
-    Automatically set the 'Location' header on the response, based on the object
-    that has been passed in.
-    
-    If more than one object was found in a container, then don't set it.
-    """
-    def __init__(self, *args, **kwargs):
-        location = kwargs.pop('location', None)
-        super(LocationHeaderMixin, self).__init__(*args, **kwargs)
-        if not location:
-            data = self._container
-            if isinstance(data, (list, tuple, QuerySet)) and len(data) == 1:
-                data = data[0]
-            if hasattr(data, 'get_absolute_url'):
-                location = data.get_absolute_url()
-            elif hasattr(data, 'href'):
-                location = data.href
-            elif isinstance(data, dict):
-                if 'href' in data:
-                    location = data['href']
-                else:
-                    links = []
-                    if 'links' in data:
-                        links = data['links']
-                    for link in links:
-                        if link['rel'] == "self":
-                            location = link["uri"]
-                
-#             else:
-#                 location = data
-        
-        if location:
-            self['Location'] = location
-
-class NoContentMixin(object):
-    """Mixin to prevent a content body from being sent."""
-    def __init__(self, *args, **kwargs):
-        err_msg = _(
-            u'Body provided on a response type that should not contain one.'
-        )
-        super(NoContentMixin, self).__init__(*args, **kwargs)
-        if hasattr(self, '_container') and self._container:
-            logger.warning(err_msg)
-        self._container = []
-
-
-######################
-#
-#   HTTP Responses
-#
-######################
-
-class Continue(BaseHttpResponse):
-    """
-    The client SHOULD continue with its request. This interim response is used to 
-    inform the client that the initial part of the request has been received and has 
-    not yet been rejected by the server. The client SHOULD continue by sending the 
-    remainder of the request or, if the request has already been completed, ignore 
-    this response. The server MUST send a final response after the request has been 
-    completed.
-    """
-    status_code = 100
-
-class SwitchingProtocols(BaseHttpResponse):
-    """
-    The server understands and is willing to comply with the client's request, via 
-    the Upgrade message header field (section 14.42), for a change in the application 
-    protocol being used on this connection. The server will switch protocols to those 
-    defined by the response's Upgrade header field immediately after the empty line 
-    which terminates the 101 response.
-
-    The protocol SHOULD be switched only when it is advantageous to do so. For example, 
-    switching to a newer version of HTTP is advantageous over older versions, and 
-    switching to a real-time, synchronous protocol might be advantageous when 
-    delivering resources that use such features.
-    """
-    status_code = 101
-
-class Processing(BaseHttpResponse):
-    """
-    As a WebDAV request may contain many sub-requests involving file operations, 
-    it may take a long time to complete the request. This code indicates that the 
-    server has received and is processing the request, but no response is available 
-    yet.This prevents the client from timing out and assuming the request was lost.
-    
-    WebDAV: RFC 2518, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 102
-
-class Checkpoint(BaseHttpResponse):
-    """
-    This code is used in the Resumable HTTP Requests Proposal to resume aborted PUT 
-    or POST requests.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 103
-    
-#####################################
-#
-#   Successful HTTP response codes
-# 
-#####################################
-
-class HttpResponseSuccess(BaseHttpResponse):
-    """
-    This class of status codes indicates the action requested by the client was 
-    received, understood, accepted and processed successfully. These status codes
-    are 2xx codes.
-    """
-    pass
-
-class OK(HttpResponseSuccess):
-    """
-    The request has succeeded. The information returned with the response is dependent 
-    on the method used in the request, for example:
-
-    * GET an entity corresponding to the requested resource is sent in the response;
-    * HEAD the entity-header fields corresponding to the requested resource are sent 
-      in the response without any message-body;
-    * POST an entity describing or containing the result of the action;
-    * TRACE an entity containing the request message as received by the end server.
-    """
-    status_code = 200
-
-class Deleted(HttpResponseSuccess):
-    """
-    A convenience class that duplicates OK, but allows us to catch it differently in
-    exception handling, if necessary.
-    """
-    status_code = 200
-
-class Created(LocationHeaderMixin, HttpResponseSuccess):
-    """
-    The request has been fulfilled and resulted in a new resource being created. The 
-    newly created resource can be referenced by the URI(s) returned in the entity of 
-    the response, with the most specific URI for the resource given by a Location header 
-    field. The response SHOULD include an entity containing a list of resource 
-    characteristics and location(s) from which the user or user agent can choose the 
-    one most appropriate. The entity format is specified by the media type given in 
-    the Content-Type header field. The origin server MUST create the resource before 
-    returning the 201 status code. If the action cannot be carried out immediately, 
-    the server SHOULD respond with 202 (Accepted) response instead.
-    
-    A 201 response MAY contain an ETag response header field indicating the current 
-    value of the entity tag for the requested variant just created.
-    """
-    status_code = 201
-    
-class Accepted(LocationHeaderMixin, HttpResponseSuccess):
-    """
-    The request has been accepted for processing, but the processing has not been 
-    completed. The request might or might not eventually be acted upon, as it might 
-    be disallowed when processing actually takes place. There is no facility for 
-    re-sending a status code from an asynchronous operation such as this.
-
-    The 202 response is intentionally non-committal. Its purpose is to allow a server 
-    to accept a request for some other process (perhaps a batch-oriented process that 
-    is only run once per day) without requiring that the user agent's connection to 
-    the server persist until the process is completed. The entity returned with this 
-    response SHOULD include an indication of the request's current status and either 
-    a pointer to a status monitor or some estimate of when the user can expect the 
-    request to be fulfilled.
-    """
-    status_code = 202
-    
-class NonAuthoritativeInformation(HttpResponseSuccess):
-    """
-    The returned metainformation in the entity-header is not the definitive set as 
-    available from the origin server, but is gathered from a local or a third-party 
-    copy. The set presented MAY be a subset or superset of the original version. For 
-    example, including local annotation information about the resource might result 
-    in a superset of the metainformation known by the origin server. Use of this 
-    response code is not required and is only appropriate when the response would 
-    otherwise be 200 (OK).
-    """
-    status_code = 203
-
-class NoContent(NoContentMixin, HttpResponseSuccess):
-    """
-    The server has fulfilled the request but does not need to return an entity-body, 
-    and might want to return updated metainformation. The response MAY include new or 
-    updated metainformation in the form of entity-headers, which if present SHOULD be 
-    associated with the requested variant.
-
-    If the client is a user agent, it SHOULD NOT change its document view from that 
-    which caused the request to be sent. This response is primarily intended to allow 
-    input for actions to take place without causing a change to the user agent's active 
-    document view, although any new or updated metainformation SHOULD be applied to the 
-    document currently in the user agent's active view.
-
-    The 204 response MUST NOT include a message-body, and thus is always terminated by 
-    the first empty line after the header fields.
-    """
-    status_code = 204
-
-class ResetContent(NoContentMixin, HttpResponseSuccess):
-    """
-    The server has fulfilled the request and the user agent SHOULD reset the document 
-    view which caused the request to be sent. This response is primarily intended to 
-    allow input for actions to take place via user input, followed by a clearing of 
-    the form in which the input is given so that the user can easily initiate another 
-    input action. The response MUST NOT include an entity.
-    """
-    status_code = 205
-
-class PartialContent(HttpResponseSuccess):
-    """
-    The server has fulfilled the partial GET request for the resource. 
-    The request MUST have included a Range header field indicating the desired range, 
-    and MAY have included an If-Range header field to make the request conditional.
-
-    The response MUST include the following header fields:
-
-    * Either a Content-Range header field (section 14.16) indicating
-      the range included with this response, or a multipart/byteranges
-      Content-Type including Content-Range fields for each part. If a
-      Content-Length header field is present in the response, its
-      value MUST match the actual number of OCTETs transmitted in the
-      message-body.
-    * Date
-    * ETag and/or Content-Location, if the header would have been sent
-      in a 200 response to the same request
-    * Expires, Cache-Control, and/or Vary, if the field-value might
-      differ from that sent in any previous response for the same variant
-    
-    If the 206 response is the result of an If-Range request that used a strong cache 
-    validator, the response SHOULD NOT include other entity-headers. If the response 
-    is the result of an If-Range request that used a weak validator, the response MUST 
-    NOT include other entity-headers; this prevents inconsistencies between cached 
-    entity-bodies and updated headers. Otherwise, the response MUST include all of the 
-    entity-headers that would have been returned with a 200 (OK) response to the same 
-    request.
-
-    A cache MUST NOT combine a 206 response with other previously cached content if 
-    the ETag or Last-Modified headers do not match exactly.
-
-    A cache that does not support the Range and Content-Range headers MUST NOT cache
-    206 (Partial) responses.
-    """
-    status_code = 206
-
-class MultiStatus(HttpResponseSuccess):
-    """
-    The message body that follows is an XML message and can contain a number of 
-    separate response codes, depending on how many sub-requests were made.
-    
-    WebDAV: RFC 4918, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 207
-
-class AlreadyReported(HttpResponseSuccess):
-    """
-    The members of a DAV binding have already been enumerated in a previous reply 
-    to this request, and are not being included again.
-    
-    WebDAV: RFC 5842, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 208
-
-class IMUsed(HttpResponseSuccess):
-    """
-    The server has fulfilled a GET request for the resource, and the response is 
-    a representation of the result of one or more instance-manipulations applied 
-    to the current instance.
-    
-    WebDAV: RFC 3229, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 226
-
-########################
-#
-#   3xx Response codes
-#
-########################
-
-class HttpResponseRedirect(BaseHttpResponse):
-    """
-    3xx series response codes.
-    """
-    pass
-
-class MultipleChoices(HttpResponseRedirect):
-    """
-    The requested resource corresponds to any one of a set of representations, each 
-    with its own specific location, and agent- driven negotiation information is being 
-    provided so that the user (or user agent) can select a preferred representation and 
-    redirect its request to that location.
-
-    Unless it was a HEAD request, the response SHOULD include an entity containing a 
-    list of resource characteristics and location(s) from which the user or user agent 
-    can choose the one most appropriate. The entity format is specified by the media 
-    type given in the Content- Type header field. Depending upon the format and the 
-    capabilities of the user agent, selection of the most appropriate choice MAY be 
-    performed automatically. However, this specification does not define any standard 
-    for such automatic selection.
-
-    If the server has a preferred choice of representation, it SHOULD include the 
-    specific URI for that representation in the Location field; user agents MAY use 
-    the Location field value for automatic redirection. This response is cacheable 
-    unless indicated otherwise.
-    """
-    status_code = 300
-
-class MovedPermanently(LocationHeaderMixin, HttpResponseRedirect):
-    """
-    The requested resource has been assigned a new permanent URI and any future 
-    references to this resource SHOULD use one of the returned URIs. Clients with 
-    link editing capabilities ought to automatically re-link references to the 
-    Request-URI to one or more of the new references returned by the server, where 
-    possible. This response is cacheable unless indicated otherwise.
-
-    The new permanent URI SHOULD be given by the Location field in the response. Unless 
-    the request method was HEAD, the entity of the response SHOULD contain a short 
-    hypertext note with a hyperlink to the new URI(s).
-
-    If the 301 status code is received in response to a request other than GET or HEAD, 
-    the user agent MUST NOT automatically redirect the request unless it can be 
-    confirmed by the user, since this might change the conditions under which the 
-    request was issued.
-
-    ..note:: When automatically redirecting a POST request after
-      receiving a 301 status code, some existing HTTP/1.0 user agents
-      will erroneously change it into a GET request.
-    """
-    status_code = 301
-
-class Found(LocationHeaderMixin, HttpResponseRedirect):
-    """
-    The requested resource resides temporarily under a different URI. Since the 
-    redirection might be altered on occasion, the client SHOULD continue to use 
-    the Request-URI for future requests. This response is only cacheable if indicated 
-    by a Cache-Control or Expires header field.
-
-    The temporary URI SHOULD be given by the Location field in the response. Unless 
-    the request method was HEAD, the entity of the response SHOULD contain a short 
-    hypertext note with a hyperlink to the new URI(s).
-
-    If the 302 status code is received in response to a request other than GET or HEAD, 
-    the user agent MUST NOT automatically redirect the request unless it can be 
-    confirmed by the user, since this might change the conditions under which the 
-    request was issued.
-
-    ..note:: RFC 1945 and RFC 2068 specify that the client is not allowed
-      to change the method on the redirected request.  However, most
-      existing user agent implementations treat 302 as if it were a 303
-      response, performing a GET on the Location field-value regardless
-      of the original request method. The status codes 303 and 307 have
-      been added for servers that wish to make unambiguously clear which
-      kind of reaction is expected of the client.
-    """
-    status_code = 302
-
-class SeeOther(LocationHeaderMixin, HttpResponseRedirect):
-    """
-    The response to the request can be found under a different URI and SHOULD be 
-    retrieved using a GET method on that resource. This method exists primarily to 
-    allow the output of a POST-activated script to redirect the user agent to a 
-    selected resource. The new URI is not a substitute reference for the originally 
-    requested resource. The 303 response MUST NOT be cached, but the response to the 
-    second (redirected) request might be cacheable.
-
-    The different URI SHOULD be given by the Location field in the response. Unless 
-    the request method was HEAD, the entity of the response SHOULD contain a short 
-    hypertext note with a hyperlink to the new URI(s).
-
-    ..note:: Many pre-HTTP/1.1 user agents do not understand the 303
-      status. When interoperability with such clients is a concern, the
-      302 status code may be used instead, since most user agents react
-      to a 302 response as described here for 303.
-    """
-    status_code = 303
-
-class NotModified(NoContentMixin, HttpResponseRedirect):
-    """
-    If the client has performed a conditional GET request and access is allowed, 
-    but the document has not been modified, the server SHOULD respond with this 
-    status code. The 304 response MUST NOT contain a message-body, and thus is 
-    always terminated by the first empty line after the header fields.
-
-    The response MUST include the following header fields:
-
-    * Date, unless its omission is required (by clockless origin server)
-      If a clockless origin server obeys these rules, and proxies and clients add 
-      their own Date to any response received without one (as already specified by 
-      [RFC 2068], section 14.19), caches will operate correctly.
-
-    * ETag and/or Content-Location, if the header would have been sent in a 200 response
-      to the same request
-    * Expires, Cache-Control, and/or Vary, if the field-value might
-      differ from that sent in any previous response for the same variant
-    
-    If the conditional GET used a strong cache validator, the response SHOULD NOT 
-    include other entity-headers. Otherwise (i.e., the conditional GET used a weak 
-    validator), the response MUST NOT include other entity-headers; this prevents 
-    inconsistencies between cached entity-bodies and updated headers.
-    
-    If a 304 response indicates an entity not currently cached, then the cache MUST 
-    disregard the response and repeat the request without the conditional.
-    
-    If a cache uses a received 304 response to update a cache entry, the cache MUST 
-    update the entry to reflect any new field values given in the response.
-    """
-    status_code = 304
-    # Must set date header.
-
-class UseProxy(LocationHeaderMixin, HttpResponseRedirect):
-    """
-    The requested resource MUST be accessed through the proxy given by the Location 
-    field. The Location field gives the URI of the proxy. The recipient is expected 
-    to repeat this single request via the proxy. 305 responses MUST only be generated 
-    by origin servers.
-
-    ..note:: RFC 2068 was not clear that 305 was intended to redirect a
-      single request, and to be generated by origin servers only.  Not
-      observing these limitations has significant security consequences.
-    """
-    status_code = 305
-
-class SwitchProxy(HttpResponseRedirect):
-    """
-    No longer used. Originally meant: 
-        "Subsequent requests should use the specified proxy."
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 306
-
-class TemporaryRedirect(HttpResponseRedirect):
-    """
-    The requested resource resides temporarily under a different URI. Since the 
-    redirection MAY be altered on occasion, the client SHOULD continue to use the 
-    Request-URI for future requests. This response is only cacheable if indicated 
-    by a Cache-Control or Expires header field.
-
-    The temporary URI SHOULD be given by the Location field in the response. Unless 
-    the request method was HEAD, the entity of the response SHOULD contain a short 
-    hypertext note with a hyperlink to the new URI(s) , since many pre-HTTP/1.1 user 
-    agents do not understand the 307 status. Therefore, the note SHOULD contain the 
-    information necessary for a user to repeat the original request on the new URI.
-
-    If the 307 status code is received in response to a request other than GET or HEAD, 
-    the user agent MUST NOT automatically redirect the request unless it can be 
-    confirmed by the user, since this might change the conditions under which the 
-    request was issued.
-    """
-    status_code = 307
-
-class ResumeIncomplete(HttpResponseRedirect):
-    """
-    This code is used in the Resumable HTTP Requests Proposal to resume aborted PUT 
-    or POST requests.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 308
-
-#######################################
-#
-#   4xx Response codes - client error
-#
-#######################################
-
-class HttpResponseError(BaseHttpResponse):
-    """
-    The 4xx class of status code is intended for cases in which the client seems 
-    to have erred. Except when responding to a HEAD request, the server should 
-    include an entity containing an explanation of the error situation, and whether 
-    it is a temporary or permanent condition. These status codes are applicable to 
-    any request method. User agents should display any included entity to the user.
-    """
-    pass
-
-class BadRequest(HttpResponseError):
-    """
-    The request could not be understood by the server due to malformed syntax. 
-    The client SHOULD NOT repeat the request without modifications.
-    """
-    status_code = 400
-
-class Unauthorized(HttpResponseError):
-    """
-    The request requires user authentication. The response MUST include a 
-    WWW-Authenticate header field containing a challenge applicable to the requested 
-    resource. The client MAY repeat the request with a suitable Authorization header 
-    field. If the request already included Authorization credentials, then the 401 
-    response indicates that authorization has been refused for those credentials. 
-    If the 401 response contains the same challenge as the prior response, and the 
-    user agent has already attempted authentication at least once, then the user 
-    SHOULD be presented the entity that was given in the response, since that entity 
-    might include relevant diagnostic information. HTTP access authentication is 
-    explained in "HTTP Authentication: Basic and Digest Access Authentication" 
-    
-    http://www.w3.org/Protocols/rfc2616/rfc2616-sec17.html#bib43
-    """
-    status_code = 401
-    
-    def __init__(self, *args, **kwargs):
-        super(Unauthorized, self).__init__(*args, **kwargs)
-        # TODO: Look for the authentication type being used here.
-        if settings.HTTP_AUTH_REALM:
-            self['WWW-Authenticate'] = 'Basic realm=%s' % (
-                settings.HTTP_AUTH_REALM
-            )
-
-class PaymentRequired(HttpResponseError):
-    """
-    This code is reserved for future use.
-    
-    Addendum - from http://en.wikipedia.org/wiki/List_of_HTTP_status_codes:
-    
-    The original intention was that this code might be used as part of some form 
-    of digital cash or micropayment scheme, but that has not happened, and this code 
-    is not usually used. As an example of its use, however, Apple's MobileMe service 
-    generates a 402 error ("httpStatusCode:402" in the Mac OS X Console log) if the 
-    MobileMe account is delinquent.
-    """
-    status_code = 402
-    
-class Forbidden(HttpResponseError):
-    """
-    The server understood the request, but is refusing to fulfill it. Authorization 
-    will not help and the request SHOULD NOT be repeated. If the request method was 
-    not HEAD and the server wishes to make public why the request has not been 
-    fulfilled, it SHOULD describe the reason for the refusal in the entity. If the 
-    server does not wish to make this information available to the client, the 
-    status code 404 (Not Found) can be used instead.
-    """
-    status_code = 403
-
-class NotFound(HttpResponseError):
-    """
-    The server has not found anything matching the Request-URI. No indication is 
-    given of whether the condition is temporary or permanent. The 410 (Gone) status 
-    code SHOULD be used if the server knows, through some internally configurable 
-    mechanism, that an old resource is permanently unavailable and has no forwarding 
-    address. This status code is commonly used when the server does not wish to 
-    reveal exactly why the request has been refused, or when no other response 
-    is applicable.
-    """
-    status_code = 404
-
-class MethodNotAllowed(HttpResponseError):
-    """
-    The method specified in the Request-Line is not allowed for the resource 
-    identified by the Request-URI. The response MUST include an Allow header 
-    containing a list of valid methods for the requested resource.
-    """
-    status_code = 405
-    # Allow header required
-
-class NotAcceptable(HttpResponseError):
-    """
-    The resource identified by the request is only capable of generating response 
-    entities which have content characteristics not acceptable according to the accept 
-    headers sent in the request.
-
-    Unless it was a HEAD request, the response SHOULD include an entity containing a 
-    list of available entity characteristics and location(s) from which the user or 
-    user agent can choose the one most appropriate. The entity format is specified by 
-    the media type given in the Content-Type header field. Depending upon the format 
-    and the capabilities of the user agent, selection of the most appropriate choice 
-    MAY be performed automatically. However, this specification does not define any 
-    standard for such automatic selection.
-
-    ..note:: HTTP/1.1 servers are allowed to return responses which are
-      not acceptable according to the accept headers sent in the
-      request. In some cases, this may even be preferable to sending a
-      406 response. User agents are encouraged to inspect the headers of
-      an incoming response to determine if it is acceptable.
-    
-    If the response could be unacceptable, a user agent SHOULD temporarily stop 
-    receipt of more data and query the user for a decision on further actions.
-    """
-    status_code = 406
-
-class ProxyAuthenticationRequired(HttpResponseError):
-    """
-    This code is similar to 401 (Unauthorized), but indicates that the client must 
-    first authenticate itself with the proxy. The proxy MUST return a Proxy-Authenticate 
-    header field containing a challenge applicable to the proxy for the 
-    requested resource. The client MAY repeat the request with a suitable 
-    Proxy-Authorization header field (section 14.34). HTTP access authentication 
-    is explained in "HTTP Authentication: Basic and Digest Access Authentication".
-    """
-    status_code = 407
-
-class RequestTimeout(HttpResponseError):
-    """
-    The client did not produce a request within the time that the server was 
-    prepared to wait. The client MAY repeat the request without modifications 
-    at any later time.
-    """
-    status_code = 408
-
-class Conflict(HttpResponseError):
-    """
-    The request could not be completed due to a conflict with the current state of 
-    the resource. This code is only allowed in situations where it is expected that 
-    the user might be able to resolve the conflict and resubmit the request. The 
-    response body SHOULD include enough information for the user to recognize the 
-    source of the conflict. Ideally, the response entity would include enough 
-    information for the user or user agent to fix the problem; however, that 
-    might not be possible and is not required.
-    
-    Conflicts are most likely to occur in response to a PUT request. For example, 
-    if versioning were being used and the entity being PUT included changes to a 
-    resource which conflict with those made by an earlier (third-party) request, 
-    the server might use the 409 response to indicate that it can't complete the 
-    request. In this case, the response entity would likely contain a list of the 
-    differences between the two versions in a format defined by the response 
-    Content-Type.
-    """
-    status_code = 409
-
-class Gone(HttpResponseError):
-    """
-    The requested resource is no longer available at the server and no forwarding 
-    address is known. This condition is expected to be considered permanent. Clients 
-    with link editing capabilities SHOULD delete references to the Request-URI after 
-    user approval. If the server does not know, or has no facility to determine, 
-    whether or not the condition is permanent, the status code 404 (Not Found) SHOULD 
-    be used instead. This response is cacheable unless indicated otherwise.
-
-    The 410 response is primarily intended to assist the task of web maintenance by 
-    notifying the recipient that the resource is intentionally unavailable and that 
-    the server owners desire that remote links to that resource be removed. Such an 
-    event is common for limited-time, promotional services and for resources belonging 
-    to individuals no longer working at the server's site. It is not necessary to mark 
-    all permanently unavailable resources as "gone" or to keep the mark for any length 
-    of time -- that is left to the discretion of the server owner.
-    """
-    status_code = 410
-
-class LengthRequired(HttpResponseError):
-    """
-    The server refuses to accept the request without a defined Content- Length. 
-    The client MAY repeat the request if it adds a valid Content-Length header 
-    field containing the length of the message-body in the request message.
-    """
-    status_code = 411
-
-class PreconditionFailed(HttpResponseError):
-    """
-    The precondition given in one or more of the request-header fields evaluated 
-    to false when it was tested on the server. This response code allows the client 
-    to place preconditions on the current resource metainformation (header field data) 
-    and thus prevent the requested method from being applied to a resource other than 
-    the one intended.
-    """
-    status_code = 412
-
-class RequestEntityTooLarge(HttpResponseError):
-    """
-    The server is refusing to process a request because the request entity is larger 
-    than the server is willing or able to process. The server MAY close the connection 
-    to prevent the client from continuing the request.
-
-    If the condition is temporary, the server SHOULD include a Retry-After header 
-    field to indicate that it is temporary and after what time the client MAY try again.
-    """
-    status_code = 413
-
-class RequestURITooLong(HttpResponseError):
-    """
-    The server is refusing to service the request because the Request-URI is longer 
-    than the server is willing to interpret. This rare condition is only likely to 
-    occur when a client has improperly converted a POST request to a GET request with 
-    long query information, when the client has descended into a URI "black hole" of 
-    redirection (e.g., a redirected URI prefix that points to a suffix of itself), or 
-    when the server is under attack by a client attempting to exploit security holes 
-    present in some servers using fixed-length buffers for reading or manipulating 
-    the Request-URI.
-    """
-    status_code = 414
-
-class UnsupportedMediaType(HttpResponseError):
-    """
-    The server is refusing to service the request because the entity of the request 
-    is in a format not supported by the requested resource for the requested method.
-    """
-    status_code = 415
-    
-class RequestedRangeNotSatisfiable(HttpResponseError):
-    """
-    A server SHOULD return a response with this status code if a request included a 
-    Range request-header field (section 14.35), and none of the range-specifier 
-    values in this field overlap the current extent of the selected resource, and 
-    the request did not include an If-Range request-header field. (For byte-ranges, 
-    this means that the first- byte-pos of all of the byte-range-spec values were 
-    greater than the current length of the selected resource.)
-
-    When this status code is returned for a byte-range request, the response SHOULD 
-    include a Content-Range entity-header field specifying the current length of the 
-    selected resource. This response MUST NOT use the multipart/byteranges content-type.
-    """
-    status_code = 416
-
-class ExpectationFailed(HttpResponseError):
-    """
-    The expectation given in an Expect request-header field could not be met by 
-    this server, or, if the server is a proxy, the server has unambiguous evidence 
-    that the request could not be met by the next-hop server.
-    """
-    status_code = 417
-
-class ImATeapot(HttpResponseError):
-    """
-    This code was defined in 1998 as one of the traditional IETF April Fools' jokes, 
-    in RFC 2324, Hyper Text Coffee Pot Control Protocol, and is not expected to be 
-    implemented by actual HTTP servers. However, known implementations do exist.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 418
-    
-class UnprocessableEntity(HttpResponseError):
-    """
-    The request was well-formed but was unable to be followed due to semantic 
-    errors.
-    
-    RFC 2324, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 422
-
-class Locked(HttpResponseError):
-    """
-    The resource that is being accessed is Locked.
-    
-    WebDAV: RFC 4918, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 423
-
-class FailedDependency(HttpResponseError):
-    """
-    Request failed due to the failure of the previous request. (eg, a PROPPATCH)
-    
-    WebDAV: RFC 4918, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 424
-
-class UnorderedCollection(HttpResponseError):
-    """
-    Defined in drafts of "WebDAV Advanced Collections Protocol", but not present 
-    in "Web Distributed Authoring and Versioning (WebDAV) Ordered Collections 
-    Protocol".
-    
-    WebDAV: RFC 3648, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 425
-
-class UpgradeRequired(HttpResponseError):
-    """
-    The client should switch to a different protocol such as TLS/1.0.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 426
-
-class PreconditionRequired(HttpResponseError):
-    """
-    The origin server requires the request to be conditional. Intended to prevent 
-    "the 'lost update' problem, where a client GETs a resource's state, modifies 
-    it, and PUTs it back to the server, when meanwhile a third party has modified 
-    the state on the server, leading to a conflict."
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 428
-
-class TooManyRequests(HttpResponseError):
-    """
-    The user has sent too many requests in a given amount of time. 
-    Intended for use with rate limiting schemes.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 429
-
-class RequestHeaderFieldsTooLarge(HttpResponseError):
-    """
-    The server is unwilling to process the request because either an individual 
-    header field, or all the header fields collectively, are too large.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 431
-
-class NoResponse(HttpResponseError):
-    """
-    An nginx HTTP server extension. The server returns no information to the 
-    client and closes the connection (useful as a deterrent for malware).
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 444
-    
-class RetryWith(LocationHeaderMixin, HttpResponseError):
-    """
-    The 449 Retry With status code indicates that the request cannot be satisfied 
-    because insufficient information was provided by the client.
-    
-    From: http://msdn.microsoft.com/en-us/library/dd891478(v=prot.10).aspx
-    """
-    status_code = 449
-
-class ClientClosedRequest(HttpResponseError):
-    """
-    An Nginx HTTP server extension. This code is introduced to log the case when 
-    the connection is closed by client while HTTP server is processing its request, 
-    making server unable to send the HTTP header back.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 499
-
-#####################################
-#
-#   5xx Status Codes: Server Error
-#
-#####################################
-
-class HttpResponseServerError(BaseHttpResponse):
-    """
-    The server failed to fulfill an apparently valid request.
-    
-    Response status codes beginning with the digit "5" indicate cases in 
-    which the server is aware that it has encountered an error or is 
-    otherwise incapable of performing the request. Except when responding 
-    to a HEAD request, the server should include an entity containing an 
-    explanation of the error situation, and indicate whether it is a 
-    temporary or permanent condition. Likewise, user agents should display 
-    any included entity to the user. These response codes are applicable to 
-    any request method.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    pass
-
-class InternalServerError(HttpResponseServerError):
-    """
-    The server encountered an unexpected condition which prevented it from fulfilling 
-    the request.
-    """
-    status_code = 500
-
-class NotImplemented(HttpResponseServerError):
-    """
-    The server does not support the functionality required to fulfill the request. 
-    This is the appropriate response when the server does not recognize the request 
-    method and is not capable of supporting it for any resource.
-    """
-    status_code = 501
-
-class BadGateway(HttpResponseServerError):
-    """
-    The server, while acting as a gateway or proxy, received an invalid response 
-    from the upstream server it accessed in attempting to fulfill the request.
-    """
-    status_code = 502
-        
-class ServiceNotAvailable(HttpResponseServerError):
-    """
-    The server is currently unable to handle the request due to a temporary 
-    overloading or maintenance of the server. The implication is that this is a 
-    temporary condition which will be alleviated after some delay. If known, the 
-    length of the delay MAY be indicated in a Retry-After header. If no Retry-After 
-    is given, the client SHOULD handle the response as it would for a 500 response.
-
-    ..note:: The existence of the 503 status code does not imply that a
-      server must use it when becoming overloaded. Some servers may wish
-      to simply refuse the connection.
-    """
-    status_code = 503
-    
-    def __init__(self, data=_(u"Server unavailable."), time=120):
-        super(ServiceNotAvailable, self).__init__(data)
-        self['Retry-After'] = time
-
-class ServiceUnavailable(ServiceNotAvailable):
-    """
-    Convenience class for different spelling.
-    """
-    pass
-
-class GatewayTimeout(HttpResponseServerError):
-    """
-    The server, while acting as a gateway or proxy, did not receive a timely response 
-    from the upstream server specified by the URI (e.g. HTTP, FTP, LDAP) or some other 
-    auxiliary server (e.g. DNS) it needed to access in attempting to complete the 
-    request.
-
-    ..note:: Note to implementors: some deployed proxies are known to
-      return 400 or 500 when DNS lookups time out.
-    """
-    status_code = 504
-
-class HttpVersionNotSupported(HttpResponseServerError):
-    """
-    The server does not support, or refuses to support, the HTTP protocol version 
-    that was used in the request message. The server is indicating that it is unable 
-    or unwilling to complete the request using the same major version as the client, 
-    other than with this error message. The response SHOULD contain an entity 
-    describing why that version is not supported and what other protocols are 
-    supported by that server.
-    """
-    status_code = 505
-
-class VariantAlsoNegotiates(HttpResponseServerError):
-    """
-    Transparent content negotiation for the request results in a circular reference.
-    
-    RFC 2295, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 506
-
-class InsufficientStorage(HttpResponseServerError):
-    """
-    The server is unable to store the representation needed to complete the request.
-    
-    WebDAV: RFC 4918, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 507
-
-class LoopDetected(HttpResponseServerError):
-    """
-    The server detected an infinite loop while processing the request 
-    (sent in lieu of 208).
-    
-    WebDAV: RFC 5842, via http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 508
-
-class BandwithLimitExceeded(HttpResponseServerError):
-    """
-    This status code, while used by many servers, is not specified in any RFCs.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 509
-
-class NotExtended(HttpResponseServerError):
-    """
-    Further extensions to the request are required for the server to fulfill it.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 510
-    
-
-class NetworkAuthenticationRequired(HttpResponseServerError):
-    """
-    The client needs to authenticate to gain network access. Intended for 
-    use by intercepting proxies used to control access to the network (e.g. 
-    "captive portals" used to require agreement to Terms of Service before 
-    granting full Internet access via a Wi-Fi hotspot)
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 511
-    
-class NetworkReadTimeout(HttpResponseServerError):
-    """
-    This status code is not specified in any RFCs, but is used by some 
-    HTTP proxies to signal a network read timeout behind the proxy to a 
-    client in front of the proxy.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 598
-    
-class NetworkConnectTimeout(HttpResponseServerError):
-    """
-    This status code is not specified in any RFCs, but is used by some
-    HTTP proxies to signal a network connect timeout behind the proxy 
-    to a client in front of the proxy.
-    
-    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-    """
-    status_code = 599

hypermedia/models.py

 from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy as _
 
+class RelList(list):
+    """
+    A list subclass that allows us to use dot notation to search for elements
+    that match the reltype.
+    
+    >>> links = RelList([{"rel":"self", "href": "self"}, {"rel":"other", "href":"other"}])
+    >>> links.self["href"]
+    'self'
+    >>> links.other["href"]
+    'other'
+    >>> links.foo
+    
+    """
+    def __getattr__(self, name):
+        for item in self:
+            if item['rel'] == name:
+                return item
+
 LOGGER = logging.getLogger('hypermedia')
 
 UNSPECIFIED = 0
Add a comment to this file

hypermedia/tests/__init__.py

Empty file added.

hypermedia/utils.py

+class RelList(list):
+    """
+    A list subclass that allows us to use dot notation to search for elements
+    that match the reltype.
+    
+    >>> links = RelList([{"rel":"self", "href": "self"}, {"rel":"other", "href":"other"}])
+    >>> links.self["href"]
+    'self'
+    
+    """
+    def __getattr__(self, name):
+        for item in self:
+            if item['rel'] == name:
+                return item
Add a comment to this file

test/project/formats/__init__.py

Empty file added.

test/project/formats/admin.py

+from django.contrib import admin
+from formats.models import DateFormat, TimeFormat, NameFormat
+
+admin.site.register(DateFormat)
+admin.site.register(TimeFormat)
+admin.site.register(NameFormat)

test/project/formats/models.py

+from django.db import models
+
+class BaseFormat(models.Model):
+    name = models.CharField(max_length=64, unique=True)
+    format = models.CharField(max_length=64)
+    
+    class Meta:
+        abstract = True
+
+    def __unicode__(self):
+        return "%s (%s)" % (self.name, self.format)
+    
+    def format_value(self, value):
+        return self.format % value
+    
+
+class DateFormat(BaseFormat):
+    def format_value(self, value):
+        return value.strftime(self.format)
+
+class TimeFormat(BaseFormat):
+    def format_value(self, value):
+        return value.strftime(self.format)
+
+class NameFormat(BaseFormat):
+    pass
+
+class UserFormats(models.Model):
+    user = models.OneToOneField('auth.User', related_name="formats")
+    date_format = models.ForeignKey(DateFormat, null=True, blank=True)
+    time_format = models.ForeignKey(TimeFormat, null=True, blank=True)
+    name_format = models.ForeignKey(NameFormat, null=True, blank=True)
+
+# After fetching a User from the db, ensure they have a .formats object?

test/project/formats/tests.py

+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.assertEqual(1 + 1, 2)

test/project/formats/views.py

+from django.core.urlresolvers import reverse
+
+from hypermedia.base import ListResource, DetailResource
+
+from formats.models import UserFormats
+
+class UserFormatsView(DetailResource):
+    model = UserFormats
+    success_url = '.'
+    
+    def get_object(self):
+        try:
+            return self.request.user.formats
+        except UserFormats.DoesNotExist:
+            formats = UserFormats.objects.create(user=self.request.user)
+            return formats
+    

test/project/forms.py

+from django import forms
+from django.contrib.admin.models import User
+
+class UserDetailForm(forms.ModelForm):    
+    class Meta:
+        model = User
+        fields = (
+            'first_name', 'last_name',
+            'username', 'email'
+        )

test/project/settings.py

-# Django settings for project project.
+import os
 
 DEBUG = True
 TEMPLATE_DEBUG = DEBUG
 
+PROJECT_ROOT = os.path.dirname(__file__)
+
 ADMINS = (
     # ('Your Name', 'your_email@example.com'),
     ('Matthew Schinckel', 'matt@schinckel.net'),
     # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
     # Always use forward slashes, even on Windows.
     # Don't forget to use absolute paths, not relative paths.
+    os.path.join(PROJECT_ROOT, 'templates'),
 )
 
 INSTALLED_APPS = (
     'django.contrib.admindocs',
     'hypermedia',
     'tasks',
+    'formats',
     'devserver',
 )
 

test/project/tasks/conf.py

+from django.conf.urls.defaults import patterns, include, url
+
+from tasks.views import TaskList, TaskDetail
+
+urlpatterns = patterns('',
+  url(r'^$', TaskList.as_view(), name="list"),
+  url(r'^(?P<pk>\d+)/$', TaskDetail.as_view(), name="detail"),
+)
+
+urls = (urlpatterns, "tasks", "tasks")

test/project/tasks/forms.py

+from django import forms

test/project/tasks/models.py

 
     @models.permalink
     def get_absolute_url(self):
-        return ('task_detail',(), {'pk':self.pk})
+        return ('tasks:detail',(), {'pk':self.pk})
     
     def get_links(self, request):
         links = []

test/project/tasks/views.py

 
 from hypermedia.base import ListResource, DetailResource
 
-from models import Task
+from tasks.models import Task
 
+# Don't really like this, too tightly coupled.
+from project.views import base_links
 
 class TaskList(ListResource):
     model = Task
     
     def get_success_url(self):
-        return reverse('task_list')
+        return reverse('tasks:list')
     
     def get_links(self, **kwargs):
-        return [
-            {"rel": "self", "href": reverse('task_list'), "prompt": "Task List"},
-        ]
+        return base_links() + []
 
 class TaskDetail(DetailResource):
     model = Task
     def get_success_url(self):
         if self.object.pk:
             return self.object.get_absolute_url()
-        return reverse('task_list')
+        return reverse('tasks:list')
         
     def get_links(self, **kwargs):
-        return [
-            {"rel": "collection", "href": reverse('task_list'), "prompt":"Task List"},
-            {"rel": "self", "href": kwargs.get('object', self.get_object()).get_absolute_url(), "prompt": "This Task"},
+        return base_links() + [
+            {"rel": "collection", "href": reverse('tasks:list'), "prompt":"Task List"},
+            {"rel": "self", "href": kwargs.get('object', self.object).get_absolute_url(), "prompt": "This Task"},
         ]

test/project/templates/base.html

+{% extends 'hypermedia/base.html' %}

test/project/urls.py

 from django.contrib import admin
 admin.autodiscover()
 
-from tasks.views import TaskList, TaskDetail
+from views import Root, UserOptions
+import tasks.conf
+from formats.views import UserFormatsView
 
 urlpatterns = patterns('',
-    # Examples:
-    # url(r'^$', 'project.views.home', name='home'),
-    # url(r'^project/', include('project.foo.urls')),
-
-    # Uncomment the admin/doc line below to enable admin documentation:
     url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
-
-    # Uncomment the next line to enable the admin:
     url(r'^admin/', include(admin.site.urls)),
     
-    url(r'^tasks/$', TaskList.as_view(), name="task_list"),
-    url(r'^tasks/(?P<pk>\d+)/', TaskDetail.as_view(), name="task_detail"),
+    url(r'^$', Root.as_view(), name="root"),
+    url(r'^tasks/', include(tasks.conf.urls)),
+    url(r'^user/$', UserOptions.as_view(), name="user"),
+    url(r'^user/formats/$', UserFormatsView.as_view(), name="user_formats"),
 )

test/project/views.py

+from django.core.urlresolvers import reverse
+
+from hypermedia.base import Resource, DetailResource
+
+from forms import UserDetailForm
+
+def base_links():
+    return [
+        {"rel": "root", "href": reverse('root'), "prompt": "Home"},
+        {"rel": "user", "href": reverse('user'), "prompt": "You"},
+    ]
+
+class Root(Resource):
+    template_name = 'base.html'
+    
+    def get_links(self):
+        return base_links() + [
+            {"rel": "links", "href": reverse('tasks:list'), "prompt": "Task List"},
+        ]
+
+class UserOptions(DetailResource):
+    form_class = UserDetailForm
+    success_url = "."
+    
+    def get_object(self):
+        return self.request.user
+    
+    def get_links(self, **kwargs):
+        links = [
+            {"rel": "formats", "href": reverse("user_formats"), "prompt":"Formats"},
+        ]
+        if self.request.user.is_staff:
+            links.append({"rel":"admin", "href": reverse("admin:index"), "prompt":"Admin"})
+        return base_links() + links
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.