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 print "XXX", accessor, relatedObject, data
74 existing = through.objects.filter(**{local_fieldName: obj});
75 print "existing", existing
79 {% for object in generator.all %}
81 class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
82 id = serializers.Field()
83 {% for ref in object.refs %}
85 {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
87 {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
90 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
91 validators = serializers.SerializerMethodField("getValidators")
92 def getHumanReadableName(self, obj):
\r
94 def getValidators(self, obj):
96 return obj.getValidators()
100 model = {{ object.camel }}
101 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
103 class {{ object.camel }}IdSerializer(XOSModelSerializer):
104 id = serializers.Field()
105 {% for ref in object.refs %}
107 {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True) #, read_only=True) #, view_name='{{ ref }}-detail')
109 {{ ref }} = serializers.PrimaryKeyRelatedField() # read_only=True) #, view_name='{{ ref }}-detail')
112 humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
113 validators = serializers.SerializerMethodField("getValidators")
114 def getHumanReadableName(self, obj):
\r
116 def getValidators(self, obj):
\r
118 return obj.getValidators()
122 model = {{ object.camel }}
123 fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
129 {% for object in generator.all %}
130 {{ object.camel }}: {{ object.camel }}Serializer,
135 class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
137 # To handle fine-grained field permissions, we have to check can_update
138 # the object has been updated but before it has been saved.
140 def update(self, request, *args, **kwargs):
\r
141 partial = kwargs.pop('partial', False)
\r
142 self.object = self.get_object_or_none()
\r
144 serializer = self.get_serializer(self.object, data=request.DATA,
\r
145 files=request.FILES, partial=partial)
\r
147 if not serializer.is_valid():
\r
148 response = {"error": "validation",
\r
149 "specific_error": "not serializer.is_valid()",
\r
150 "reasons": serializer.errors}
\r
151 return Response(response, status=status.HTTP_400_BAD_REQUEST)
\r
154 self.pre_save(serializer.object)
\r
155 except ValidationError as err:
\r
156 # full_clean on model instance may be called in pre_save,
\r
157 # so we have to handle eventual errors.
\r
158 response = {"error": "validation",
\r
159 "specific_error": "ValidationError in pre_save",
\r
160 "reasons": err.message_dict}
\r
161 return Response(response, status=status.HTTP_400_BAD_REQUEST)
\r
163 if serializer.object is not None:
\r
164 if not serializer.object.can_update(request.user):
\r
165 return Response(status=status.HTTP_400_BAD_REQUEST)
\r
167 if self.object is None:
\r
168 self.object = serializer.save(force_insert=True)
\r
169 self.post_save(self.object, created=True)
\r
170 return Response(serializer.data, status=status.HTTP_201_CREATED)
\r
172 self.object = serializer.save(force_update=True)
\r
173 self.post_save(self.object, created=False)
\r
174 return Response(serializer.data, status=status.HTTP_200_OK)
176 def destroy(self, request, *args, **kwargs):
177 obj = self.get_object()
178 if obj.can_update(request.user):
179 return super(generics.RetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
181 return Response(status=status.HTTP_400_BAD_REQUEST)
184 # Based on core/views/*.py
185 {% for object in generator.all %}
187 class {{ object.camel }}List(generics.ListCreateAPIView):
188 queryset = {{ object.camel }}.objects.select_related().all()
189 serializer_class = {{ object.camel }}Serializer
190 id_serializer_class = {{ object.camel }}IdSerializer
191 filter_backends = (filters.DjangoFilterBackend,)
192 filter_fields = ({% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
194 def get_serializer_class(self):
195 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
197 return self.id_serializer_class
199 return self.serializer_class
201 def get_queryset(self):
202 return {{ object.camel }}.select_by_user(self.request.user)
204 def create(self, request, *args, **kwargs):
205 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
206 if not (serializer.is_valid()):
207 response = {"error": "validation",
208 "specific_error": "not serializer.is_valid()",
\r
209 "reasons": serializer.errors}
\r
210 return Response(response, status=status.HTTP_400_BAD_REQUEST)
211 obj = serializer.object
212 obj.caller = request.user
213 if obj.can_update(request.user):
214 return super({{ object.camel }}List, self).create(request, *args, **kwargs)
216 raise Exception("failed obj.can_update")
218 ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
219 if (ret.status_code%100 != 200):
220 raise Exception(ret.data)
225 class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
226 queryset = {{ object.camel }}.objects.select_related().all()
227 serializer_class = {{ object.camel }}Serializer
228 id_serializer_class = {{ object.camel }}IdSerializer
230 def get_serializer_class(self):
231 no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
233 return self.id_serializer_class
235 return self.serializer_class
237 def get_queryset(self):
238 return {{ object.camel }}.select_by_user(self.request.user)
240 # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
242 # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView