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
14 Schema of the generator object:
15 all: Set of all Model objects
16 all_if(regex): Set of Model objects that match regex
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
24 TODO: Deal with subnets
27 def get_REST_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'),
37 def api_root(request, format=None):
39 {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
43 # Based on serializers.py
45 class XOSModelSerializer(serializers.ModelSerializer):
46 def save_object(self, obj, **kwargs):
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.
53 So, let's strip out these m2m relations, and deal with them ourself.
55 obj._complex_m2m_data={};
\r
56 if getattr(obj, '_m2m_data', None):
\r
57 for relatedObject in obj._meta.get_all_related_many_to_many_objects():
\r
58 if (relatedObject.field.rel.through._meta.auto_created):
\r
59 # These are non-trough ManyToMany relations and
\r
60 # can be updated just fine
\r
62 fieldName = relatedObject.get_accessor_name()
\r
63 if fieldName in obj._m2m_data.keys():
\r
64 obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
\r
65 del obj._m2m_data[fieldName]
\r
67 serializers.ModelSerializer.save_object(self, obj, **kwargs);
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()
73 remote_fieldName = relatedObject.field.m2m_field_name()
75 # get the current set of existing relations
76 existing = through.objects.filter(**{local_fieldName: obj});
78 data_ids = [item.id for item in data]
79 existing_ids = [getattr(item,remote_fieldName).id for item in existing]
81 #print "data_ids", data_ids
82 #print "existing_ids", existing_ids
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)
90 # add relations that are in 'data' but not in 'existing'
92 if (item.id not in existing_ids):
94 newModel = through(**{local_fieldName: obj, remote_fieldName: item})
97 {% for object in generator.all %}
99 class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
100 id = serializers.Field()
101 {% for ref in object.refs %}
103 {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
105 {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
108 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
109 validators = serializers.SerializerMethodField("getValidators")
110 def getHumanReadableName(self, obj):
\r
112 def getValidators(self, obj):
114 return obj.getValidators()
118 model = {{ object.camel }}
119 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
121 class {{ object.camel }}IdSerializer(XOSModelSerializer):
122 id = serializers.Field()
123 {% for ref in object.refs %}
125 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True) #, read_only=True) #, view_name='{{ ref }}-detail')
127 {{ ref }} = serializers.PrimaryKeyRelatedField() # read_only=True) #, view_name='{{ ref }}-detail')
130 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
131 validators = serializers.SerializerMethodField("getValidators")
132 def getHumanReadableName(self, obj):
\r
134 def getValidators(self, obj):
\r
136 return obj.getValidators()
140 model = {{ object.camel }}
141 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
147 {% for object in generator.all %}
148 {{ object.camel }}: {{ object.camel }}Serializer,
153 class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
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.
158 def update(self, request, *args, **kwargs):
\r
159 partial = kwargs.pop('partial', False)
\r
160 self.object = self.get_object_or_none()
\r
162 serializer = self.get_serializer(self.object, data=request.DATA,
\r
163 files=request.FILES, partial=partial)
\r
165 if not serializer.is_valid():
\r
166 response = {"error": "validation",
\r
167 "specific_error": "not serializer.is_valid()",
\r
168 "reasons": serializer.errors}
\r
169 return Response(response, status=status.HTTP_400_BAD_REQUEST)
\r
172 self.pre_save(serializer.object)
\r
173 except ValidationError as err:
\r
174 # full_clean on model instance may be called in pre_save,
\r
175 # so we have to handle eventual errors.
\r
176 response = {"error": "validation",
\r
177 "specific_error": "ValidationError in pre_save",
\r
178 "reasons": err.message_dict}
\r
179 return Response(response, status=status.HTTP_400_BAD_REQUEST)
\r
181 if serializer.object is not None:
\r
182 if not serializer.object.can_update(request.user):
\r
183 return Response(status=status.HTTP_400_BAD_REQUEST)
\r
185 if self.object is None:
\r
186 self.object = serializer.save(force_insert=True)
\r
187 self.post_save(self.object, created=True)
\r
188 return Response(serializer.data, status=status.HTTP_201_CREATED)
\r
190 self.object = serializer.save(force_update=True)
\r
191 self.post_save(self.object, created=False)
\r
192 return Response(serializer.data, status=status.HTTP_200_OK)
194 def destroy(self, request, *args, **kwargs):
195 obj = self.get_object()
196 if obj.can_update(request.user):
197 return super(generics.RetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
199 return Response(status=status.HTTP_400_BAD_REQUEST)
202 # Based on core/views/*.py
203 {% for object in generator.all %}
205 class {{ object.camel }}List(generics.ListCreateAPIView):
206 queryset = {{ object.camel }}.objects.select_related().all()
207 serializer_class = {{ object.camel }}Serializer
208 id_serializer_class = {{ object.camel }}IdSerializer
209 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 %})
212 def get_serializer_class(self):
213 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
215 return self.id_serializer_class
217 return self.serializer_class
219 def get_queryset(self):
220 return {{ object.camel }}.select_by_user(self.request.user)
222 def create(self, request, *args, **kwargs):
223 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
224 if not (serializer.is_valid()):
225 response = {"error": "validation",
226 "specific_error": "not serializer.is_valid()",
\r
227 "reasons": serializer.errors}
\r
228 return Response(response, status=status.HTTP_400_BAD_REQUEST)
229 obj = serializer.object
230 obj.caller = request.user
231 if obj.can_update(request.user):
232 return super({{ object.camel }}List, self).create(request, *args, **kwargs)
234 raise Exception("failed obj.can_update")
236 ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
237 if (ret.status_code%100 != 200):
238 raise Exception(ret.data)
243 class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
244 queryset = {{ object.camel }}.objects.select_related().all()
245 serializer_class = {{ object.camel }}Serializer
246 id_serializer_class = {{ object.camel }}IdSerializer
248 def get_serializer_class(self):
249 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
251 return self.id_serializer_class
253 return self.serializer_class
255 def get_queryset(self):
256 return {{ object.camel }}.select_by_user(self.request.user)
258 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
260 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView