Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 1 | from rest_framework import generics |
| 2 | from rest_framework import serializers |
| 3 | from rest_framework.response import Response |
| 4 | from rest_framework import status |
| 5 | from xos.apibase import XOSRetrieveUpdateDestroyAPIView, XOSListCreateAPIView |
| 6 | from rest_framework import viewsets |
| 7 | from django.conf.urls import patterns, url |
Scott Baker | 0744cf4 | 2016-04-08 12:38:54 -0700 | [diff] [blame] | 8 | from xos.exceptions import * |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 9 | from rest_framework.reverse import reverse |
| 10 | from django.core.urlresolvers import get_script_prefix, resolve, Resolver404 |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 11 | |
Scott Baker | bdabb9b | 2016-05-06 16:09:55 -0700 | [diff] [blame] | 12 | # rest_framework 3.x |
| 13 | ReadOnlyField = serializers.ReadOnlyField |
| 14 | |
| 15 | ICON_URLS = {"success": "/static/admin/img/icon_success.gif", |
| 16 | "clock": "/static/admin/img/icon_clock.gif", |
| 17 | "error": "/static/admin/img/icon_error.gif"} |
| 18 | |
| 19 | class PlusObjectMixin: |
| 20 | def getBackendIcon(self): |
| 21 | (icon, tooltip) = self.get_backend_icon() |
| 22 | icon_url = ICON_URLS.get(icon, "unknown") |
| 23 | return icon_url |
| 24 | |
| 25 | def getBackendHtml(self): |
| 26 | (icon, tooltip) = self.get_backend_icon() |
| 27 | icon_url = ICON_URLS.get(icon, "unknown") |
| 28 | |
| 29 | if tooltip: |
| 30 | return '<span title="%s"><img src="%s"></span>' % (tooltip, icon_url) |
| 31 | else: |
| 32 | return '<img src="%s">' % icon_url |
Scott Baker | 3dbdc87 | 2016-03-28 13:22:10 -0700 | [diff] [blame] | 33 | |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 34 | """ PlusSerializerMixin |
| 35 | |
| 36 | Implements Serializer fields that are common to all OpenCloud objects. For |
| 37 | example, stuff related to backend fields. |
| 38 | """ |
| 39 | |
Scott Baker | 272ec45 | 2016-03-31 16:30:00 -0700 | [diff] [blame] | 40 | class PlusModelSerializer(serializers.ModelSerializer): |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 41 | backendIcon = serializers.SerializerMethodField("getBackendIcon") |
| 42 | backendHtml = serializers.SerializerMethodField("getBackendHtml") |
| 43 | |
| 44 | # This will cause a descendant class to pull in the methods defined |
| 45 | # above. See rest_framework/serializers.py: _get_declared_fields(). |
| 46 | base_fields = {"backendIcon": backendIcon, "backendHtml": backendHtml} |
| 47 | # Rest_framework 3.0 uses _declared_fields instead of base_fields |
| 48 | _declared_fields = {"backendIcon": backendIcon, "backendHtml": backendHtml} |
| 49 | |
| 50 | def getBackendIcon(self, obj): |
| 51 | return obj.getBackendIcon() |
| 52 | |
| 53 | def getBackendHtml(self, obj): |
| 54 | return obj.getBackendHtml() |
| 55 | |
Scott Baker | 272ec45 | 2016-03-31 16:30:00 -0700 | [diff] [blame] | 56 | def create(self, validated_data): |
| 57 | property_fields = getattr(self, "property_fields", []) |
| 58 | create_fields = {} |
| 59 | for k in validated_data: |
| 60 | if not k in property_fields: |
| 61 | create_fields[k] = validated_data[k] |
Scott Baker | 0744cf4 | 2016-04-08 12:38:54 -0700 | [diff] [blame] | 62 | instance = self.Meta.model(**create_fields) |
| 63 | |
Scott Baker | abec39c | 2016-04-08 12:42:38 -0700 | [diff] [blame] | 64 | if instance and hasattr(instance,"can_update") and self.context.get('request',None): |
| 65 | user = self.context['request'].user |
| 66 | if user.__class__.__name__=="AnonymousUser": |
| 67 | raise XOSPermissionDenied() |
| 68 | if not instance.can_update(user): |
| 69 | raise XOSPermissionDenied() |
Scott Baker | 272ec45 | 2016-03-31 16:30:00 -0700 | [diff] [blame] | 70 | |
| 71 | for k in validated_data: |
| 72 | if k in property_fields: |
Scott Baker | 0744cf4 | 2016-04-08 12:38:54 -0700 | [diff] [blame] | 73 | setattr(instance, k, validated_data[k]) |
Scott Baker | 272ec45 | 2016-03-31 16:30:00 -0700 | [diff] [blame] | 74 | |
Scott Baker | 0744cf4 | 2016-04-08 12:38:54 -0700 | [diff] [blame] | 75 | instance.caller = self.context['request'].user |
| 76 | instance.save() |
| 77 | return instance |
Scott Baker | 272ec45 | 2016-03-31 16:30:00 -0700 | [diff] [blame] | 78 | |
| 79 | def update(self, instance, validated_data): |
| 80 | nested_fields = getattr(self, "nested_fields", []) |
| 81 | for k in validated_data.keys(): |
| 82 | v = validated_data[k] |
| 83 | if k in nested_fields: |
| 84 | d = getattr(instance,k) |
| 85 | d.update(v) |
| 86 | setattr(instance,k,d) |
| 87 | else: |
| 88 | setattr(instance, k, v) |
| 89 | instance.caller = self.context['request'].user |
| 90 | instance.save() |
| 91 | return instance |
| 92 | |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 93 | class XOSViewSet(viewsets.ModelViewSet): |
Scott Baker | 40fa230 | 2016-03-25 13:33:11 -0700 | [diff] [blame] | 94 | api_path="" |
Scott Baker | 48dca96 | 2016-05-02 17:04:25 -0700 | [diff] [blame] | 95 | read_only=False |
Scott Baker | 40fa230 | 2016-03-25 13:33:11 -0700 | [diff] [blame] | 96 | |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 97 | @classmethod |
Scott Baker | 2b12c19 | 2016-04-01 16:27:53 -0700 | [diff] [blame] | 98 | def get_api_method_path(self): |
| 99 | if self.method_name: |
| 100 | return self.api_path + self.method_name + "/" |
| 101 | else: |
| 102 | return self.api_path |
| 103 | |
| 104 | @classmethod |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 105 | def detail_url(self, pattern, viewdict, name): |
Scott Baker | d9c3a29 | 2016-05-02 16:56:09 -0700 | [diff] [blame] | 106 | return url(self.get_api_method_path() + r'(?P<pk>[a-zA-Z0-9\-_]+)/' + pattern, |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 107 | self.as_view(viewdict), |
| 108 | name=self.base_name+"_"+name) |
| 109 | |
| 110 | @classmethod |
| 111 | def list_url(self, pattern, viewdict, name): |
Scott Baker | 2b12c19 | 2016-04-01 16:27:53 -0700 | [diff] [blame] | 112 | return url(self.get_api_method_path() + pattern, |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 113 | self.as_view(viewdict), |
| 114 | name=self.base_name+"_"+name) |
| 115 | |
| 116 | @classmethod |
Scott Baker | 3dbdc87 | 2016-03-28 13:22:10 -0700 | [diff] [blame] | 117 | def get_urlpatterns(self, api_path="^"): |
Scott Baker | 40fa230 | 2016-03-25 13:33:11 -0700 | [diff] [blame] | 118 | self.api_path = api_path |
| 119 | |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 120 | patterns = [] |
| 121 | |
Scott Baker | 48dca96 | 2016-05-02 17:04:25 -0700 | [diff] [blame] | 122 | if self.read_only: |
| 123 | patterns.append(url(self.get_api_method_path() + '$', self.as_view({'get': 'list'}), name=self.base_name+'_list')) |
| 124 | patterns.append(url(self.get_api_method_path() + '(?P<pk>[a-zA-Z0-9\-_]+)/$', self.as_view({'get': 'retrieve'}), name=self.base_name+'_detail')) |
| 125 | else: |
| 126 | patterns.append(url(self.get_api_method_path() + '$', self.as_view({'get': 'list', 'post': 'create'}), name=self.base_name+'_list')) |
| 127 | patterns.append(url(self.get_api_method_path() + '(?P<pk>[a-zA-Z0-9\-_]+)/$', self.as_view({'get': 'retrieve', 'put': 'update', 'post': 'update', 'delete': 'destroy', 'patch': 'partial_update'}), name=self.base_name+'_detail')) |
Scott Baker | 3a05558 | 2016-03-25 10:55:03 -0700 | [diff] [blame] | 128 | |
| 129 | return patterns |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 130 | |
Scott Baker | 6291e49 | 2016-04-05 21:27:10 -0700 | [diff] [blame] | 131 | def get_serializer_class(self): |
| 132 | if hasattr(self, "custom_serializers") and hasattr(self, "action") and (self.action in self.custom_serializers): |
| 133 | return self.custom_serializers[self.action] |
| 134 | else: |
| 135 | return super(XOSViewSet, self).get_serializer_class() |
| 136 | |
Scott Baker | 0744cf4 | 2016-04-08 12:38:54 -0700 | [diff] [blame] | 137 | def get_object(self): |
| 138 | obj = super(XOSViewSet, self).get_object() |
| 139 | |
| 140 | if self.action=="update" or self.action=="destroy" or self.action.startswith("set_"): |
| 141 | if obj and hasattr(obj,"can_update"): |
| 142 | user = self.request.user |
| 143 | if user.__class__.__name__=="AnonymousUser": |
| 144 | raise XOSPermissionDenied() |
| 145 | if not obj.can_update(user): |
| 146 | raise XOSPermissionDenied() |
| 147 | |
| 148 | return obj |
| 149 | |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 150 | class XOSIndexViewSet(viewsets.ViewSet): |
| 151 | view_urls=[] |
| 152 | subdirs=[] |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 153 | api_path = None |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 154 | |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 155 | def __init__(self, view_urls, subdirs, api_path): |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 156 | self.view_urls = view_urls |
| 157 | self.subdirs = subdirs |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 158 | self.api_path = api_path |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 159 | super(XOSIndexViewSet, self).__init__() |
| 160 | |
| 161 | def list(self, request): |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 162 | endpoints = {} |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 163 | for view_url in self.view_urls: |
| 164 | method_name = view_url[1].split("/")[-1] |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 165 | method_url = "http://" + request.get_host() + get_script_prefix() + self.api_path + "/" + method_name |
| 166 | endpoints[method_name] = method_url |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 167 | |
| 168 | for subdir in self.subdirs: |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 169 | method_name = subdir |
| 170 | method_url = get_script_prefix() + self.api_path + "/" + subdir + "/" |
| 171 | # Check to make sure that an endpoint exists at this method_url. This |
| 172 | # prunes out subdirs that don't have any methods (like examples/) |
| 173 | try: |
| 174 | resolve(method_url) |
| 175 | except Resolver404: |
| 176 | continue |
| 177 | method_url = "http://" + request.get_host() + method_url |
| 178 | endpoints[method_name] = method_url |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 179 | |
Scott Baker | 16cd1f0 | 2016-05-04 21:14:27 -0700 | [diff] [blame] | 180 | return Response(endpoints) |
Scott Baker | c838e88 | 2016-04-05 14:13:09 -0700 | [diff] [blame] | 181 | |