aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/guide/dev_guide.expressions.ngdoc
blob: 177a5e87a05104198231724af77962565c0a3e42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
@workInProgress
@ngdoc overview
@name Developer Guide: Understanding Angular Expressions
@description

Expressions are {@link dev_guide.templates.databinding bindings} that you write in HTML and embed
in templates in order to create views in angular. Angular expressions are similar but not
equivalent to JavaScript expressions.

For example, these are all valid expressions<
"""
The :mod:`request` module provides a :class:`Request` class used to wrap the standard `request`
object received in all the views.

The wrapped request then offers a richer API, in particular :

    - content automatically parsed according to `Content-Type` header,
      and available as :meth:`.DATA<Request.DATA>`
    - full support of PUT method, including support for file uploads
    - form overloading of HTTP method, content type and content
"""
from StringIO import StringIO

from django.http.multipartparser import parse_header
from rest_framework import exceptions
from rest_framework.settings import api_settings


def is_form_media_type(media_type):
    """
    Return True if the media type is a valid form media type.
    """
    base_media_type, params = parse_header(media_type)
    return (base_media_type == 'application/x-www-form-urlencoded' or
            base_media_type == 'multipart/form-data')


class Empty(object):
    """
    Placeholder for unset attributes.
    Cannot use `None`, as that may be a valid value.
    """
    pass


def _hasattr(obj, name):
    return not getattr(obj, name) is Empty


def clone_request(request, method):
    """
    Internal helper method to clone a request, replacing with a different
    HTTP method.  Used for checking permissions against other methods.
    """
    ret = Request(request._request,
                  request.parsers,
                  request.authenticators,
                  request.parser_context)
    ret._data = request._data
    ret._files = request._files
    ret._content_type = request._content_type
    ret._stream = request._stream
    ret._method = method
    if hasattr(request, '_user'):
        ret._user = request._user
    if hasattr(request, '_auth'):
        ret._auth = request._auth
    return ret


