File: dj.py

package info (click to toggle)
python-restless 2.2.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 608 kB
  • sloc: python: 2,124; makefile: 148
file content (132 lines) | stat: -rw-r--r-- 4,662 bytes parent folder | download
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

from django.conf import settings
from django.urls import path
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.http import HttpResponse, Http404
from django.views.decorators.csrf import csrf_exempt

from .constants import OK, NO_CONTENT
from .exceptions import NotFound, BadRequest
from .resources import Resource


class DjangoResource(Resource):
    """
    A Django-specific ``Resource`` subclass.

    Doesn't require any special configuration, but helps when working in a
    Django environment.
    """

    def serialize_list(self, data):
        if data is None:
            return super(DjangoResource, self).serialize_list(data)

        if getattr(self, 'paginate', False):
            page_size = getattr(self, 'page_size', getattr(settings, 'RESTLESS_PAGE_SIZE', 10))
            paginator = Paginator(data, page_size)

            page_number = self.request.GET.get('p', 1)

            if page_number not in paginator.page_range:
                raise BadRequest('Invalid page number')

            self.page = paginator.page(page_number)
            data = self.page.object_list

        return super(DjangoResource, self).serialize_list(data)

    def wrap_list_response(self, data):
        response_dict = super(DjangoResource, self).wrap_list_response(data)

        if hasattr(self, 'page'):
            next_page = self.page.has_next() and self.page.next_page_number() or None
            previous_page = self.page.has_previous() and self.page.previous_page_number() or None

            response_dict['pagination'] = {
                'num_pages': self.page.paginator.num_pages,
                'count': self.page.paginator.count,
                'page': self.page.number,
                'start_index': self.page.start_index(),
                'end_index': self.page.end_index(),
                'next_page': next_page,
                'previous_page': previous_page,
                'per_page': self.page.paginator.per_page,
            }

        return response_dict

    # Because Django.
    @classmethod
    def as_list(self, *args, **kwargs):
        return csrf_exempt(super(DjangoResource, self).as_list(*args, **kwargs))

    @classmethod
    def as_detail(self, *args, **kwargs):
        return csrf_exempt(super(DjangoResource, self).as_detail(*args, **kwargs))

    def is_debug(self):
        return settings.DEBUG

    def build_response(self, data, status=OK):
        if status == NO_CONTENT:
            # Avoid crashing the client when it tries to parse nonexisting JSON.
            content_type = 'text/plain'
        else:
            content_type = 'application/json'
        resp = HttpResponse(data, content_type=content_type, status=status)
        return resp

    def build_error(self, err):
        # A bit nicer behavior surrounding things that don't exist.
        if isinstance(err, (ObjectDoesNotExist, Http404)):
            err = NotFound(msg=str(err))

        return super(DjangoResource, self).build_error(err)

    @classmethod
    def build_url_name(cls, name, name_prefix=None):
        """
        Given a ``name`` & an optional ``name_prefix``, this generates a name
        for a URL.

        :param name: The name for the URL (ex. 'detail')
        :type name: string

        :param name_prefix: (Optional) A prefix for the URL's name (for
            resolving). The default is ``None``, which will autocreate a prefix
            based on the class name. Ex: ``BlogPostResource`` ->
            ``api_blog_post_list``
        :type name_prefix: string

        :returns: The final name
        :rtype: string
        """
        if name_prefix is None:
            name_prefix = 'api_{}'.format(
                cls.__name__.replace('Resource', '').lower()
            )

        name_prefix = name_prefix.rstrip('_')
        return '_'.join([name_prefix, name])

    @classmethod
    def urls(cls, name_prefix=None):
        """
        A convenience method for hooking up the URLs.

        This automatically adds a list & a detail endpoint to your URLconf.

        :param name_prefix: (Optional) A prefix for the URL's name (for
            resolving). The default is ``None``, which will autocreate a prefix
            based on the class name. Ex: ``BlogPostResource`` ->
            ``api_blogpost_list``
        :type name_prefix: string

        :returns: A list of ``url`` objects for ``include(...)``
        """
        return [
            path('', cls.as_list(), name=cls.build_url_name('list', name_prefix)),
            path('<pk>/', cls.as_detail(), name=cls.build_url_name('detail', name_prefix)),
        ]