support for through relations in REST API, WIP
[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             print "XXX", accessor, relatedObject, data
74             existing = through.objects.filter(**{local_fieldName: obj});
75             print "existing", existing
76
77
78
79 {% for object in generator.all %}
80
81 class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
82     id = serializers.Field()
83     {% for ref in object.refs %}
84     {% if ref.multi %}
85     {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
86     {% else %}
87     {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
88     {% endif %}
89     {% endfor %}
90     humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
91     validators = serializers.SerializerMethodField("getValidators")
92     def getHumanReadableName(self, obj):\r
93         return str(obj)
94     def getValidators(self, obj):
95         try:
96             return obj.getValidators()
97         except:
98             return None
99     class Meta:
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 %})
102
103 class {{ object.camel }}IdSerializer(XOSModelSerializer):
104     id = serializers.Field()
105     {% for ref in object.refs %}
106     {% if ref.multi %}
107     {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True) #, read_only=True) #, view_name='{{ ref }}-detail')
108     {% else %}
109     {{ ref }} = serializers.PrimaryKeyRelatedField() # read_only=True) #, view_name='{{ ref }}-detail')
110     {% endif %}
111     {% endfor %}
112     humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
113     validators = serializers.SerializerMethodField("getValidators")
114     def getHumanReadableName(self, obj):\r
115         return str(obj)\r
116     def getValidators(self, obj):\r
117         try:
118             return obj.getValidators()
119         except:
120             return None\r
121     class Meta:\r
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 %})
124
125
126 {% endfor %}
127
128 serializerLookUp = { 
129 {% for object in generator.all %}
130                  {{ object.camel }}: {{ object.camel }}Serializer,
131 {% endfor %}
132                  None: None,
133                 }
134
135 class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
136
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.
139
140     def update(self, request, *args, **kwargs):\r
141         partial = kwargs.pop('partial', False)\r
142         self.object = self.get_object_or_none()\r
143 \r
144         serializer = self.get_serializer(self.object, data=request.DATA,\r
145                                          files=request.FILES, partial=partial)\r
146 \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
152 \r
153         try:\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
162 \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
166 \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
171 \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)
175
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)
180         else:
181             return Response(status=status.HTTP_400_BAD_REQUEST)
182
183
184 # Based on core/views/*.py
185 {% for object in generator.all %}
186
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 %})
193
194     def get_serializer_class(self):
195         no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
196         if (no_hyperlinks):
197             return self.id_serializer_class
198         else:
199             return self.serializer_class
200
201     def get_queryset(self):
202         return {{ object.camel }}.select_by_user(self.request.user)
203
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)
215         else:
216             raise Exception("failed obj.can_update")
217
218         ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
219         if (ret.status_code%100 != 200):
220             raise Exception(ret.data)
221
222         return ret
223
224
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
229
230     def get_serializer_class(self):
231         no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
232         if (no_hyperlinks):
233             return self.id_serializer_class
234         else:
235             return self.serializer_class
236     
237     def get_queryset(self):
238         return {{ object.camel }}.select_by_user(self.request.user)
239
240     # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
241
242     # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
243
244 {% endfor %}