class Request(object):
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers_classes(list/tuple). The parsers to use for parsing the
          request content.
        - authentication_classes(list/tuple). The authentications used to try
          authenticating the request's user.
    """

    _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE
    _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE
    _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._method = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self

    def _default_negotiator(self):
        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()

    @property
    def method(self):
        """
        Returns the HTTP method.

        This allows the `method` to be overridden by using a hidden `form`
        field on a form POST request.
        """
        if not _hasattr(self, '_method'):
            self._load_method_and_content_type()
        return self._method

    @property
    def content_type(self):
        """
        Returns the content type header.

        This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`,
        as it allows the content type to be overridden by using a hidden form
        field on a form POST request.
        """
        if not _hasattr(self, '_content_type'):
            self._load_method_and_content_type()
        return self._content_type

    @property
    def stream(self):
        """
        Returns an object that may be used to stream the request content.
        """
        if not _hasattr(self, '_stream'):
            self._load_stream()
        return self._stream

    @property
    def QUERY_PARAMS(self):
        """
        More semantically correct name for request.GET.
        """
        return self._request.GET

    @property
    def DATA(self):
        """
        Parses the request body and returns the data.

        Similar to usual behaviour of `request.POST`, except that it handles
        arbitrary parsers, and also works on methods other than POST (eg PUT).
        """
        if not _hasattr(self, '_data'):
            self._load_data_and_files()
        return self._data

    @property
    def FILES(self):
        """
        Parses the request body and returns any files uploaded in the request.

        Similar to usual behaviour of `request.FILES`, except that it handles
        arbitrary parsers, and also works on methods other than POST (eg PUT).
        """
        if not _hasattr(self, '_files'):
            self._load_data_and_files()
        return self._files

    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            self._user, self._auth = self._authenticate()
        return self._user

    @property
    def auth(self):
        """
        Returns any non-user authentication information associated with the
        request, such as an authentication token.
        """
        if not hasattr(self, '_auth'):
            self._user, self._auth = self._authenticate()
        return self._auth

    def _load_data_and_files(self):
        """
        Parses the request content into self.DATA and self.FILES.
        """
        if not _hasattr(self, '_content_type'):
            self._load_method_and_content_type()

        if not _hasattr(self, '_data'):
            self._data, self._files = self._parse()

    def _load_method_and_content_type(self):
        """
        Sets the method and content_type, and then check if they've
        been overridden.
        """
        self._content_type = self.META.get('HTTP_CONTENT_TYPE',
                                           self.META.get('CONTENT_TYPE', ''))
        self._perform_form_overloading()
        # if the HTTP method was not overloaded, we take the raw HTTP method
        if not _hasattr(self, '_method'):
            self._method = self._request.method

    def _load_stream(self):
        """
        Return the content body of the request, as a stream.
        """
        try:
            content_length = int(self.META.get('CONTENT_LENGTH',
                                    self.META.get('HTTP_CONTENT_LENGTH')))
        except (ValueError, TypeError):
            content_length = 0

        if content_length == 0:
            self._stream = None
        elif hasattr(self._request, 'read'):
            self._stream = self._request
        else:
            self._stream = StringIO(self.raw_post_data)

    def _perform_form_overloading(self):
        """
        If this is a form POST request, then we need to check if the method and
        content/content_type have been overridden by setting them in hidden
        form fields or not.
        """

        USE_FORM_OVERLOADING = (
            self._METHOD_PARAM or
            (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM)
        )

        # We only need to use form overloading on form POST requests.
        if (not USE_FORM_OVERLOADING
            or self._request.method != 'POST'
            or not is_form_media_type(self._content_type)):
            return

        # At this point we're committed to parsing the request as form data.
        self._data = self._request.POST
        self._files = self._request.FILES

        # Method overloading - change the method and remove the param from the content.
        if (self._METHOD_PARAM and
            self._METHOD_PARAM in self._data):
            self._method = self._data[self._METHOD_PARAM].upper()

        # Content overloading - modify the content type, and force re-parse.
        if (self._CONTENT_PARAM and
            self._CONTENTTYPE_PARAM and
            self._CONTENT_PARAM in self._data and
            self._CONTENTTYPE_PARAM in self._data):
            self._content_type = self._data[self._CONTENTTYPE_PARAM]
            self._stream = StringIO(self._data[self._CONTENT_PARAM])
            self._data, self._files = (Empty, Empty)

    def _parse(self):
        """
        Parse the request content, returning a two-tuple of (data, files)

        May raise an `UnsupportedMediaType`, or `ParseError` exception.
        """
        stream = self.stream
        media_type = self.content_type

        if stream is None or media_type is None:
            return (None, None)

        parser = self.negotiator.select_parser(self, self.parsers)

        if not parser:
            raise exceptions.UnsupportedMediaType(media_type)

        parsed = parser.parse(stream, media_type, self.parser_context)

        # Parser classes may return the raw data, or a
        # DataAndFiles object.  Unpack the result as required.
        try:
            return (parsed.data, parsed.files)
        except AttributeError:
            return (parsed, None)

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance in turn.
        Returns a two-tuple of (user, authtoken).
        """
        for authenticator in self.authenticators:
            user_auth_tuple = authenticator.authenticate(self)
            if not user_auth_tuple is None:
                return user_auth_tuple
        return self._not_authenticated()

    def _not_authenticated(self):
        """
        Return a two-tuple of (user, authtoken), representing an
        unauthenticated request.

        By default this will be (AnonymousUser, None).
        """
        if api_settings.UNAUTHENTICATED_USER:
            user = api_settings.UNAUTHENTICATED_USER()
        else:
            user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            auth = api_settings.UNAUTHENTICATED_TOKEN()
        else:
            auth = None

        return (user, auth)

    def __getattr__(self, attr):
        """
        Proxy other attributes to the underlying HttpRequest object.
        """
        return getattr(self._request, attr)
g for the server response, and it will become defined soon). If expression evaluation wasn't forgiving we'd have to write bindings that clutter the code, for example: `{{((a||{}).b||{}).c}}` Similarly, invoking a function `a.b.c()` on undefined or null simply returns undefined. Assignments work the same way in reverse: a.b.c = 10 ...creates the intermediary objects even if a is undefined. ## No Control Flow Statements You cannot write a control flow statement in an expression. The reason behind this is core to the angular philosophy that application logic should be in controllers, not in the view. If you need a conditional (including ternary operators), loop, or to throw from a view expression, delegate to a JavaScript method instead. ## Type Augmentation Built-in types have methods like `[].push()`, but the richness of these methods is limited. Consider the example below, which allows you to do a simple search over a canned set of contacts. The example would be much more complicated if we did not have the `Array:$filter()`. There is no built-in method on `Array` called {@link api/angular.Array.filter $filter} and angular doesn't add it to `Array.prototype` because that could collide with other JavaScript frameworks. For this reason the scope expression evaluator augments the built-in types to make them act like they have extra methods. The actual method for `$filter()` is `angular.Array.filter()`. You can call it from JavaScript. Extensions: You can further extend the expression vocabulary by adding new methods to `angular.Array` or `angular.String`, etc. <doc:example> <doc:source> <div ng:init="friends = [ {name:'John', phone:'555-1212'}, {name:'Mary', phone:'555-9876'}, {name:'Mike', phone:'555-4321'}, {name:'Adam', phone:'555-5678'}, {name:'Julie', phone:'555-8765'}]"></div> Search: <input name="searchText"/> <table class="example3"> <tr><th>Name</th><th>Phone</th><tr> <tr ng:repeat="friend in friends.$filter(searchText)"> <td>{{friend.name}}</td> <td>{{friend.phone}}</td> </tr> </table> </doc:source> <doc:scenario> it('should filter the list', function(){ var tr = using('table.example3').repeater('tr.ng-attr-widget'); expect(tr.count()).toBe(5); input('searchText').enter('a'); expect(tr.count()).toBe(2); }); </doc:scenario> </doc:example> ## Filters When presenting data to the user, you might need to convert the data from its raw format to a user-friendly format. For example, you might have a data object that needs to be formatted according to the locale before displaying it to the user. You can pass expressions through a chain of filters like this: name | uppercase The expression evaluator simply passes the value of name to angular.filter.uppercase. Chain filters using this syntax: value | filter1 | filter2 You can also pass colon-delimited arguments to filters, for example, to display the number 123 with 2 decimal points: 123 | number:2 # The $ You might be wondering, what is the significance of the $ prefix? It is simply a prefix that angular uses, to differentiate its API names from others. If angular didn't use $, then evaluating `a.length()` would return undefined because neither a nor angular define such a property. Consider that in a future version of angular we might choose to add a length method, in which case the behavior of the expression would change. Worse yet, you the developer could create a length property and then we would have a collision. This problem exists because angular augments existing objects with additional behavior. By prefixing its additions with $ we are reserving our namespace so that angular developers and developers who use angular can develop in harmony without collisions. ## Related Topics * {@link dev_guide.compiler.markup Understanding Angular Markup} * {@link dev_guide.templates.filters Understanding Angular Filters} ## Related API * {@link api/angular.compile Angular Compiler API}