1 from rest_framework.decorators import api_view
2 from rest_framework.response import Response
3 from rest_framework.reverse import reverse
4 from rest_framework import serializers
5 from rest_framework import generics
6 from rest_framework import status
7 from rest_framework.generics import GenericAPIView
8 from core.models import *
9 from django.forms import widgets
10 from rest_framework import filters
11 from django.conf.urls import patterns, url
13 if hasattr(serializers, "ReadOnlyField"):
15 IdField = serializers.ReadOnlyField
18 IdField = serializers.Field
21 Schema of the generator object:
22 all: Set of all Model objects
23 all_if(regex): Set of Model objects that match regex
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
31 TODO: Deal with subnets
34 def get_REST_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'),
44 def api_root(request, format=None):
46 {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
50 # Based on serializers.py
52 class XOSModelSerializer(serializers.ModelSerializer):
53 def save_object(self, obj, **kwargs):
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.
60 So, let's strip out these m2m relations, and deal with them ourself.
62 obj._complex_m2m_data={};
\r
63 if getattr(obj, '_m2m_data', None):
\r
64 for relatedObject in obj._meta.get_all_related_many_to_many_objects():
\r
65 if (relatedObject.field.rel.through._meta.auto_created):
\r
66 # These are non-trough ManyToMany relations and
\r
67 # can be updated just fine
\r
69 fieldName = relatedObject.get_accessor_name()
\r
70 if fieldName in obj._m2m_data.keys():
\r
71 obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
\r
72 del obj._m2m_data[fieldName]
\r
74 serializers.ModelSerializer.save_object(self, obj, **kwargs);
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()
80 remote_fieldName = relatedObject.field.m2m_field_name()
82 # get the current set of existing relations
83 existing = through.objects.filter(**{local_fieldName: obj});
85 data_ids = [item.id for item in data]
86 existing_ids = [getattr(item,remote_fieldName).id for item in existing]
88 #print "data_ids", data_ids
89 #print "existing_ids", existing_ids
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)
97 # add relations that are in 'data' but not in 'existing'
99 if (item.id not in existing_ids):
101 newModel = through(**{local_fieldName: obj, remote_fieldName: item})
104 {% for object in generator.all %}
106 class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
108 {% for ref in object.refs %}
110 {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
112 {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
115 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
116 validators = serializers.SerializerMethodField("getValidators")
117 def getHumanReadableName(self, obj):
\r
119 def getValidators(self, obj):
121 return obj.getValidators()
125 model = {{ object.camel }}
126 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
128 class {{ object.camel }}IdSerializer(XOSModelSerializer):
130 {% for ref in object.refs %}
132 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True, queryset = {{ ref.camel }}.objects.all())
134 {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
137 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
138 validators = serializers.SerializerMethodField("getValidators")
139 def getHumanReadableName(self, obj):
\r
141 def getValidators(self, obj):
\r
143 return obj.getValidators()
147 model = {{ object.camel }}
148 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
154 {% for object in generator.all %}
155 {{ object.camel }}: {{ object.camel }}Serializer,
160 class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
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.
165 def update(self, request, *args, **kwargs):
\r
166 partial = kwargs.pop('partial', False)
\r
167 self.object = self.get_object_or_none()
\r
169 serializer = self.get_serializer(self.object, data=request.DATA,
\r
170 files=request.FILES, partial=partial)
\r
172 if not serializer.is_valid():
\r
173 response = {"error": "validation",
\r
174 "specific_error": "not serializer.is_valid()",
\r
175 "reasons": serializer.errors}
\r
176 return Response(response, status=status.HTTP_400_BAD_REQUEST)
\r
179 self.pre_save(serializer.object)
\r
180 except ValidationError as err:
\r
181 # full_clean on model instance may be called in pre_save,
\r
182 # so we have to handle eventual errors.
\r
183 response = {"error": "validation",
\r
184 "specific_error": "ValidationError in pre_save",
\r
185 "reasons": err.message_dict}
\r
186 return Response(response, status=status.HTTP_400_BAD_REQUEST)
\r
188 if serializer.object is not None:
\r
189 if not serializer.object.can_update(request.user):
\r
190 return Response(status=status.HTTP_400_BAD_REQUEST)
\r
192 if self.object is None:
\r
193 self.object = serializer.save(force_insert=True)
\r
194 self.post_save(self.object, created=True)
\r
195 return Response(serializer.data, status=status.HTTP_201_CREATED)
\r
197 self.object = serializer.save(force_update=True)
\r
198 self.post_save(self.object, created=False)
\r
199 return Response(serializer.data, status=status.HTTP_200_OK)
201 def destroy(self, request, *args, **kwargs):
202 obj = self.get_object()
203 if obj.can_update(request.user):
204 return super(generics.RetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
206 return Response(status=status.HTTP_400_BAD_REQUEST)
209 # Based on core/views/*.py
210 {% for object in generator.all %}
212 class {{ object.camel }}List(generics.ListCreateAPIView):
213 queryset = {{ object.camel }}.objects.select_related().all()
214 serializer_class = {{ object.camel }}Serializer
215 id_serializer_class = {{ object.camel }}IdSerializer
216 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 %})
219 def get_serializer_class(self):
220 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
222 return self.id_serializer_class
224 return self.serializer_class
226 def get_queryset(self):
227 return {{ object.camel }}.select_by_user(self.request.user)
229 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()",
\r
234 "reasons": serializer.errors}
\r
235 return Response(response, status=status.HTTP_400_BAD_REQUEST)
236 obj = serializer.object
237 obj.caller = request.user
238 if obj.can_update(request.user):
239 return super({{ object.camel }}List, self).create(request, *args, **kwargs)
241 raise Exception("failed obj.can_update")
243 ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
244 if (ret.status_code%100 != 200):
245 raise Exception(ret.data)
250 class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
251 queryset = {{ object.camel }}.objects.select_related().all()
252 serializer_class = {{ object.camel }}Serializer
253 id_serializer_class = {{ object.camel }}IdSerializer
255 def get_serializer_class(self):
256 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
258 return self.id_serializer_class
260 return self.serializer_class
262 def get_queryset(self):
263 return {{ object.camel }}.select_by_user(self.request.user)
265 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
267 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView