blob: 7051c13687e605d8ce94036b054e80f9d18be9e9 [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
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050013
Scott Bakerb9973aa2014-12-23 11:13:52 -080014if hasattr(serializers, "ReadOnlyField"):
15 # rest_framework 3.x
16 IdField = serializers.ReadOnlyField
17else:
18 # rest_framework 2.x
19 IdField = serializers.Field
20
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050021"""
Sapan Bhatia970beb52014-10-08 11:34:23 -040022 Schema of the generator object:
23 all: Set of all Model objects
24 all_if(regex): Set of Model objects that match regex
Scott Bakerc480fb22014-11-12 01:12:28 -080025
Sapan Bhatia970beb52014-10-08 11:34:23 -040026 Model object:
27 plural: English plural of object name
28 camel: CamelCase version of object name
29 refs: list of references to other Model objects
30 props: list of properties minus refs
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050031
Sapan Bhatia970beb52014-10-08 11:34:23 -040032 TODO: Deal with subnets
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050033"""
34
Scott Baker8ffd7d72014-11-10 15:58:58 -080035def get_REST_patterns():
36 return patterns('',
37 url(r'^plstackapi/$', api_root),
38 {% for object in generator.all %}
39 url(r'plstackapi/{{ object.rest_name }}/$', {{ object.camel }}List.as_view(), name='{{ object.singular }}-list'),
40 url(r'plstackapi/{{ object.rest_name }}/(?P<pk>[a-zA-Z0-9\-]+)/$', {{ object.camel }}Detail.as_view(), name ='{{ object.singular }}-detail'),
41 {% endfor %}
42 )
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050043
44@api_view(['GET'])
45def api_root(request, format=None):
46 return Response({
Sapan Bhatia970beb52014-10-08 11:34:23 -040047 {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
48 {% endfor %}
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050049 })
50
51# Based on serializers.py
52
Scott Bakercac09742014-12-17 18:22:33 -080053class XOSModelSerializer(serializers.ModelSerializer):
54 def save_object(self, obj, **kwargs):
55
56 """ rest_framework can't deal with ManyToMany relations that have a
57 through table. In plstackapi, most of the through tables we have
58 use defaults or blank fields, so there's no reason why we shouldn't
59 be able to save these objects.
60
61 So, let's strip out these m2m relations, and deal with them ourself.
62 """
63 obj._complex_m2m_data={};
64 if getattr(obj, '_m2m_data', None):
65 for relatedObject in obj._meta.get_all_related_many_to_many_objects():
66 if (relatedObject.field.rel.through._meta.auto_created):
67 # These are non-trough ManyToMany relations and
68 # can be updated just fine
69 continue
70 fieldName = relatedObject.get_accessor_name()
71 if fieldName in obj._m2m_data.keys():
72 obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
73 del obj._m2m_data[fieldName]
74
75 serializers.ModelSerializer.save_object(self, obj, **kwargs);
76
77 for (accessor, stuff) in obj._complex_m2m_data.items():
78 (relatedObject, data) = stuff
79 through = relatedObject.field.rel.through
80 local_fieldName = relatedObject.field.m2m_reverse_field_name()
Scott Baker148f5e12014-12-18 12:59:04 -080081 remote_fieldName = relatedObject.field.m2m_field_name()
82
83 # get the current set of existing relations
Scott Bakercac09742014-12-17 18:22:33 -080084 existing = through.objects.filter(**{local_fieldName: obj});
Scott Bakercac09742014-12-17 18:22:33 -080085
Scott Baker148f5e12014-12-18 12:59:04 -080086 data_ids = [item.id for item in data]
87 existing_ids = [getattr(item,remote_fieldName).id for item in existing]
Scott Bakercac09742014-12-17 18:22:33 -080088
Scott Baker148f5e12014-12-18 12:59:04 -080089 #print "data_ids", data_ids
90 #print "existing_ids", existing_ids
91
92 # remove relations that are in 'existing' but not in 'data'
93 for item in list(existing):
94 if (getattr(item,remote_fieldName).id not in data_ids):
95 print "delete", getattr(item,remote_fieldName)
96 item.delete() #(purge=True)
97
98 # add relations that are in 'data' but not in 'existing'
99 for item in data:
100 if (item.id not in existing_ids):
101 #print "add", item
102 newModel = through(**{local_fieldName: obj, remote_fieldName: item})
103 newModel.save()
Scott Bakercac09742014-12-17 18:22:33 -0800104
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500105{% for object in generator.all %}
106
107class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
Scott Bakerb9973aa2014-12-23 11:13:52 -0800108 id = IdField()
Sapan Bhatia970beb52014-10-08 11:34:23 -0400109 {% for ref in object.refs %}
110 {% if ref.multi %}
111 {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
112 {% else %}
113 {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
114 {% endif %}
115 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800116 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800117 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800118 def getHumanReadableName(self, obj):
119 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800120 def getValidators(self, obj):
121 try:
122 return obj.getValidators()
123 except:
124 return None
Sapan Bhatia970beb52014-10-08 11:34:23 -0400125 class Meta:
126 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800127 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 -0700128
Scott Bakercac09742014-12-17 18:22:33 -0800129class {{ object.camel }}IdSerializer(XOSModelSerializer):
Scott Bakerb9973aa2014-12-23 11:13:52 -0800130 id = IdField()
Sapan Bhatia970beb52014-10-08 11:34:23 -0400131 {% for ref in object.refs %}
132 {% if ref.multi %}
Scott Baker79348852014-12-23 10:43:03 -0800133 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True, queryset = {{ ref.camel }}.objects.all())
Sapan Bhatia970beb52014-10-08 11:34:23 -0400134 {% else %}
Scott Baker79348852014-12-23 10:43:03 -0800135 {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
Sapan Bhatia970beb52014-10-08 11:34:23 -0400136 {% endif %}
137 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800138 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800139 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800140 def getHumanReadableName(self, obj):
141 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800142 def getValidators(self, obj):
143 try:
144 return obj.getValidators()
145 except:
146 return None
Scott Bakercac09742014-12-17 18:22:33 -0800147 class Meta:
Sapan Bhatia970beb52014-10-08 11:34:23 -0400148 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800149 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 -0700150
151
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500152{% endfor %}
153
154serializerLookUp = {
155{% for object in generator.all %}
156 {{ object.camel }}: {{ object.camel }}Serializer,
157{% endfor %}
158 None: None,
159 }
160
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700161class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
162
163 # To handle fine-grained field permissions, we have to check can_update
164 # the object has been updated but before it has been saved.
165
166 def update(self, request, *args, **kwargs):
167 partial = kwargs.pop('partial', False)
168 self.object = self.get_object_or_none()
169
170 serializer = self.get_serializer(self.object, data=request.DATA,
171 files=request.FILES, partial=partial)
172
173 if not serializer.is_valid():
Scott Baker351148c2014-11-25 00:53:25 -0800174 response = {"error": "validation",
175 "specific_error": "not serializer.is_valid()",
176 "reasons": serializer.errors}
177 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700178
179 try:
180 self.pre_save(serializer.object)
181 except ValidationError as err:
182 # full_clean on model instance may be called in pre_save,
183 # so we have to handle eventual errors.
Scott Baker351148c2014-11-25 00:53:25 -0800184 response = {"error": "validation",
185 "specific_error": "ValidationError in pre_save",
186 "reasons": err.message_dict}
187 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700188
189 if serializer.object is not None:
190 if not serializer.object.can_update(request.user):
191 return Response(status=status.HTTP_400_BAD_REQUEST)
192
193 if self.object is None:
194 self.object = serializer.save(force_insert=True)
195 self.post_save(self.object, created=True)
196 return Response(serializer.data, status=status.HTTP_201_CREATED)
197
198 self.object = serializer.save(force_update=True)
199 self.post_save(self.object, created=False)
200 return Response(serializer.data, status=status.HTTP_200_OK)
201
202 def destroy(self, request, *args, **kwargs):
203 obj = self.get_object()
204 if obj.can_update(request.user):
Scott Bakerde12f092014-10-09 11:09:15 -0700205 return super(generics.RetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700206 else:
207 return Response(status=status.HTTP_400_BAD_REQUEST)
208
209
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500210# Based on core/views/*.py
211{% for object in generator.all %}
212
213class {{ object.camel }}List(generics.ListCreateAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500214 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500215 serializer_class = {{ object.camel }}Serializer
Scott Baker9a270922014-07-03 18:01:30 -0700216 id_serializer_class = {{ object.camel }}IdSerializer
Scott Baker46b58542014-08-11 17:26:12 -0700217 filter_backends = (filters.DjangoFilterBackend,)
218 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 -0700219
220 def get_serializer_class(self):
Scott Baker71621062015-01-30 15:42:02 -0800221 no_hyperlinks=False
222 if hasattr(self.request,"QUERY_PARAMS"):
223 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker9a270922014-07-03 18:01:30 -0700224 if (no_hyperlinks):
225 return self.id_serializer_class
226 else:
227 return self.serializer_class
228
Tony Mackeb8eb312014-02-04 20:50:39 -0500229 def get_queryset(self):
Scott Baker960431e2015-02-02 10:41:12 -0800230 if (not self.request.user.is_authenticated()):
Scott Bakercb60f8a2015-02-02 13:53:46 -0800231 raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
Tony Mack8f04ee32014-02-05 10:27:39 -0500232 return {{ object.camel }}.select_by_user(self.request.user)
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500233
Tony Mack9525eba2014-02-05 10:57:21 -0500234 def create(self, request, *args, **kwargs):
Scott Bakerc480fb22014-11-12 01:12:28 -0800235 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
236 if not (serializer.is_valid()):
Scott Baker351148c2014-11-25 00:53:25 -0800237 response = {"error": "validation",
238 "specific_error": "not serializer.is_valid()",
239 "reasons": serializer.errors}
240 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerc480fb22014-11-12 01:12:28 -0800241 obj = serializer.object
Scott Bakerde12f092014-10-09 11:09:15 -0700242 obj.caller = request.user
243 if obj.can_update(request.user):
244 return super({{ object.camel }}List, self).create(request, *args, **kwargs)
245 else:
246 raise Exception("failed obj.can_update")
247
Sapan Bhatia970beb52014-10-08 11:34:23 -0400248 ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
249 if (ret.status_code%100 != 200):
250 raise Exception(ret.data)
Scott Bakerde12f092014-10-09 11:09:15 -0700251
252 return ret
253
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500254
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700255class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500256 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500257 serializer_class = {{ object.camel }}Serializer
Scott Baker46b58542014-08-11 17:26:12 -0700258 id_serializer_class = {{ object.camel }}IdSerializer
259
260 def get_serializer_class(self):
Scott Baker71621062015-01-30 15:42:02 -0800261 no_hyperlinks=False
262 if hasattr(self.request,"QUERY_PARAMS"):
263 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker46b58542014-08-11 17:26:12 -0700264 if (no_hyperlinks):
265 return self.id_serializer_class
266 else:
267 return self.serializer_class
Scott Baker71621062015-01-30 15:42:02 -0800268
Tony Mackeb8eb312014-02-04 20:50:39 -0500269 def get_queryset(self):
Scott Baker960431e2015-02-02 10:41:12 -0800270 if (not self.request.user.is_authenticated()):
Scott Bakercb60f8a2015-02-02 13:53:46 -0800271 raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
Tony Mack9525eba2014-02-05 10:57:21 -0500272 return {{ object.camel }}.select_by_user(self.request.user)
273
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700274 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Tony Mack9525eba2014-02-05 10:57:21 -0500275
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700276 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500277
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500278{% endfor %}