from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import status
+from rest_framework.generics import GenericAPIView
from core.models import *
from django.forms import widgets
+from rest_framework import filters
+from django.conf.urls import patterns, url
+
+if hasattr(serializers, "ReadOnlyField"):
+ # rest_framework 3.x
+ IdField = serializers.ReadOnlyField
+else:
+ # rest_framework 2.x
+ IdField = serializers.Field
"""
- Schema of the generator object:
- all: Set of all Model objects
- all_if(regex): Set of Model objects that match regex
-
- Model object:
- plural: English plural of object name
- camel: CamelCase version of object name
- refs: list of references to other Model objects
- props: list of properties minus refs
-
- TODO: Deal with subnets
+ Schema of the generator object:
+ all: Set of all Model objects
+ all_if(regex): Set of Model objects that match regex
+
+ Model object:
+ plural: English plural of object name
+ camel: CamelCase version of object name
+ refs: list of references to other Model objects
+ props: list of properties minus refs
+
+ TODO: Deal with subnets
"""
-# Based on api_root.py
+def get_REST_patterns():
+ return patterns('',
+ url(r'^plstackapi/$', api_root),
+ {% for object in generator.all %}
+ url(r'plstackapi/{{ object.rest_name }}/$', {{ object.camel }}List.as_view(), name='{{ object.singular }}-list'),
+ url(r'plstackapi/{{ object.rest_name }}/(?P<pk>[a-zA-Z0-9\-]+)/$', {{ object.camel }}Detail.as_view(), name ='{{ object.singular }}-detail'),
+ {% endfor %}
+ )
@api_view(['GET'])
def api_root(request, format=None):
return Response({
- {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
- {% endfor %}
+ {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
+ {% endfor %}
})
# Based on serializers.py
+class XOSModelSerializer(serializers.ModelSerializer):
+ def save_object(self, obj, **kwargs):
+
+ """ rest_framework can't deal with ManyToMany relations that have a
+ through table. In plstackapi, most of the through tables we have
+ use defaults or blank fields, so there's no reason why we shouldn't
+ be able to save these objects.
+
+ So, let's strip out these m2m relations, and deal with them ourself.
+ """
+ obj._complex_m2m_data={};\r
+ if getattr(obj, '_m2m_data', None):\r
+ for relatedObject in obj._meta.get_all_related_many_to_many_objects():\r
+ if (relatedObject.field.rel.through._meta.auto_created):\r
+ # These are non-trough ManyToMany relations and\r
+ # can be updated just fine\r
+ continue\r
+ fieldName = relatedObject.get_accessor_name()\r
+ if fieldName in obj._m2m_data.keys():\r
+ obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])\r
+ del obj._m2m_data[fieldName]\r
+\r
+ serializers.ModelSerializer.save_object(self, obj, **kwargs);
+
+ for (accessor, stuff) in obj._complex_m2m_data.items():
+ (relatedObject, data) = stuff
+ through = relatedObject.field.rel.through
+ local_fieldName = relatedObject.field.m2m_reverse_field_name()
+ remote_fieldName = relatedObject.field.m2m_field_name()
+
+ # get the current set of existing relations
+ existing = through.objects.filter(**{local_fieldName: obj});
+
+ data_ids = [item.id for item in data]
+ existing_ids = [getattr(item,remote_fieldName).id for item in existing]
+
+ #print "data_ids", data_ids
+ #print "existing_ids", existing_ids
+
+ # remove relations that are in 'existing' but not in 'data'
+ for item in list(existing):
+ if (getattr(item,remote_fieldName).id not in data_ids):
+ print "delete", getattr(item,remote_fieldName)
+ item.delete() #(purge=True)
+
+ # add relations that are in 'data' but not in 'existing'
+ for item in data:
+ if (item.id not in existing_ids):
+ #print "add", item
+ newModel = through(**{local_fieldName: obj, remote_fieldName: item})
+ newModel.save()
+
{% for object in generator.all %}
class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
- id = serializers.Field()
- {% for ref in object.refs %}
- {{ ref.plural }} = serializers.HyperlinkedRelatedField(view_name='{{ ref }}-detail')
- {% endfor %}
- class Meta:
- model = {{ object }}
- fields = ({% for prop in object.props %}'{{ prop }}',{% endfor %})
+ id = IdField()
+ {% for ref in object.refs %}
+ {% if ref.multi %}
+ {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
+ {% else %}
+ {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
+ {% endif %}
+ {% endfor %}
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):\r
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = {{ object.camel }}
+ fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
+
+class {{ object.camel }}IdSerializer(XOSModelSerializer):
+ id = IdField()
+ {% for ref in object.refs %}
+ {% if ref.multi %}
+ {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True, queryset = {{ ref.camel }}.objects.all())
+ {% else %}
+ {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
+ {% endif %}
+ {% endfor %}
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):\r
+ return str(obj)\r
+ def getValidators(self, obj):\r
+ try:
+ return obj.getValidators()
+ except:
+ return None\r
+ class Meta:\r
+ model = {{ object.camel }}
+ fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
+
+
{% endfor %}
serializerLookUp = {
None: None,
}
+class PlanetStackRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
+
+ # To handle fine-grained field permissions, we have to check can_update
+ # the object has been updated but before it has been saved.
+
+ def update(self, request, *args, **kwargs):\r
+ partial = kwargs.pop('partial', False)\r
+ self.object = self.get_object_or_none()\r
+\r
+ serializer = self.get_serializer(self.object, data=request.DATA,\r
+ files=request.FILES, partial=partial)\r
+\r
+ if not serializer.is_valid():\r
+ response = {"error": "validation",\r
+ "specific_error": "not serializer.is_valid()",\r
+ "reasons": serializer.errors}\r
+ return Response(response, status=status.HTTP_400_BAD_REQUEST)\r
+\r
+ try:\r
+ self.pre_save(serializer.object)\r
+ except ValidationError as err:\r
+ # full_clean on model instance may be called in pre_save,\r
+ # so we have to handle eventual errors.\r
+ response = {"error": "validation",\r
+ "specific_error": "ValidationError in pre_save",\r
+ "reasons": err.message_dict}\r
+ return Response(response, status=status.HTTP_400_BAD_REQUEST)\r
+\r
+ if serializer.object is not None:\r
+ if not serializer.object.can_update(request.user):\r
+ return Response(status=status.HTTP_400_BAD_REQUEST)\r
+\r
+ if self.object is None:\r
+ self.object = serializer.save(force_insert=True)\r
+ self.post_save(self.object, created=True)\r
+ return Response(serializer.data, status=status.HTTP_201_CREATED)\r
+\r
+ self.object = serializer.save(force_update=True)\r
+ self.post_save(self.object, created=False)\r
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ def destroy(self, request, *args, **kwargs):
+ obj = self.get_object()
+ if obj.can_update(request.user):
+ return super(generics.RetrieveUpdateDestroyAPIView, self).destroy(request, *args, **kwargs)
+ else:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+
+
# Based on core/views/*.py
{% for object in generator.all %}
class {{ object.camel }}List(generics.ListCreateAPIView):
- #queryset = {{ object.camel }}.objects.all()
+ queryset = {{ object.camel }}.objects.select_related().all()
serializer_class = {{ object.camel }}Serializer
-
+ id_serializer_class = {{ object.camel }}IdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ({% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
+
+ def get_serializer_class(self):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
def get_queryset(self):
- return {{ object.camel }}.select_by_user(self.request.user
+ return {{ object.camel }}.select_by_user(self.request.user)
-class {{ object.camel }}Detail(generics.RetrieveUpdateDestroyAPIView):
- #queryset = {{ object.camel }}.objects.all()
+ def create(self, request, *args, **kwargs):
+ serializer = self.get_serializer(data=request.DATA, files=request.FILES)
+ if not (serializer.is_valid()):
+ response = {"error": "validation",
+ "specific_error": "not serializer.is_valid()",\r
+ "reasons": serializer.errors}\r
+ return Response(response, status=status.HTTP_400_BAD_REQUEST)
+ obj = serializer.object
+ obj.caller = request.user
+ if obj.can_update(request.user):
+ return super({{ object.camel }}List, self).create(request, *args, **kwargs)
+ else:
+ raise Exception("failed obj.can_update")
+
+ ret = super({{ object.camel }}List, self).create(request, *args, **kwargs)
+ if (ret.status_code%100 != 200):
+ raise Exception(ret.data)
+
+ return ret
+
+
+class {{ object.camel }}Detail(PlanetStackRetrieveUpdateDestroyAPIView):
+ queryset = {{ object.camel }}.objects.select_related().all()
serializer_class = {{ object.camel }}Serializer
+ id_serializer_class = {{ object.camel }}IdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
def get_queryset(self):
- return {{ object.camel }}.select_by_user(self.request.user)
+ return {{ object.camel }}.select_by_user(self.request.user)
+
+ # update() is handled by PlanetStackRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by PlanetStackRetrieveUpdateDestroyAPIView
{% endfor %}