Statistics
| Branch: | Tag: | Revision:

root / snf-django-lib / snf_django / lib / api / utils.py @ 4267cb32

History | View | Annotate | Download (4.9 kB)

1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import datetime
35
from dateutil.parser import parse as date_parse
36
from django.utils import simplejson as json
37

    
38
from django.conf import settings
39
from snf_django.lib.api import faults
40

    
41

    
42
class UTC(datetime.tzinfo):
43
    """
44
    Helper UTC time information object.
45
    """
46

    
47
    def utcoffset(self, dt):
48
        return datetime.timedelta(0)
49

    
50
    def tzname(self, dt):
51
        return 'UTC'
52

    
53
    def dst(self, dt):
54
        return datetime.timedelta(0)
55

    
56

    
57
def isoformat(d):
58
    """Return an ISO8601 date string that includes a timezone.
59

60
    >>> from datetime import datetime
61
    >>> d = datetime(2012, 8, 10, 00, 59, 59)
62
    >>> isoformat(d)
63
    '2012-08-10T00:59:59+00:00'
64
    """
65

    
66
    return d.replace(tzinfo=UTC()).isoformat()
67

    
68

    
69
def isoparse(s):
70
    """Parse an ISO8601 date string into a datetime object."""
71

    
72
    if not s:
73
        return None
74

    
75
    try:
76
        since = date_parse(s)
77
        utc_since = since.astimezone(UTC()).replace(tzinfo=None)
78
    except ValueError:
79
        raise faults.BadRequest('Invalid changes-since parameter.')
80

    
81
    now = datetime.datetime.now()
82
    if utc_since > now:
83
        raise faults.BadRequest('changes-since value set in the future.')
84

    
85
    if now - utc_since > datetime.timedelta(seconds=settings.POLL_LIMIT):
86
        raise faults.BadRequest('Too old changes-since value.')
87

    
88
    return utc_since
89

    
90

    
91
def get_json_body(request):
92
    """Get the JSON request body as a Python object.
93

94
    Check that that content type is json and deserialize the body of the
95
    request that contains a JSON document to a Python object.
96

97
    """
98
    data = request.body
99
    content_type = request.META.get("CONTENT_TYPE")
100
    if content_type is None:
101
        raise faults.BadRequest("Missing Content-Type header field")
102
    if content_type.startswith("application/json"):
103
        try:
104
            return json.loads(data)
105
        except UnicodeDecodeError:
106
            raise faults.BadRequest("Could not decode request as UTF-8 string")
107
        except ValueError:
108
            raise faults.BadRequest("Could not decode request body as JSON")
109
    else:
110
        raise faults.BadRequest("Unsupported Content-type: '%s'" %
111
                                content_type)
112

    
113

    
114
def prefix_pattern(prefix, append_slash=True):
115
    """Return a reliable urls.py pattern from a prefix"""
116
    prefix = prefix.strip('/')
117
    if prefix and append_slash:
118
        prefix += '/'
119
    pattern = '^' + prefix
120
    return pattern
121

    
122

    
123
def filter_modified_since(request, objects):
124
    """Filter DB objects based on 'changes-since' request parameter.
125

126
    Parse request for 'changes-since' parameter and get only the DB objects
127
    that have been updated after that time. Otherwise, return the non-deleted
128
    objects.
129

130
    """
131
    since = isoparse(request.GET.get("changes-since"))
132
    if since:
133
        modified_objs = objects.filter(updated__gte=since)
134
        if not modified_objs:
135
            raise faults.NotModified()
136
        return modified_objs
137
    else:
138
        return objects.filter(deleted=False)
139

    
140

    
141
def get_attribute(request, attribute, attr_type=None, required=True):
142
    value = request.get(attribute, None)
143
    if required and value is None:
144
        raise faults.BadRequest("Malformed request. Missing attribute '%s'." %
145
                                attribute)
146
    if attr_type is not None and value is not None\
147
       and not isinstance(value, attr_type):
148
        raise faults.BadRequest("Malformed request. Invalid '%s' field"
149
                                % attribute)
150
    return value