b1b0251583acd7aa850bc159c9137c9edf317b52
[plstackapi.git] / planetstack / apigen / api.template.py
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
12
13 """
14     Schema of the generator object:
15         all: Set of all Model objects
16         all_if(regex): Set of Model objects that match regex
17
18     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
23
24     TODO: Deal with subnets
25 """
26
27 def 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     )
35
36 @api_view(['GET'])
37 def api_root(request, format=None):
38     return Response({
39         {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
40         {% endfor %}
41     })
42
43 # Based on serializers.py
44
45 class 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={};\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
61                     continue\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
66 \r
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()
73             remote_fieldName = relatedObject.field.m2m_field_name()
74
75             # get the current set of existing relations
76             existing = through.objects.filter(**{local_fieldName: obj});
77
78             data_ids = [item.id for item in data]
79             existing_ids = [getattr(item,remote_fieldName).id for item in existing]
80
81             #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()
96
97 {% for object in generator.all %}
98
99 class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
100     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 %}
108     humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
109     validators = serializers.SerializerMethodField("getValidators")
110     def getHumanReadableName(self, obj):\r
111         return str(obj)
112     def getValidators(self, obj):
113         try:
114             return obj.getValidators()
115         except:
116             return None
117     class Meta:
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 %})
120
121 class {{ object.camel }}IdSerializer(XOSModelSerializer):
122     id = serializers.Field()
123     {% for ref in object.refs %}
124     {% if ref.multi %}
125     {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True) #, read_only=True) #, view_name='{{ ref }}-detail')
126     {% else %}
127     {{ ref }} = serializers.PrimaryKeyRelatedField() # read_only=True) #, view_name='{{ ref }}-detail')
128     {% endif %}
129     {% endfor %}
130     humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
131     validators = serializers.SerializerMethodField("getValidators")
132     def getHumanReadableName(self, obj):\r
133         return str(obj)\r
134     def getValidators(self, obj):\r
135         try:
136             return obj.getValidators()
137         except:
138             return None\r
139     class Meta:\r
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 %})
142
143
144 {% endfor %}
145
146 serializerLookUp = { 
147 {% for object in generator.all %}
148                  {{ object.camel }}: {{ object.camel }}Serializer,
149 {% endfor %}
150                  None: None,
151                 }
152
153 class 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):\r
159         partial = kwargs.pop('partial', False)\r
160         self.object = self.get_object_or_none()\r
161 \r
162         serializer = self.get_serializer(self.object, data=request.DATA,\r
163                                          files=request.FILES, partial=partial)\r
164 \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
170 \r
171         try:\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
180 \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
184 \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
189 \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)
193
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)
198         else:
199             return Response(status=status.HTTP_400_BAD_REQUEST)
200
201
202 # Based on core/views/*.py
203 {% for object in generator.all %}
204
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 %})
211
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
219     def get_queryset(self):
220         return {{ object.camel }}.select_by_user(self.request.user)
221
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)
233         else:
234             raise Exception("failed obj.can_update")
235
236         ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
237         if (ret.status_code%100 != 200):
238             raise Exception(ret.data)
239
240         return ret
241
242
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
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
254     
255     def get_queryset(self):
256         return {{ object.camel }}.select_by_user(self.request.user)
257
258     # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
259
260     # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
261
262 {% endfor %}