blob: b1b0251583acd7aa850bc159c9137c9edf317b52 [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
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050012
13"""
Sapan Bhatia970beb52014-10-08 11:34:23 -040014 Schema of the generator object:
15 all: Set of all Model objects
16 all_if(regex): Set of Model objects that match regex
Scott Bakerc480fb22014-11-12 01:12:28 -080017
Sapan Bhatia970beb52014-10-08 11:34:23 -040018 Model object:
19 plural: English plural of object name
20 camel: CamelCase version of object name
21 refs: list of references to other Model objects
22 props: list of properties minus refs
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050023
Sapan Bhatia970beb52014-10-08 11:34:23 -040024 TODO: Deal with subnets
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050025"""
26
Scott Baker8ffd7d72014-11-10 15:58:58 -080027def get_REST_patterns():
28 return patterns('',
29 url(r'^plstackapi/$', api_root),
30 {% for object in generator.all %}
31 url(r'plstackapi/{{ object.rest_name }}/$', {{ object.camel }}List.as_view(), name='{{ object.singular }}-list'),
32 url(r'plstackapi/{{ object.rest_name }}/(?P<pk>[a-zA-Z0-9\-]+)/$', {{ object.camel }}Detail.as_view(), name ='{{ object.singular }}-detail'),
33 {% endfor %}
34 )
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050035
36@api_view(['GET'])
37def api_root(request, format=None):
38 return Response({
Sapan Bhatia970beb52014-10-08 11:34:23 -040039 {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
40 {% endfor %}
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050041 })
42
43# Based on serializers.py
44
Scott Bakercac09742014-12-17 18:22:33 -080045class XOSModelSerializer(serializers.ModelSerializer):
46 def save_object(self, obj, **kwargs):
47
48 """ rest_framework can't deal with ManyToMany relations that have a
49 through table. In plstackapi, most of the through tables we have
50 use defaults or blank fields, so there's no reason why we shouldn't
51 be able to save these objects.
52
53 So, let's strip out these m2m relations, and deal with them ourself.
54 """
55 obj._complex_m2m_data={};
56 if getattr(obj, '_m2m_data', None):
57 for relatedObject in obj._meta.get_all_related_many_to_many_objects():
58 if (relatedObject.field.rel.through._meta.auto_created):
59 # These are non-trough ManyToMany relations and
60 # can be updated just fine
61 continue
62 fieldName = relatedObject.get_accessor_name()
63 if fieldName in obj._m2m_data.keys():
64 obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
65 del obj._m2m_data[fieldName]
66
67 serializers.ModelSerializer.save_object(self, obj, **kwargs);
68
69 for (accessor, stuff) in obj._complex_m2m_data.items():
70 (relatedObject, data) = stuff
71 through = relatedObject.field.rel.through
72 local_fieldName = relatedObject.field.m2m_reverse_field_name()
Scott Baker148f5e12014-12-18 12:59:04 -080073 remote_fieldName = relatedObject.field.m2m_field_name()
74
75 # get the current set of existing relations
Scott Bakercac09742014-12-17 18:22:33 -080076 existing = through.objects.filter(**{local_fieldName: obj});
Scott Bakercac09742014-12-17 18:22:33 -080077
Scott Baker148f5e12014-12-18 12:59:04 -080078 data_ids = [item.id for item in data]
79 existing_ids = [getattr(item,remote_fieldName).id for item in existing]
Scott Bakercac09742014-12-17 18:22:33 -080080
Scott Baker148f5e12014-12-18 12:59:04 -080081 #print "data_ids", data_ids
82 #print "existing_ids", existing_ids
83
84 # remove relations that are in 'existing' but not in 'data'
85 for item in list(existing):
86 if (getattr(item,remote_fieldName).id not in data_ids):
87 print "delete", getattr(item,remote_fieldName)
88 item.delete() #(purge=True)
89
90 # add relations that are in 'data' but not in 'existing'
91 for item in data:
92 if (item.id not in existing_ids):
93 #print "add", item
94 newModel = through(**{local_fieldName: obj, remote_fieldName: item})
95 newModel.save()
Scott Bakercac09742014-12-17 18:22:33 -080096
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050097{% for object in generator.all %}
98
99class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
Sapan Bhatia970beb52014-10-08 11:34:23 -0400100 id = serializers.Field()
101 {% for ref in object.refs %}
102 {% if ref.multi %}
103 {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
104 {% else %}
105 {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
106 {% endif %}
107 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800108 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800109 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800110 def getHumanReadableName(self, obj):
111 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800112 def getValidators(self, obj):
113 try:
114 return obj.getValidators()
115 except:
116 return None
Sapan Bhatia970beb52014-10-08 11:34:23 -0400117 class Meta:
118 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800119 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 -0700120
Scott Bakercac09742014-12-17 18:22:33 -0800121class {{ object.camel }}IdSerializer(XOSModelSerializer):
Sapan Bhatia970beb52014-10-08 11:34:23 -0400122 id = serializers.Field()
123 {% for ref in object.refs %}
124 {% if ref.multi %}
Scott Bakercac09742014-12-17 18:22:33 -0800125 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True) #, read_only=True) #, view_name='{{ ref }}-detail')
Sapan Bhatia970beb52014-10-08 11:34:23 -0400126 {% else %}
Scott Bakercac09742014-12-17 18:22:33 -0800127 {{ ref }} = serializers.PrimaryKeyRelatedField() # read_only=True) #, view_name='{{ ref }}-detail')
Sapan Bhatia970beb52014-10-08 11:34:23 -0400128 {% endif %}
129 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800130 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800131 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800132 def getHumanReadableName(self, obj):
133 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800134 def getValidators(self, obj):
135 try:
136 return obj.getValidators()
137 except:
138 return None
Scott Bakercac09742014-12-17 18:22:33 -0800139 class Meta:
Sapan Bhatia970beb52014-10-08 11:34:23 -0400140 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800141 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 -0700142
143
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500144{% endfor %}
145
146serializerLookUp = {
147{% for object in generator.all %}
148 {{ object.camel }}: {{ object.camel }}Serializer,
149{% endfor %}
150 None: None,
151 }
152
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700153class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
154
155 # To handle fine-grained field permissions, we have to check can_update
156 # the object has been updated but before it has been saved.
157
158 def update(self, request, *args, **kwargs):
159 partial = kwargs.pop('partial', False)
160 self.object = self.get_object_or_none()
161
162 serializer = self.get_serializer(self.object, data=request.DATA,
163 files=request.FILES, partial=partial)
164
165 if not serializer.is_valid():
Scott Baker351148c2014-11-25 00:53:25 -0800166 response = {"error": "validation",
167 "specific_error": "not serializer.is_valid()",
168 "reasons": serializer.errors}
169 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700170
171 try:
172 self.pre_save(serializer.object)
173 except ValidationError as err:
174 # full_clean on model instance may be called in pre_save,
175 # so we have to handle eventual errors.
Scott Baker351148c2014-11-25 00:53:25 -0800176 response = {"error": "validation",
177 "specific_error": "ValidationError in pre_save",
178 "reasons": err.message_dict}
179 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700180
181 if serializer.object is not None:
182 if not serializer.object.can_update(request.user):
183 return Response(status=status.HTTP_400_BAD_REQUEST)
184
185 if self.object is None:
186 self.object = serializer.save(force_insert=True)
187 self.post_save(self.object, created=True)
188 return Response(serializer.data, status=status.HTTP_201_CREATED)
189
190 self.object = serializer.save(force_update=True)
191 self.post_save(self.object, created=False)
192 return Response(serializer.data, status=status.HTTP_200_OK)
193
194 def destroy(self, request, *args, **kwargs):
195 obj = self.get_object()
196 if obj.can_update(request.user):
Scott Bakerde12f092014-10-09 11:09:15 -0700197 return super(generics.RetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700198 else:
199 return Response(status=status.HTTP_400_BAD_REQUEST)
200
201
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500202# Based on core/views/*.py
203{% for object in generator.all %}
204
205class {{ object.camel }}List(generics.ListCreateAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500206 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500207 serializer_class = {{ object.camel }}Serializer
Scott Baker9a270922014-07-03 18:01:30 -0700208 id_serializer_class = {{ object.camel }}IdSerializer
Scott Baker46b58542014-08-11 17:26:12 -0700209 filter_backends = (filters.DjangoFilterBackend,)
210 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 -0700211
212 def get_serializer_class(self):
213 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
214 if (no_hyperlinks):
215 return self.id_serializer_class
216 else:
217 return self.serializer_class
218
Tony Mackeb8eb312014-02-04 20:50:39 -0500219 def get_queryset(self):
Tony Mack8f04ee32014-02-05 10:27:39 -0500220 return {{ object.camel }}.select_by_user(self.request.user)
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500221
Tony Mack9525eba2014-02-05 10:57:21 -0500222 def create(self, request, *args, **kwargs):
Scott Bakerc480fb22014-11-12 01:12:28 -0800223 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
224 if not (serializer.is_valid()):
Scott Baker351148c2014-11-25 00:53:25 -0800225 response = {"error": "validation",
226 "specific_error": "not serializer.is_valid()",
227 "reasons": serializer.errors}
228 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerc480fb22014-11-12 01:12:28 -0800229 obj = serializer.object
Scott Bakerde12f092014-10-09 11:09:15 -0700230 obj.caller = request.user
231 if obj.can_update(request.user):
232 return super({{ object.camel }}List, self).create(request, *args, **kwargs)
233 else:
234 raise Exception("failed obj.can_update")
235
Sapan Bhatia970beb52014-10-08 11:34:23 -0400236 ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
237 if (ret.status_code%100 != 200):
238 raise Exception(ret.data)
Scott Bakerde12f092014-10-09 11:09:15 -0700239
240 return ret
241
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500242
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700243class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500244 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500245 serializer_class = {{ object.camel }}Serializer
Scott Baker46b58542014-08-11 17:26:12 -0700246 id_serializer_class = {{ object.camel }}IdSerializer
247
248 def get_serializer_class(self):
249 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
250 if (no_hyperlinks):
251 return self.id_serializer_class
252 else:
253 return self.serializer_class
Tony Mackeb8eb312014-02-04 20:50:39 -0500254
255 def get_queryset(self):
Tony Mack9525eba2014-02-05 10:57:21 -0500256 return {{ object.camel }}.select_by_user(self.request.user)
257
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700258 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Tony Mack9525eba2014-02-05 10:57:21 -0500259
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700260 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500261
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500262{% endfor %}