blob: eb3074d79bb9c3915ac4041b3a77631f003d0bea [file] [log] [blame]
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -05001from rest_framework.decorators import api_view
2from rest_framework.response import Response
3from rest_framework.reverse import reverse
4from rest_framework import serializers
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -05005from rest_framework import generics
Scott Bakerdcb9b5d2014-10-07 00:10:17 -07006from rest_framework import status
Scott Bakerc480fb22014-11-12 01:12:28 -08007from rest_framework.generics import GenericAPIView
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -05008from core.models import *
9from django.forms import widgets
Scott Baker46b58542014-08-11 17:26:12 -070010from rest_framework import filters
Scott Baker8ffd7d72014-11-10 15:58:58 -080011from django.conf.urls import patterns, url
Scott Bakercb60f8a2015-02-02 13:53:46 -080012from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
Scott Baker923f0962015-02-02 14:33:08 -080013from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050014
Scott Bakerb9973aa2014-12-23 11:13:52 -080015if hasattr(serializers, "ReadOnlyField"):
16 # rest_framework 3.x
17 IdField = serializers.ReadOnlyField
18else:
19 # rest_framework 2.x
20 IdField = serializers.Field
21
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050022"""
Sapan Bhatia970beb52014-10-08 11:34:23 -040023 Schema of the generator object:
24 all: Set of all Model objects
25 all_if(regex): Set of Model objects that match regex
Scott Bakerc480fb22014-11-12 01:12:28 -080026
Sapan Bhatia970beb52014-10-08 11:34:23 -040027 Model object:
28 plural: English plural of object name
29 camel: CamelCase version of object name
30 refs: list of references to other Model objects
31 props: list of properties minus refs
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050032
Sapan Bhatia970beb52014-10-08 11:34:23 -040033 TODO: Deal with subnets
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050034"""
35
Scott Baker8ffd7d72014-11-10 15:58:58 -080036def get_REST_patterns():
37 return patterns('',
38 url(r'^plstackapi/$', api_root),
39 {% for object in generator.all %}
40 url(r'plstackapi/{{ object.rest_name }}/$', {{ object.camel }}List.as_view(), name='{{ object.singular }}-list'),
41 url(r'plstackapi/{{ object.rest_name }}/(?P<pk>[a-zA-Z0-9\-]+)/$', {{ object.camel }}Detail.as_view(), name ='{{ object.singular }}-detail'),
42 {% endfor %}
43 )
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050044
45@api_view(['GET'])
46def api_root(request, format=None):
47 return Response({
Sapan Bhatia970beb52014-10-08 11:34:23 -040048 {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
49 {% endfor %}
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050050 })
51
52# Based on serializers.py
53
Scott Bakercac09742014-12-17 18:22:33 -080054class XOSModelSerializer(serializers.ModelSerializer):
55 def save_object(self, obj, **kwargs):
56
57 """ rest_framework can't deal with ManyToMany relations that have a
58 through table. In plstackapi, most of the through tables we have
59 use defaults or blank fields, so there's no reason why we shouldn't
60 be able to save these objects.
61
62 So, let's strip out these m2m relations, and deal with them ourself.
63 """
64 obj._complex_m2m_data={};
65 if getattr(obj, '_m2m_data', None):
66 for relatedObject in obj._meta.get_all_related_many_to_many_objects():
67 if (relatedObject.field.rel.through._meta.auto_created):
68 # These are non-trough ManyToMany relations and
69 # can be updated just fine
70 continue
71 fieldName = relatedObject.get_accessor_name()
72 if fieldName in obj._m2m_data.keys():
73 obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
74 del obj._m2m_data[fieldName]
75
76 serializers.ModelSerializer.save_object(self, obj, **kwargs);
77
78 for (accessor, stuff) in obj._complex_m2m_data.items():
79 (relatedObject, data) = stuff
80 through = relatedObject.field.rel.through
81 local_fieldName = relatedObject.field.m2m_reverse_field_name()
Scott Baker148f5e12014-12-18 12:59:04 -080082 remote_fieldName = relatedObject.field.m2m_field_name()
83
84 # get the current set of existing relations
Scott Bakercac09742014-12-17 18:22:33 -080085 existing = through.objects.filter(**{local_fieldName: obj});
Scott Bakercac09742014-12-17 18:22:33 -080086
Scott Baker148f5e12014-12-18 12:59:04 -080087 data_ids = [item.id for item in data]
88 existing_ids = [getattr(item,remote_fieldName).id for item in existing]
Scott Bakercac09742014-12-17 18:22:33 -080089
Scott Baker148f5e12014-12-18 12:59:04 -080090 #print "data_ids", data_ids
91 #print "existing_ids", existing_ids
92
93 # remove relations that are in 'existing' but not in 'data'
94 for item in list(existing):
95 if (getattr(item,remote_fieldName).id not in data_ids):
96 print "delete", getattr(item,remote_fieldName)
97 item.delete() #(purge=True)
98
99 # add relations that are in 'data' but not in 'existing'
100 for item in data:
101 if (item.id not in existing_ids):
102 #print "add", item
103 newModel = through(**{local_fieldName: obj, remote_fieldName: item})
104 newModel.save()
Scott Bakercac09742014-12-17 18:22:33 -0800105
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500106{% for object in generator.all %}
107
108class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
Scott Bakerb9973aa2014-12-23 11:13:52 -0800109 id = IdField()
Sapan Bhatia970beb52014-10-08 11:34:23 -0400110 {% for ref in object.refs %}
111 {% if ref.multi %}
112 {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
113 {% else %}
114 {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
115 {% endif %}
116 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800117 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800118 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800119 def getHumanReadableName(self, obj):
120 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800121 def getValidators(self, obj):
122 try:
123 return obj.getValidators()
124 except:
125 return None
Sapan Bhatia970beb52014-10-08 11:34:23 -0400126 class Meta:
127 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800128 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
Scott Baker9a270922014-07-03 18:01:30 -0700129
Scott Bakercac09742014-12-17 18:22:33 -0800130class {{ object.camel }}IdSerializer(XOSModelSerializer):
Scott Bakerb9973aa2014-12-23 11:13:52 -0800131 id = IdField()
Sapan Bhatia970beb52014-10-08 11:34:23 -0400132 {% for ref in object.refs %}
133 {% if ref.multi %}
Scott Baker79348852014-12-23 10:43:03 -0800134 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True, queryset = {{ ref.camel }}.objects.all())
Sapan Bhatia970beb52014-10-08 11:34:23 -0400135 {% else %}
Scott Baker79348852014-12-23 10:43:03 -0800136 {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
Sapan Bhatia970beb52014-10-08 11:34:23 -0400137 {% endif %}
138 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800139 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800140 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800141 def getHumanReadableName(self, obj):
142 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800143 def getValidators(self, obj):
144 try:
145 return obj.getValidators()
146 except:
147 return None
Scott Bakercac09742014-12-17 18:22:33 -0800148 class Meta:
Sapan Bhatia970beb52014-10-08 11:34:23 -0400149 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800150 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
Scott Baker9a270922014-07-03 18:01:30 -0700151
152
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500153{% endfor %}
154
155serializerLookUp = {
156{% for object in generator.all %}
157 {{ object.camel }}: {{ object.camel }}Serializer,
158{% endfor %}
159 None: None,
160 }
161
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700162class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
163
164 # To handle fine-grained field permissions, we have to check can_update
165 # the object has been updated but before it has been saved.
166
167 def update(self, request, *args, **kwargs):
168 partial = kwargs.pop('partial', False)
169 self.object = self.get_object_or_none()
170
171 serializer = self.get_serializer(self.object, data=request.DATA,
172 files=request.FILES, partial=partial)
173
174 if not serializer.is_valid():
Scott Baker351148c2014-11-25 00:53:25 -0800175 response = {"error": "validation",
176 "specific_error": "not serializer.is_valid()",
177 "reasons": serializer.errors}
178 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700179
180 try:
181 self.pre_save(serializer.object)
182 except ValidationError as err:
183 # full_clean on model instance may be called in pre_save,
184 # so we have to handle eventual errors.
Scott Baker351148c2014-11-25 00:53:25 -0800185 response = {"error": "validation",
186 "specific_error": "ValidationError in pre_save",
187 "reasons": err.message_dict}
188 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700189
190 if serializer.object is not None:
191 if not serializer.object.can_update(request.user):
192 return Response(status=status.HTTP_400_BAD_REQUEST)
193
194 if self.object is None:
195 self.object = serializer.save(force_insert=True)
196 self.post_save(self.object, created=True)
197 return Response(serializer.data, status=status.HTTP_201_CREATED)
198
199 self.object = serializer.save(force_update=True)
200 self.post_save(self.object, created=False)
201 return Response(serializer.data, status=status.HTTP_200_OK)
202
203 def destroy(self, request, *args, **kwargs):
204 obj = self.get_object()
205 if obj.can_update(request.user):
Scott Baker923f0962015-02-02 14:33:08 -0800206 return super(PlanetStackRetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700207 else:
208 return Response(status=status.HTTP_400_BAD_REQUEST)
209
Scott Baker923f0962015-02-02 14:33:08 -0800210 def handle_exception(self, exc):
211 # REST API drops the string attached to Django's PermissionDenied
212 # exception, and replaces it with a generic "Permission Denied"
213 if isinstance(exc, DjangoPermissionDenied):
214 response=Response({'detail': str(exc)}, status=status.HTTP_403_FORBIDDEN)
215 response.exception=True
216 return response
217 else:
218 return super(PlanetStackRetrieveUpdateDestroyAPIView, self).handle_exception(exc)
219
220class PlanetStackListCreateAPIView(generics.ListCreateAPIView):
221 def handle_exception(self, exc):
222 # REST API drops the string attached to Django's PermissionDenied
223 # exception, and replaces it with a generic "Permission Denied"
224 if isinstance(exc, DjangoPermissionDenied):
225 response=Response({'detail': str(exc)}, status=status.HTTP_403_FORBIDDEN)
226 response.exception=True
227 return response
228 else:
229 return super(PlanetStackListCreateAPIView, self).handle_exception(exc)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700230
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500231# Based on core/views/*.py
232{% for object in generator.all %}
233
Scott Baker923f0962015-02-02 14:33:08 -0800234class {{ object.camel }}List(PlanetStackListCreateAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500235 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500236 serializer_class = {{ object.camel }}Serializer
Scott Baker9a270922014-07-03 18:01:30 -0700237 id_serializer_class = {{ object.camel }}IdSerializer
Scott Baker46b58542014-08-11 17:26:12 -0700238 filter_backends = (filters.DjangoFilterBackend,)
239 filter_fields = ({% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
Scott Baker9a270922014-07-03 18:01:30 -0700240
241 def get_serializer_class(self):
Scott Baker71621062015-01-30 15:42:02 -0800242 no_hyperlinks=False
243 if hasattr(self.request,"QUERY_PARAMS"):
244 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker9a270922014-07-03 18:01:30 -0700245 if (no_hyperlinks):
246 return self.id_serializer_class
247 else:
248 return self.serializer_class
249
Tony Mackeb8eb312014-02-04 20:50:39 -0500250 def get_queryset(self):
Scott Baker960431e2015-02-02 10:41:12 -0800251 if (not self.request.user.is_authenticated()):
Scott Bakercb60f8a2015-02-02 13:53:46 -0800252 raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
Tony Mack8f04ee32014-02-05 10:27:39 -0500253 return {{ object.camel }}.select_by_user(self.request.user)
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500254
Tony Mack9525eba2014-02-05 10:57:21 -0500255 def create(self, request, *args, **kwargs):
Scott Bakerc480fb22014-11-12 01:12:28 -0800256 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
257 if not (serializer.is_valid()):
Scott Baker351148c2014-11-25 00:53:25 -0800258 response = {"error": "validation",
259 "specific_error": "not serializer.is_valid()",
260 "reasons": serializer.errors}
261 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerc480fb22014-11-12 01:12:28 -0800262 obj = serializer.object
Scott Bakerde12f092014-10-09 11:09:15 -0700263 obj.caller = request.user
264 if obj.can_update(request.user):
265 return super({{ object.camel }}List, self).create(request, *args, **kwargs)
266 else:
267 raise Exception("failed obj.can_update")
268
Sapan Bhatia970beb52014-10-08 11:34:23 -0400269 ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
270 if (ret.status_code%100 != 200):
271 raise Exception(ret.data)
Scott Bakerde12f092014-10-09 11:09:15 -0700272
273 return ret
274
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500275
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700276class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500277 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500278 serializer_class = {{ object.camel }}Serializer
Scott Baker46b58542014-08-11 17:26:12 -0700279 id_serializer_class = {{ object.camel }}IdSerializer
280
281 def get_serializer_class(self):
Scott Baker71621062015-01-30 15:42:02 -0800282 no_hyperlinks=False
283 if hasattr(self.request,"QUERY_PARAMS"):
284 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker46b58542014-08-11 17:26:12 -0700285 if (no_hyperlinks):
286 return self.id_serializer_class
287 else:
288 return self.serializer_class
Scott Baker71621062015-01-30 15:42:02 -0800289
Tony Mackeb8eb312014-02-04 20:50:39 -0500290 def get_queryset(self):
Scott Baker960431e2015-02-02 10:41:12 -0800291 if (not self.request.user.is_authenticated()):
Scott Bakercb60f8a2015-02-02 13:53:46 -0800292 raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
Tony Mack9525eba2014-02-05 10:57:21 -0500293 return {{ object.camel }}.select_by_user(self.request.user)
294
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700295 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Tony Mack9525eba2014-02-05 10:57:21 -0500296
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700297 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500298
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500299{% endfor %}