Простой REST для Django
Для одного прототипа понадобилось сделать быстренько REST сервис для стандартного набора действий типа Create, Read, Update, Delete (CRUD).
Писать специальный код для этого не хотелось, поэтому мировой разум подсказал решение в виде django-rest-interface.
Сама по себе библиотечка проста и изящна. Единственно НО – у нее нет классического setup.py, поэтому через easy_install установить не получится.
Использовать сервис надо будет из Flex приложения, что тоже имеет особенности, а именно:
- Flex плохо дружит с XML тэгами с символом «-» в названии. А корневой элемент xml сериализатора для REST такой символ содержит
- Flex использует теги для обозначения полей, название тега – это название поля. Сериализатор для полей создает тег field с атрибутом name.
Для устранения этих проблем было сделано следующее:
Создаем свой сериализатор
Сериализатор был сделать на основе стандартного сериализатора xml из поставки Python:
""" Flex serializer. Based on XML serializer. """ from django.conf import settings from django.core.serializers import base from django.db import models from django.utils.xmlutils import SimplerXMLGenerator from django.utils.encoding import smart_unicode from xml.dom import pulldom class Serializer(base.Serializer): """ Serializes a QuerySet to XML. """ def indent(self, level): if self.options.get('indent', None) is not None: self.xml.ignorableWhitespace('\n' + ' ' * self.options.get('indent', None) * level) def start_serialization(self): """ Start serialization -- open the XML document and the root element. """ self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", settings.DEFAULT_CHARSET)) self.xml.startDocument() self.xml.startElement("objects", {"version" : "1.0"}) def end_serialization(self): """ End serialization -- end the document. """ self.indent(0) self.xml.endElement("objects") self.xml.endDocument() def start_object(self, obj): """ Called as each object is handled. """ if not hasattr(obj, "_meta"): raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj)) self.indent(1) self.xml.startElement(obj.__class__.__name__, {}) self.indent(2) self.xml.startElement("pk", {}) self.xml.characters(smart_unicode(obj._get_pk_val())) self.xml.endElement("pk") self.xml.startElement("model", {}) self.xml.characters(smart_unicode(obj._meta)) self.xml.endElement("model") def end_object(self, obj): """ Called after handling all fields for an object. """ self.indent(1) self.xml.endElement(obj.__class__.__name__) def handle_field(self, obj, field): """ Called to handle each field on an object (except for ForeignKeys and ManyToManyFields) """ self.indent(2) self.xml.startElement(field.name, { "type" : field.get_internal_type() }) # Get a "string version" of the object's data (this is handled by the # serializer base class). if getattr(obj, field.name) is not None: value = self.get_string_value(obj, field) self.xml.characters(smart_unicode(value)) else: self.xml.addQuickElement("None") self.xml.endElement(field.name) def handle_fk_field(self, obj, field): """ Called to handle a ForeignKey (we need to treat them slightly differently from regular fields). """ self._start_relational_field(field) related = getattr(obj, field.name) if related is not None: if field.rel.field_name == related._meta.pk.name: # Related to remote object via primary key related = related._get_pk_val() else: # Related to remote object via other field related = getattr(related, field.rel.field_name) self.xml.characters(smart_unicode(related)) else: self.xml.addQuickElement("None") self.xml.endElement(field.name) def handle_m2m_field(self, obj, field): """ Called to handle a ManyToManyField. Related objects are only serialized as references to the object's PK (i.e. the related *data* is not dumped, just the relation). """ if field.creates_table: self._start_relational_field(field) for relobj in getattr(obj, field.name).iterator(): self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())}) self.xml.endElement(field.name) def _start_relational_field(self, field): """ Helper to output the element for relational fields """ self.indent(2) self.xml.startElement(field.name, { "rel" : field.rel.__class__.__name__, "to" : smart_unicode(field.rel.to._meta), }) |
2. Создаем сериализатор для django-rest
class FlexResponder(SerializeResponder): def __init__(self, paginate_by=None, allow_empty=False): SerializeResponder.__init__(self, 'flex', 'application/xml', paginate_by=paginate_by, allow_empty=allow_empty) def error(self, request, status_code, error_dict=None): """ Return XML error response that includes a human readable error message, application-specific errors and a machine readable status code. """ from django.conf import settings if not error_dict: error_dict = ErrorDict() response = HttpResponse(mimetype = self.mimetype) response.status_code = status_code xml = SimplerXMLGenerator(response, settings.DEFAULT_CHARSET) xml.startDocument() xml.startElement("django-error", {}) xml.addQuickElement(name="error-message", contents='%d %s' % (status_code, STATUS_CODE_TEXT[status_code])) xml.addQuickElement(name="status-code", contents=str(status_code)) if error_dict: xml.startElement("model-errors", {}) for (model_field, errors) in error_dict.items(): for error in errors: xml.addQuickElement(name=model_field, contents=error) xml.endElement("model-errors") xml.endElement("django-error") xml.endDocument() return response |
3. Подключаем сериализатор в Python через settings.py:
SERIALIZATION_MODULES = { 'flex': 'myserver.flex_serializer' } |
4. Подключаем сериализатор к коллекции:
test_resource = Collection( queryset = models.Test.objects.all(), responder = FlexResponder() ) |
Комментарии