remove Dashboard Views from user admin
[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 if hasattr(serializers, "ReadOnlyField"):
14     # rest_framework 3.x
15     IdField = serializers.ReadOnlyField
16 else:
17     # rest_framework 2.x
18     IdField = serializers.Field
19
20 """
21     Schema of the generator object:
22         all: Set of all Model objects
23         all_if(regex): Set of Model objects that match regex
24
25     Model object:
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
30
31     TODO: Deal with subnets
32 """
33
34 def get_REST_patterns():
35     return 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'),
40     {% endfor %}
41     )
42
43 @api_view(['GET'])
44 def api_root(request, format=None):
45     return Response({
46         {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
47         {% endfor %}
48     })
49
50 # Based on serializers.py
51
52 class XOSModelSerializer(serializers.ModelSerializer):
53     def save_object(self, obj, **kwargs):
54
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.
59
60             So, let's strip out these m2m relations, and deal with them ourself.
61         """
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
68                     continue\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
73 \r
74         serializers.ModelSerializer.save_object(self, obj, **kwargs);
75
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()
81
82             # get the current set of existing relations
83             existing = through.objects.filter(**{local_fieldName: obj});
84
85             data_ids = [item.id for item in data]
86             existing_ids = [getattr(item,remote_fieldName).id for item in existing]
87
88             #print "data_ids", data_ids
89             #print "existing_ids", existing_ids
90
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)
96
97             # add relations that are in 'data' but not in 'existing'
98             for item in data:
99                if (item.id not in existing_ids):
100                    #print "add", item
101                    newModel = through(**{local_fieldName: obj, remote_fieldName: item})
102                    newModel.save()
103
104 {% for object in generator.all %}
105
106 class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
107     id = IdField()
108     {% for ref in object.refs %}
109     {% if ref.multi %}
110     {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
111     {% else %}
112     {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
113     {% endif %}
114     {% endfor %}
115     humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
116     validators = serializers.SerializerMethodField("getValidators")
117     def getHumanReadableName(self, obj):\r
118         return str(obj)
119     def getValidators(self, obj):
120         try:
121             return obj.getValidators()
122         except:
123             return None
124     class Meta:
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 %})
127
128 class {{ object.camel }}IdSerializer(XOSModelSerializer):
129     id = IdField()
130     {% for ref in object.refs %}
131     {% if ref.multi %}
132     {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True,  queryset = {{ ref.camel }}.objects.all())
133     {% else %}
134     {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
135     {% endif %}
136     {% endfor %}
137     humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
138     validators = serializers.SerializerMethodField("getValidators")
139     def getHumanReadableName(self, obj):\r
140         return str(obj)\r
141     def getValidators(self, obj):\r
142         try:
143             return obj.getValidators()
144         except:
145             return None\r
146     class Meta:\r
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 %})
149
150
151 {% endfor %}
152
153 serializerLookUp = { 
154 {% for object in generator.all %}
155                  {{ object.camel }}: {{ object.camel }}Serializer,
156 {% endfor %}
157                  None: None,
158                 }
159
160 class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
161
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.
164
165     def update(self, request, *args, **kwargs):\r
166         partial = kwargs.pop('partial', False)\r
167         self.object = self.get_object_or_none()\r
168 \r
169         serializer = self.get_serializer(self.object, data=request.DATA,\r
170                                          files=request.FILES, partial=partial)\r
171 \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
177 \r
178         try:\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
187 \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
191 \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
196 \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)
200
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)
205         else:
206             return Response(status=status.HTTP_400_BAD_REQUEST)
207
208
209 # Based on core/views/*.py
210 {% for object in generator.all %}
211
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 %})
218
219     def get_serializer_class(self):
220         no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
221         if (no_hyperlinks):
222             return self.id_serializer_class
223         else:
224             return self.serializer_class
225
226     def get_queryset(self):
227         return {{ object.camel }}.select_by_user(self.request.user)
228
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)
240         else:
241             raise Exception("failed obj.can_update")
242
243         ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
244         if (ret.status_code%100 != 200):
245             raise Exception(ret.data)
246
247         return ret
248
249
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
254
255     def get_serializer_class(self):
256         no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
257         if (no_hyperlinks):
258             return self.id_serializer_class
259         else:
260             return self.serializer_class
261     
262     def get_queryset(self):
263         return {{ object.camel }}.select_by_user(self.request.user)
264
265     # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
266
267     # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
268
269 {% endfor %}