blob: 1f2c3a5d7d157485d45cdfbf6c8a53fe60d22c55 [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
Scott Bakerb9973aa2014-12-23 11:13:52 -080013if hasattr(serializers, "ReadOnlyField"):
14 # rest_framework 3.x
15 IdField = serializers.ReadOnlyField
16else:
17 # rest_framework 2.x
18 IdField = serializers.Field
19
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050020"""
Sapan Bhatia970beb52014-10-08 11:34:23 -040021 Schema of the generator object:
22 all: Set of all Model objects
23 all_if(regex): Set of Model objects that match regex
Scott Bakerc480fb22014-11-12 01:12:28 -080024
Sapan Bhatia970beb52014-10-08 11:34:23 -040025 Model object:
26 plural: English plural of object name
27 camel: CamelCase version of object name
28 refs: list of references to other Model objects
29 props: list of properties minus refs
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050030
Sapan Bhatia970beb52014-10-08 11:34:23 -040031 TODO: Deal with subnets
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050032"""
33
Scott Baker8ffd7d72014-11-10 15:58:58 -080034def get_REST_patterns():
35 return patterns('',
36 url(r'^plstackapi/$', api_root),
37 {% for object in generator.all %}
38 url(r'plstackapi/{{ object.rest_name }}/$', {{ object.camel }}List.as_view(), name='{{ object.singular }}-list'),
39 url(r'plstackapi/{{ object.rest_name }}/(?P<pk>[a-zA-Z0-9\-]+)/$', {{ object.camel }}Detail.as_view(), name ='{{ object.singular }}-detail'),
40 {% endfor %}
41 )
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050042
43@api_view(['GET'])
44def api_root(request, format=None):
45 return Response({
Sapan Bhatia970beb52014-10-08 11:34:23 -040046 {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
47 {% endfor %}
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -050048 })
49
50# Based on serializers.py
51
Scott Bakercac09742014-12-17 18:22:33 -080052class XOSModelSerializer(serializers.ModelSerializer):
53 def save_object(self, obj, **kwargs):
54
55 """ rest_framework can't deal with ManyToMany relations that have a
56 through table. In plstackapi, most of the through tables we have
57 use defaults or blank fields, so there's no reason why we shouldn't
58 be able to save these objects.
59
60 So, let's strip out these m2m relations, and deal with them ourself.
61 """
62 obj._complex_m2m_data={};
63 if getattr(obj, '_m2m_data', None):
64 for relatedObject in obj._meta.get_all_related_many_to_many_objects():
65 if (relatedObject.field.rel.through._meta.auto_created):
66 # These are non-trough ManyToMany relations and
67 # can be updated just fine
68 continue
69 fieldName = relatedObject.get_accessor_name()
70 if fieldName in obj._m2m_data.keys():
71 obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
72 del obj._m2m_data[fieldName]
73
74 serializers.ModelSerializer.save_object(self, obj, **kwargs);
75
76 for (accessor, stuff) in obj._complex_m2m_data.items():
77 (relatedObject, data) = stuff
78 through = relatedObject.field.rel.through
79 local_fieldName = relatedObject.field.m2m_reverse_field_name()
Scott Baker148f5e12014-12-18 12:59:04 -080080 remote_fieldName = relatedObject.field.m2m_field_name()
81
82 # get the current set of existing relations
Scott Bakercac09742014-12-17 18:22:33 -080083 existing = through.objects.filter(**{local_fieldName: obj});
Scott Bakercac09742014-12-17 18:22:33 -080084
Scott Baker148f5e12014-12-18 12:59:04 -080085 data_ids = [item.id for item in data]
86 existing_ids = [getattr(item,remote_fieldName).id for item in existing]
Scott Bakercac09742014-12-17 18:22:33 -080087
Scott Baker148f5e12014-12-18 12:59:04 -080088 #print "data_ids", data_ids
89 #print "existing_ids", existing_ids
90
91 # remove relations that are in 'existing' but not in 'data'
92 for item in list(existing):
93 if (getattr(item,remote_fieldName).id not in data_ids):
94 print "delete", getattr(item,remote_fieldName)
95 item.delete() #(purge=True)
96
97 # add relations that are in 'data' but not in 'existing'
98 for item in data:
99 if (item.id not in existing_ids):
100 #print "add", item
101 newModel = through(**{local_fieldName: obj, remote_fieldName: item})
102 newModel.save()
Scott Bakercac09742014-12-17 18:22:33 -0800103
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500104{% for object in generator.all %}
105
106class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
Scott Bakerb9973aa2014-12-23 11:13:52 -0800107 id = IdField()
Sapan Bhatia970beb52014-10-08 11:34:23 -0400108 {% for ref in object.refs %}
109 {% if ref.multi %}
110 {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
111 {% else %}
112 {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
113 {% endif %}
114 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800115 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800116 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800117 def getHumanReadableName(self, obj):
118 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800119 def getValidators(self, obj):
120 try:
121 return obj.getValidators()
122 except:
123 return None
Sapan Bhatia970beb52014-10-08 11:34:23 -0400124 class Meta:
125 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800126 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 -0700127
Scott Bakercac09742014-12-17 18:22:33 -0800128class {{ object.camel }}IdSerializer(XOSModelSerializer):
Scott Bakerb9973aa2014-12-23 11:13:52 -0800129 id = IdField()
Sapan Bhatia970beb52014-10-08 11:34:23 -0400130 {% for ref in object.refs %}
131 {% if ref.multi %}
Scott Baker79348852014-12-23 10:43:03 -0800132 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True, queryset = {{ ref.camel }}.objects.all())
Sapan Bhatia970beb52014-10-08 11:34:23 -0400133 {% else %}
Scott Baker79348852014-12-23 10:43:03 -0800134 {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
Sapan Bhatia970beb52014-10-08 11:34:23 -0400135 {% endif %}
136 {% endfor %}
Scott Baker196ffef2014-11-21 11:32:24 -0800137 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
Scott Baker351148c2014-11-25 00:53:25 -0800138 validators = serializers.SerializerMethodField("getValidators")
Scott Baker196ffef2014-11-21 11:32:24 -0800139 def getHumanReadableName(self, obj):
140 return str(obj)
Scott Baker351148c2014-11-25 00:53:25 -0800141 def getValidators(self, obj):
142 try:
143 return obj.getValidators()
144 except:
145 return None
Scott Bakercac09742014-12-17 18:22:33 -0800146 class Meta:
Sapan Bhatia970beb52014-10-08 11:34:23 -0400147 model = {{ object.camel }}
Scott Baker351148c2014-11-25 00:53:25 -0800148 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 -0700149
150
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500151{% endfor %}
152
153serializerLookUp = {
154{% for object in generator.all %}
155 {{ object.camel }}: {{ object.camel }}Serializer,
156{% endfor %}
157 None: None,
158 }
159
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700160class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
161
162 # To handle fine-grained field permissions, we have to check can_update
163 # the object has been updated but before it has been saved.
164
165 def update(self, request, *args, **kwargs):
166 partial = kwargs.pop('partial', False)
167 self.object = self.get_object_or_none()
168
169 serializer = self.get_serializer(self.object, data=request.DATA,
170 files=request.FILES, partial=partial)
171
172 if not serializer.is_valid():
Scott Baker351148c2014-11-25 00:53:25 -0800173 response = {"error": "validation",
174 "specific_error": "not serializer.is_valid()",
175 "reasons": serializer.errors}
176 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700177
178 try:
179 self.pre_save(serializer.object)
180 except ValidationError as err:
181 # full_clean on model instance may be called in pre_save,
182 # so we have to handle eventual errors.
Scott Baker351148c2014-11-25 00:53:25 -0800183 response = {"error": "validation",
184 "specific_error": "ValidationError in pre_save",
185 "reasons": err.message_dict}
186 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700187
188 if serializer.object is not None:
189 if not serializer.object.can_update(request.user):
190 return Response(status=status.HTTP_400_BAD_REQUEST)
191
192 if self.object is None:
193 self.object = serializer.save(force_insert=True)
194 self.post_save(self.object, created=True)
195 return Response(serializer.data, status=status.HTTP_201_CREATED)
196
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 Bakerde12f092014-10-09 11:09:15 -0700204 return super(generics.RetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700205 else:
206 return Response(status=status.HTTP_400_BAD_REQUEST)
207
208
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500209# Based on core/views/*.py
210{% for object in generator.all %}
211
212class {{ object.camel }}List(generics.ListCreateAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500213 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500214 serializer_class = {{ object.camel }}Serializer
Scott Baker9a270922014-07-03 18:01:30 -0700215 id_serializer_class = {{ object.camel }}IdSerializer
Scott Baker46b58542014-08-11 17:26:12 -0700216 filter_backends = (filters.DjangoFilterBackend,)
217 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 -0700218
219 def get_serializer_class(self):
Scott Baker71621062015-01-30 15:42:02 -0800220 no_hyperlinks=False
221 if hasattr(self.request,"QUERY_PARAMS"):
222 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker9a270922014-07-03 18:01:30 -0700223 if (no_hyperlinks):
224 return self.id_serializer_class
225 else:
226 return self.serializer_class
227
Tony Mackeb8eb312014-02-04 20:50:39 -0500228 def get_queryset(self):
Tony Mack8f04ee32014-02-05 10:27:39 -0500229 return {{ object.camel }}.select_by_user(self.request.user)
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500230
Tony Mack9525eba2014-02-05 10:57:21 -0500231 def create(self, request, *args, **kwargs):
Scott Bakerc480fb22014-11-12 01:12:28 -0800232 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
233 if not (serializer.is_valid()):
Scott Baker351148c2014-11-25 00:53:25 -0800234 response = {"error": "validation",
235 "specific_error": "not serializer.is_valid()",
236 "reasons": serializer.errors}
237 return Response(response, status=status.HTTP_400_BAD_REQUEST)
Scott Bakerc480fb22014-11-12 01:12:28 -0800238 obj = serializer.object
Scott Bakerde12f092014-10-09 11:09:15 -0700239 obj.caller = request.user
240 if obj.can_update(request.user):
241 return super({{ object.camel }}List, self).create(request, *args, **kwargs)
242 else:
243 raise Exception("failed obj.can_update")
244
Sapan Bhatia970beb52014-10-08 11:34:23 -0400245 ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
246 if (ret.status_code%100 != 200):
247 raise Exception(ret.data)
Scott Bakerde12f092014-10-09 11:09:15 -0700248
249 return ret
250
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500251
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700252class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500253 queryset = {{ object.camel }}.objects.select_related().all()
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500254 serializer_class = {{ object.camel }}Serializer
Scott Baker46b58542014-08-11 17:26:12 -0700255 id_serializer_class = {{ object.camel }}IdSerializer
256
257 def get_serializer_class(self):
Scott Baker71621062015-01-30 15:42:02 -0800258 no_hyperlinks=False
259 if hasattr(self.request,"QUERY_PARAMS"):
260 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
Scott Baker46b58542014-08-11 17:26:12 -0700261 if (no_hyperlinks):
262 return self.id_serializer_class
263 else:
264 return self.serializer_class
Scott Baker71621062015-01-30 15:42:02 -0800265
Tony Mackeb8eb312014-02-04 20:50:39 -0500266 def get_queryset(self):
Tony Mack9525eba2014-02-05 10:57:21 -0500267 return {{ object.camel }}.select_by_user(self.request.user)
268
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700269 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Tony Mack9525eba2014-02-05 10:57:21 -0500270
Scott Bakerdcb9b5d2014-10-07 00:10:17 -0700271 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500272
Sapan Bhatia3bbf5ed2014-01-13 13:29:12 -0500273{% endfor %}