blob: 7ba0fe3163e8c2385d273d47c31574f85ba02066 [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 Baker89a7b7c2014-10-07 00:10:17 -07006from rest_framework import status
Scott Baker6ad17902014-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 Bakerf556b922014-11-10 15:58:58 -080011from django.conf.urls import patterns, url
Scott Baker4af864e2015-02-02 13:53:46 -080012from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
Scott Baker15af5652015-02-02 14:33:08 -080013from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050014
Scott Bakera009d562014-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 Bhatiaf69dd5c2014-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 Baker6ad17902014-11-12 01:12:28 -080026
Sapan Bhatiaf69dd5c2014-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 Bhatiaf69dd5c2014-10-08 11:34:23 -040033 TODO: Deal with subnets
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050034"""
35
Scott Bakerf556b922014-11-10 15:58:58 -080036def get_REST_patterns():
37 return patterns('',
Scott Baker1faefd32015-02-02 15:53:37 -080038 url(r'^xos/$', api_root),
Scott Bakerf556b922014-11-10 15:58:58 -080039 {% for object in generator.all %}
Scott Baker1faefd32015-02-02 15:53:37 -080040 url(r'xos/{{ object.rest_name }}/$', {{ object.camel }}List.as_view(), name='{{ object.singular }}-list'),
41 url(r'xos/{{ object.rest_name }}/(?P<pk>[a-zA-Z0-9\-]+)/$', {{ object.camel }}Detail.as_view(), name ='{{ object.singular }}-detail'),
Scott Bakerf556b922014-11-10 15:58:58 -080042 {% endfor %}
43 )
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050044
45@api_view(['GET'])
46def api_root(request, format=None):
47 return Response({
Sapan Bhatiaf69dd5c2014-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 Baker71b21f62014-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
Scott Baker1faefd32015-02-02 15:53:37 -080058 through table. In xos, most of the through tables we have
Scott Baker71b21f62014-12-17 18:22:33 -080059 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 Bakerd923b812014-12-18 12:59:04 -080082 remote_fieldName = relatedObject.field.m2m_field_name()
83
84 # get the current set of existing relations
Scott Baker71b21f62014-12-17 18:22:33 -080085 existing = through.objects.filter(**{local_fieldName: obj});
Scott Baker71b21f62014-12-17 18:22:33 -080086
Scott Bakerd923b812014-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 Baker71b21f62014-12-17 18:22:33 -080089
Scott Bakerd923b812014-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 Baker71b21f62014-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 Bakera009d562014-12-23 11:13:52 -0800109 id = IdField()
Sapan Bhatiaf69dd5c2014-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 Bakerb744f1c2014-11-21 11:32:24 -0800117 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker96eb3fd2014-11-25 00:53:25 -0800118 validators = serializers.SerializerMethodField("getValidators")
Scott Bakerb744f1c2014-11-21 11:32:24 -0800119 def getHumanReadableName(self, obj):
120 return str(obj)
Scott Baker96eb3fd2014-11-25 00:53:25 -0800121 def getValidators(self, obj):
122 try:
123 return obj.getValidators()
124 except:
125 return None
Sapan Bhatiaf69dd5c2014-10-08 11:34:23 -0400126 class Meta:
127 model = {{ object.camel }}
Scott Baker96eb3fd2014-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 Baker71b21f62014-12-17 18:22:33 -0800130class {{ object.camel }}IdSerializer(XOSModelSerializer):
Scott Bakera009d562014-12-23 11:13:52 -0800131 id = IdField()
Sapan Bhatiaf69dd5c2014-10-08 11:34:23 -0400132 {% for ref in object.refs %}
133 {% if ref.multi %}
Scott Baker0461aee2014-12-23 10:43:03 -0800134 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True, queryset = {{ ref.camel }}.objects.all())
Sapan Bhatiaf69dd5c2014-10-08 11:34:23 -0400135 {% else %}
Scott Baker0461aee2014-12-23 10:43:03 -0800136 {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
Sapan Bhatiaf69dd5c2014-10-08 11:34:23 -0400137 {% endif %}
138 {% endfor %}
Scott Bakerb744f1c2014-11-21 11:32:24 -0800139 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker96eb3fd2014-11-25 00:53:25 -0800140 validators = serializers.SerializerMethodField("getValidators")
Scott Bakerb744f1c2014-11-21 11:32:24 -0800141 def getHumanReadableName(self, obj):
142 return str(obj)
Scott Baker96eb3fd2014-11-25 00:53:25 -0800143 def getValidators(self, obj):
144 try:
145 return obj.getValidators()
146 except:
147 return None
Scott Baker71b21f62014-12-17 18:22:33 -0800148 class Meta:
Sapan Bhatiaf69dd5c2014-10-08 11:34:23 -0400149 model = {{ object.camel }}
Scott Baker96eb3fd2014-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 Baker89a7b7c2014-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 Baker96eb3fd2014-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 Baker89a7b7c2014-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 Baker96eb3fd2014-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 Baker89a7b7c2014-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:
Scott Bakerbd46a922015-02-03 15:02:17 -0800195 raise Exception("Use the List API for creating objects")
Scott Baker89a7b7c2014-10-07 00:10:17 -0700196
197 self.object = serializer.save(force_update=True)
198 self.post_save(self.object, created=False)
199 return Response(serializer.data, status=status.HTTP_200_OK)
200
201 def destroy(self, request, *args, **kwargs):
202 obj = self.get_object()
203 if obj.can_update(request.user):
Scott Baker15af5652015-02-02 14:33:08 -0800204 return super(PlanetStackRetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
Scott Baker89a7b7c2014-10-07 00:10:17 -0700205 else:
206 return Response(status=status.HTTP_400_BAD_REQUEST)
207
Scott Baker15af5652015-02-02 14:33:08 -0800208 def handle_exception(self, exc):
209 # REST API drops the string attached to Django's PermissionDenied
210 # exception, and replaces it with a generic "Permission Denied"
211 if isinstance(exc, DjangoPermissionDenied):
212 response=Response({'detail': str(exc)}, status=status.HTTP_403_FORBIDDEN)
213 response.exception=True
214 return response
215 else:
216 return super(PlanetStackRetrieveUpdateDestroyAPIView, self).handle_exception(exc)
217
218class PlanetStackListCreateAPIView(generics.ListCreateAPIView):
219 def handle_exception(self, exc):
220 # REST API drops the string attached to Django's PermissionDenied
221 # exception, and replaces it with a generic "Permission Denied"
222 if isinstance(exc, DjangoPermissionDenied):
223 response=Response({'detail': str(exc)}, status=status.HTTP_403_FORBIDDEN)
224 response.exception=True
225 return response
226 else:
227 return super(PlanetStackListCreateAPIView, self).handle_exception(exc)
Scott Baker89a7b7c2014-10-07 00:10:17 -0700228
Scott Bakerbd46a922015-02-03 15:02:17 -0800229 def create(self, request, *args, **kwargs):
230 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
231 if not (serializer.is_valid()):
232 response = {"error": "validation",
233 "specific_error": "not serializer.is_valid()",
234 "reasons": serializer.errors}
235 return Response(response, status=status.HTTP_400_BAD_REQUEST)
236
237 # now do XOS can_update permission checking
238
239 obj = serializer.object
240 obj.caller = request.user
241 if not obj.can_update(request.user):
242 response = {"error": "validation",
243 "specific_error": "failed can_update",
244 "reasons": []}
245 return Response(response, status=status.HTTP_400_BAD_REQUEST)
246
247 # stuff below is from generics.ListCreateAPIView
248
249 if (hasattr(self, "pre_save")):
250 # rest_framework 2.x
251 self.pre_save(serializer.object)
252 self.object = serializer.save(force_insert=True)
253 self.post_save(self.object, created=True)
254 else:
255 # rest_framework 3.x
256 self.perform_create(serializer)
257
258 headers = self.get_success_headers(serializer.data)
259 return Response(serializer.data, status=status.HTTP_201_CREATED,
260 headers=headers)
261
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500262# Based on core/views/*.py
263{% for object in generator.all %}
264
Scott Baker15af5652015-02-02 14:33:08 -0800265class {{ object.camel }}List(PlanetStackListCreateAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500266 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500267 serializer_class = {{ object.camel }}Serializer
Scott Baker9a270922014-07-03 18:01:30 -0700268 id_serializer_class = {{ object.camel }}IdSerializer
Scott Baker46b58542014-08-11 17:26:12 -0700269 filter_backends = (filters.DjangoFilterBackend,)
270 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 -0700271
272 def get_serializer_class(self):
Scott Bakercc184792015-01-30 15:42:02 -0800273 no_hyperlinks=False
274 if hasattr(self.request,"QUERY_PARAMS"):
275 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker9a270922014-07-03 18:01:30 -0700276 if (no_hyperlinks):
277 return self.id_serializer_class
278 else:
279 return self.serializer_class
280
Tony Mackeb8eb312014-02-04 20:50:39 -0500281 def get_queryset(self):
Scott Baker20101af2015-02-02 10:41:12 -0800282 if (not self.request.user.is_authenticated()):
Scott Baker4af864e2015-02-02 13:53:46 -0800283 raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
Tony Mack8f04ee32014-02-05 10:27:39 -0500284 return {{ object.camel }}.select_by_user(self.request.user)
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500285
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500286
Scott Baker89a7b7c2014-10-07 00:10:17 -0700287class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500288 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500289 serializer_class = {{ object.camel }}Serializer
Scott Baker46b58542014-08-11 17:26:12 -0700290 id_serializer_class = {{ object.camel }}IdSerializer
291
292 def get_serializer_class(self):
Scott Bakercc184792015-01-30 15:42:02 -0800293 no_hyperlinks=False
294 if hasattr(self.request,"QUERY_PARAMS"):
295 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker46b58542014-08-11 17:26:12 -0700296 if (no_hyperlinks):
297 return self.id_serializer_class
298 else:
299 return self.serializer_class
Scott Bakercc184792015-01-30 15:42:02 -0800300
Tony Mackeb8eb312014-02-04 20:50:39 -0500301 def get_queryset(self):
Scott Baker20101af2015-02-02 10:41:12 -0800302 if (not self.request.user.is_authenticated()):
Scott Baker4af864e2015-02-02 13:53:46 -0800303 raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
Tony Mack9525eba2014-02-05 10:57:21 -0500304 return {{ object.camel }}.select_by_user(self.request.user)
305
Scott Baker89a7b7c2014-10-07 00:10:17 -0700306 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Tony Mack9525eba2014-02-05 10:57:21 -0500307
Scott Baker89a7b7c2014-10-07 00:10:17 -0700308 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500309
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500310{% endfor %}