{ "info": { "author": "C. Paschalides", "author_email": "already.late@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Framework :: Django", "Intended Audience :: Developers", "License :: Freely Distributable", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP" ], "description": "# django-icetea\n\n``django-icetea`` is a package built on top of [Django](https://www.djangoproject.com/) and provides abstractions for creating REST APIs.\n\nIt has been influenced by the architecture of [django-piston](https://bitbucket.org/jespern/django-piston/wiki/Home) and [piston-perfect](https://github.com/smartpr/piston-perfect).\n\nI decided to build ``django-icetea``, in order to have an API framework with tight foundations, consistent and intuitive behaviour, *readable code*, and of course, easy to use.\n\n## Installation\n\n``django-icetea`` is registered in [PyPI](http://pypi.python.org/pypi/django-icetea/), so \ninstalling it is as easy as running:\n\n pip install django-icetea\n\nHowever, I would suggest using the latest version from github. The master branch is always stable.\n\n### Settings parameters\nIn your application's ``settings.py`` file, you can specify the following\nparameters related to ``django-icetea``:\n\n* ``ICETEA_ERRORS``: With ``True``, enables the sending of emails to the\napplications's admins, in the case of Server Errors. Default is ``True``.\n\n* ``ICETEA_DISPLAY_ERRORS``: With ``True``, returns well-formed error messages in the case of\nServer Errors. It requires that ``DEBUG=True``. Default is ``True``.\n\n## Documentation\n\nThe code is thoroughly documented. Use [epydoc](http://epydoc.sourceforge.net/) to parse it and generate a\ndocument out of it. For example, in order to create an *html* page with the\ndocumentation, ``cd`` into the *django-icetea* folder, and issue:\n\n epydoc --html icetea -o docs\n\nThis will create folder *docs*. Open the file *index.html* for the whole code\ndocumentation.\n\n## Philosophy\n\n``django-icetea`` aims to provide the abstractions for providing out-of-the-box \nfunctionality for creating APIs. It strives to keep things clear and explicit,\nwithout any unnecessary magic behind the scenes.\n\nIt is very extensible, and the default behaviour can be overridden, extended\nand modified at will.\n\nAs in any project though, some assumptions have to be made, and some\nconventions need to be predefined. \n\nFor example, although *HTTP* is an\napplication protocol, it is mostly used for interaction with Web\nbrowsers. When applied in more generic request/response schemes, there are\nscenarios that the protocol itself does not indicate the correct behavior.\nFor this reason, I mostly view *HTTP* as the means of transmission\nfor requests and responses. It is unaware of business logic, and therefore it\nlacks the means of mapping application specific semantics or errors to *HTTP\nResponses*. \n\nA specific case in which the *HTTP* protocol doesn't really specify the\nbehaviour, is the following:\n\n> Say we need to create a model instance; We issue a *POST* request to the\n> API, and we expect a response which will indicate *if* the\n> resource has been created, and if yes, return the resource.\n> The server first needs to validate the data it has received. If the data\n> are invalid, the API should return a ``400 Bad Request`` response. If the data\n> are valid, but upon creation the database fails, what do we do? Do we\n> return ``400 Bad Request``? No way. This will confuse the user and indicate\n> that the data provided were invalid. The request was validated successfully,\n> so this is not the case, Do we return a Successful response ``200 OK``,\n> and empty data? I choose for the latter. This indicates that the data were\n> indeed valid, but the resource failed to be created.\n\nIn anycase developing an API is all about consistent and unambiguous\ncommunication between the client and the server. This has been one of my main\ngoals with this project. If different applications require different semantics,\n*django-icetea*'s code can easily be modified to support them.\n\nMoreover, following the [Principle of the least astonishment](http://en.wikipedia.org/wiki/Principle_of_least_astonishment) which is what *Python* in general, and *Django* in particular encourage, I\nhave tried to follow the general behavior that *Django* users are familiar\nwith. An example of this is the ``validation`` method of the ``ModelHandler`` class (in the ``handlers.py`` module). It\ncleans the data, creates model instances (without committing them to the\ndatabase) and validates them using the model's ``full-clean`` method. Once this\nis done, we are certain that we are dealing with perfectly valid model\ninstances, which we can safely write to the database. This means that we don't\nhave the need to do all these steps manually, since they are offered by\n*django-icetea* out of the box. It is exactly how\nvalidation for Django *Modelform* works, and how most *Django* developers are\nused to doing things.. \nThe means have changed (REST API instead of Forms), but the procedure is still the\nsame.\n\n## Short Introduction\n\n*django-icetea* offers 2 types of handlers:\n\n* ``ModelHandler``: Used to expose *Django* models to the API. Offers CRUD\n functionality out of the box.\n\n* ``BaseHandler``: Used to expose data that don't map on a model. Most of the\n functionality will need to be written manually. \n\n### Glossary\n\n``Singular Request``: A request that refers to a single resource. The resource\nis usually identified by the url. For example a *GET/PUT/DELETE* request on ``/resource//`` is a\nsingular request.\n\n``Plural Request``: A request that affects(retrieves or modifies) a group of resources (usually a subset or all the resources that the client has the right to view). It could be plural GET, plural PUT,\nor plural DELETE.\n\n``Bulk Request``: Request with an array of data in its request body. It only makes\nsense for *POST* requests, and aims to create multiple instances in one request. For Bulk POST requests there\nis no recommended behavior or semantics, so we defined our own semantics, in order to make sure\nthat the functionality is predictable and makes the most sense. More details\ncan be seen in section [Bulk POST requests](https://github.com/stargazer/django-icetea#bulk-post-requests).\n\n### Assumptions\n\nThe only assumption that *django-icetea* makes is that singular requests are\ndenoted by the keyword argument ``id``. So for example a GET request of the\nfollowing form ``/resource//`` requests the resource with ``id=``.\n\nThis is essential mostly for security related checks, which mainly control\nwhether the request is plural, and if such a request has been explicitly\nallowed.\n\n### Incoming Requests\n\nThe ``Content-type`` header for incoming requests should be\n``application/json``. This is currently the only request body format that\n*django-icetea* recognizes.\n\n### Outgoing responses\n\nThe outgoing responses of *django-icetea* can be of one of the following\nformats:\n\n* ``application/json``\n* ``text/xml``\n* ``text/html``\n* ``application/vnd.ms-excel``\n\nThe default is ``application/json``.\nPlease not that in the case of outputting ``json``, or ``xml``, it is easy to serialize\ndata structures in the response. However in the case of ``html`` and\nespecially ``xls`` format, there should (probably) be application specific semantics applied to the output\nemitters.\n\n### Status codes\n\nThe Status codes have the following meanings:\n\n* ``200 OK``: Request was served successfully\n* ``403 Forbidden``: The client is not authenticated\n* ``405 Method Not Allowed``: The request method was performed on a resource that does not support that method\n* ``410 Gone``: The resource is not available\n* ``422 UnprocessableEntity``: The request was valid but could not be processed due to the semantics of the resource (for example, a *DELETE* request on a resource that belongs to the authenticated client. We might choose not to allow deletion of the specific resource if its field *x* has a specific value. In that case we respond with a *422 UnprocessableEntity* response).\n\nFor a very detailed description of the expected responses for any\nkind of request, please check section [Request and response protocol](https://github.com/stargazer/django-icetea#request-and-response-protocol).\n\n\n### Tests\n\n``django-icetea`` comes with its own test suite, found in the ``tests.py`` module. This module defines Base Test Classes, which are used to test ``django-icetea`` itself, and can also be used to test any API implementation.\n\n### CSRF tokens\n\nDjango uses *CSRF tokens*, in order to deal with web browsers' \n[CSRF vulnerability](http://www.squarefree.com/securitytips/web-developers.html#CSRF). \nDjango's *CsrfViewMiddleware* inserts a *CSRF token* as a hidden field in every form using the \n*POST method*, before sending the form to the web browser. For every subsequent \n*POST request* from the web browser, the same middleware checks the token, to ensure that \nit contains the expected value. If not a *403 Forbidden* response is returned.\n\nHowever, since *django-icetea* is an API and does not make use of forms, the\nCSRF token doesn't make a lot of sense. So by default *django-icetea* views are\n*CSRF exempted*, meaning they don't require the CSRF token.\n\n## Usage\n\nSay we have an API built on top of ``django-icetea``. Let's assume we have\na Django app called ``foo``, with a model ``FooModel``.\n \nWe want to define 2 API handlers; One that exposes the model ``FooModel``,\nand another one that exposes some other non-model data.\n\nOther than defining the business logic, handlers also act as means of\nrepresentation. For example, ``ModelHandler`` classes, define how the\ncorresponding model will be represented within that handler(which fields should\nbe exposed), but also in cases that it is nested in the responses of other handlers.\n\n### foo/handlers.py\n\nTODO: Example with a BaseHandler\n\nHere we define our API handler, which is the implementer of the business\nlogic\n\n``` python\nfrom foo.models import foomodel\nfrom icetea.handlers import ModelHandler\n\nclass FooHandler(ModelHandler):\n authentication = True\n model = FooModel\n\n read = True\n create = True\n\n allowed_out_fields = (\n 'id',\n 'field1', \n 'field2',\n )\n\n allowed_in_fields = (\n 'field1',\n 'field2',\n )\n``` \n\n### foo/urls.py\n\nWe need to create resources(equivalent to Django views), which will initiate\nthe serving of API requests\n\n``` python\nfrom djanco.conf.urls.defaults import *\nfrom foo.handlers import FooHandler\nfrom icetea.resource import Resource\n\nfoo_handler = Resource(FooHandler)\n\nurlpatterns = patterns('',\n url(r'^foo/$ ', foo_handler),\n url(r'^foo/(?P\\d+)/$ ', foo_handler),\n)\n```\n\n## Handler level attributes\n\n### Relevant for all handlers\n\n#### read, create, update, delete\n\nIf any of these parameters is ``True``, then the handler allows ``GET``,\n``POST``, ``PUT`` and ``DELETE`` requests respectively.\n\nIf instead they are defined as methods, eg:\n\n``` python\ndef read(self, request, *args, **kwargs):\n pass\n```\n\nThen the corresponding action is enabled, and the default functionality is\noverridden by the method we defined.\n\n#### bulk_create\n\nIf ``True`` enables bulk-POST requests. Default is ``False``. See section [Notes](https://github.com/stargazer/django-icetea#notes) for more information.\n\nRequires that ``create = True``.\n\nWhen enabled, you should anticipate on ``400 Bad Request`` responses, with a list in their body.\n\n#### plural_update\nIf ``True``, enables plural PUT requests, which means updating multiple\nresources in one request. It is a potentially catastrophic operation, and for\nthis reason is should be explicitly allowed. Default is ``False``.\n\nRequires that ``update = True``.\n\nWhen enabled, you should anticipate on ``400 Bad Request`` responses, with a list in their body.\n\n#### plural_delete\nIf ``True`` enables plural DELETE requests, which means deleting multiple\nresources in one request. It is a potentially catastrophic operation, and for\nthis reason it should be explicitly allowed. Default is ``False``.\n\nRequires that ``delete=True``.\n\nWhen enabled, you should anticipate on ``422 Unprocessable Entity`` responses, with a list in their body.\n\n#### request_fields\n\nIndicates which querystring parameter will act as a a request-level field\nselector. If ``True``, then the selector is ``field``. If ``False``, there will be no field selection. Default is ``True``.\n\n#### order\n \nIndicates which querystring parameter will act as the order-type selector\non the result set of the requested operation.\nIf ``True``, then the parameter is ``order``. If ``False``, no order-type\nselection can be performed. Default is ``False``.\nThe order logic, should be implemented in the handler's ``order_data``\nmethod.\n\n#### slice\n\nIndicates which querystring parameter will be used to request slicing of\nthe result set of the requested operation.\nIf ``True``, then the parameter is ``slice``. If ``False``, no slicing will\nbe possible. Default is ``False``.\nThe slicing notation follows Python's *slice notation*, of ``start:stop:step``. \n\n#### authentication\n \nIf ``True``, only authenticated users can access the handler. The *Django\nauthentication* is used. Default value is ``False``.\n\n#### allowed_out_fields\n \nTuple of fields, which indicates the fields that the handler is allowed to\noutput. \n\nIn the case of a ``ModelHandler``, it indicates model fields.\n\nIn the case of a ``BaseHandler``, it only has sense if the handler returns dictionaries, or lists of dictionaries, and it indicates the dictionary keys that the handler is allowed to output.\n \nThe actual fields that a request will eventually output, is a function of\nthis parameter, as well as the request-level field selection, indicated by\nthe ``field``.\n\n#### allowed_in_fields\n \nTuple of fields, which indicates the fields that the handler is allowed to\ntake from the incoming request body. In the case of ``ModelHandler``\nclasses, no primary keys or related keys are allowed.\n\n### Relevant only for handlers extending ModelHandler\n\n#### model\n \nThe database model which the Handler exposes.\n\n#### filters\n\nA dictionary of ``filter name``: ``filter_operation`` couples. ``filter\nname`` defines the querystring parameter used to apply the filtering on the\ncurrent request. ``filter_operation`` corresponds to a Django lookup\nfilter, which will be applied on the request's resuls data. \n\n#### exclude_nested\n\nFields which should be excluded when the model is nested in another\nhandler's response.\n\n#### excel_filename\n\nThe filename to be given to the attachment, if the the request needs to output\nto ``excel`` format. It can either be a string or a handler method that returns a\nstring. Default value is ``file.xls``\n\n## Notes\n\n### Adding extra (fake) fields on a ModelHandler\n\nIt's possible that we want to add fake fields on the output of a ``ModelHandler``. \nBy *fake* I mean fields that are not actual physical model fields, but simply extra \ninformation that we wish to include on the API handler's output. Doing so is very easy.\n\nIn the model class, you simply need to define the ``fake_fields`` tuple, with the\nnames of the fake fields. Then we define the class method\n``compute_fake_fields(self, field)``, which should return the values of the fake fields.\n\nFor example:\n\n``` python\nfake_fields = ('num_tweets', 'num_retweets',)\n\ndef _compute_fake_fields(self, field):\n if field == 'num_tweets':\n return self.tweets.count() \n\n elif field == 'num_retweets':\n return Retweet.objects.filter(tweet__in=self.tweets.all()).count()\n```\nThe method ``_compute_fake_fields`` is invoked by the ``Emitter`` class, which\nconstructs the output of the handler. The ``field`` parameter is the field name\nthat is evaluated. So the ``_compute_fake_fields`` method should be able to compute\nall the field names in the ``fake_fields`` tuple.\n\nFrom this point on, the API handlers can treat these fields as normal model fields.\nMeaning, they can be included in the tuples ``allowed_out_fields``,\n``exclude_nested``, etc, depending on how you want to treat them.\n\n### Bulk POST requests\n\n*Bulk POST request* refers to a single ``POST`` request which attempts to create\nmultiple resources. The specifications of ``REST`` or ``HTTP`` don't specify\nany standard behaviour for such requests, and instead discourage its use, due\nto poor semantics.\n\nFor example, how would the API signal an error on one of the data objects in \nthe request body? Or, how would it signal a database error, when all the data\nobjects in the request body were valid?\n\nI chose the following behavior:\n\n* Any error in the request body, will return a ``Bad Request`` response.\n For example when the data in the request body refer to Django models, if\n even one of the models fails to validate, the response will be ``400 Bad\n Request``. The response body will include a list, with all the objects that\n could not be validated. Every object should have an ``index`` parameter, that\n specifies a zero-bazed index of the request body parameter that could not be\n validated.\n\n (Similarly a ``POST`` request for a single instance, returns ``Bad request``\n if the request body does not contain valid data) \n\n* If the request body is valid, the response is ``OK``, and its body\n contains a list of all the successfully added model instances. If one model\n instance failed to be created (due for example to a database error),\n although it contained valid data, it will not be part of the response\n data.\n\n (Similarly a POST request for a single instance, returns an ``OK``\n response, and the model instance in the request body. If the model\n instance failed to be created, although it was valid, we return an ``OK``\n response, with ``null`` in the response body)\n\nThis is in my opinion the most intuitive behavior. However I think that it all\ndepends on the requirements of each application, and the clients using the API.\nSo feel free to modify the existing behavior.\n\nBy default *bulk POST requests* are disabled. They can be enabled by setting\n``bulk_create = True`` in the handler class.\n\n### Building inheritable handlers... Metaclass magic\n\nIn this subsection, the term ``operation`` means one of ``read``, ``create``,\n``update``, ``delete``.\n\nWhen a handler class sets ``read = True``, basically it says to the system:\n\n> I want to inherit the standard ``read`` functionality. Please provide me with it.\n\nThis works with some metaclass magic. Clearly some magic needs to be in\nplease in order to convert the boolean attribute ``read``, to a method. \n\nThe way metaclasses work, is that when a class is initialized, the Python\ninterpreter scans its own member attributes, and *then* runs the code of the\nmetaclass. In this case, what the metaclass does, is remove all those\noperations that have been defined with ``True``, like `read = True``, in order\nto make space for them to be inherited. The metaclass runs on class\ninitialization, whereas the [Python\nMRO](http://www.python.org/getit/releases/2.3/mro/) runs on runtime.\n\nFor this reason, when a handler class is defined, in order to provide\ninheritable behaviour for other handlers, unless it defines the operations as\nmethods, it needs to provide them as ``read = True``. This way its metaclass\nwill remove these attributes and make \"space\", for classes that inherit\nfrom it, to inherit the behaviour that these operations define. Of course the\nhandlers that inherit from a base handler, will need to first explicitly allow\nan operation, in case they want to inherit its functionality.\n \nSo the way to see it when building handlers:\n\n> Setting ``read = True``, means that the handler itself and handlers that\n> inherit from it, will inherit the ``read`` functionality, given that they\n> allow so.\n>\n> Setting ``read = False``, or not setting the ``read`` attribute at all, will block\n> the ``read`` functionality for the handler and handlers that inherit from it.\n \n### Request and response protocol\n\nHere we describe the responses (status code and response body) for all types of\nHTTP requests, successful or not.\n \n* GET\n * Singular\n * Successful:\n * Status code: 200\n * Response body: Dictionary\n * Errors:\n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n * Status code: 410 \n * Response body: Empty\n\n * Plural\n * Successful:\n * Status code: 200\n * Response body: List\n * Errors: \n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n\n* POST\n * Refers to single resource (Dictionary in request body)\n * Successful:\n * Status code: 200\n * Response body: Dictionary\n * Response body: ``null`` (Happens in the case when a\n database failure prevents the data object from being\n written to the database)\n * Errors: \n * Status code: 400\n * Response body: Dictionary\n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n * Status code: 422\n * Response body: Dictionary\n * Bulk (List in request body)\n * Successful\n * Status code: 200\n * Response body: List\n * Errors \n * Status code: 400\n * Response body: List (list items are dictionaries. Every\n dictionary should have an``index`` parameter which defines a\n zero-based index of the request body instance that was\n invalid)\n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n * Status code: 422\n * Response body: List (list items are dictionaries. Every\n dictionary should have an ``index`` parameter which defines a\n zero-based index of the request body instance that caused\n the error)\n\n* PUT\n * Singular\n * Successful\n * Status code: 200\n * Response body: Dictionary\n * Errors\n * Status code: 400\n * Response body: Dictionary\n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n * Status code: 410\n * Response body: Empty\n * Status code: 422\n * Response body: Dictionary\n * Plural\n * Successful \n * Status code: 200\n * Response body: List\n * Errors\n * Status code: 400\n * Response body: List (list items are dictionaries. Every\n dictionary should provide an ``id`` parameter which defines\n the ``id`` of the (model) instance that was invalid)\n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n * Status code: 422\n * Response body: List (list items are dictionaries. Every\n dictionary should provide an ``id`` parameter which defines\n the ``id` of the (model) instance that caused the error)\n\n* DELETE\n * Singular\n * Successful\n * Status code: 200\n * Response body: Dictionary\n * Errors \n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n * Status code: 410\n * Response body: Empty\n * Status code: 422\n * Response body: Dictionary\n * Plural\n * Successful\n * Status code: 200\n * Response body: List\n * Errors\n * Status code: 403\n * Response body: Empty\n * Status code: 405\n * Response body: Empty\n * Status code: 422\n * Response body: List (list items are dictionaries. Every\n dictionary should provide an ``id`` parameter which defined\n the ``id`` of the (model) instance that caused the error)\n \n#### Note\nIn the presence of errors, if the response body is a dictionary or a list,\nevery error instance(*one* in the case of dictionary, *multiple* in the case of a\nlist) will contain the following keys:\n\n* ``errors``: It will be a dictionary of {``field``: [``error``]} pairs where possible,\n or a list of strings describing the errors.\n* ``type`` : Error type\n\nIn the case of a list of errors, every item will contain the key ``index``, or\n``id``, which will specify which request body item, or which data model instance caused the corresponding error.\n\n\n##### Examples\n\n``` python\n# Error response of a POST request for a single resource\n{\n \"errors\": {\n \"text\": [\n \"This field cannot be blank\"\n ]\n },\n \"type\": \"Validation Error\"\n}\n\n```\n\n``` python\n# Error response of a Bulk POST request\n[\n {\n \"index\": 0,\n \"errors\": {\n \"gender\": [\n \"Value u'bi' is not a valid choice.\"\n ], \n \"email\": [\n \"Invalid Email\"\n ]\n },\n \"type\": \"Validation Error\"\n },\n {\n \"index\": 3,\n \"errors\": {\n \"postcode\": [\n \"Invalid Postcode\"\n ]\n \"type\": \"Validation Error\"\n }\n }\n] \n\n```\n\n``` python\n# Error response of a plural DELETE request\n[\n {\n \"id\": 2,\n \"errors\": [\n \"Instance cannot be deleted\"\n ],\n \"type\": \"Unprocessable Entity Error\"\n },\n {\n \"id\": 4,\n \"errors\": [\n \"Instance cannot be deleted\"\n ],\n \"type\": \"Unprocessable Entity Error\"\n }\n] \n\n``` \n", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/stargazer/django-icetea", "keywords": "rest,restful,api,crud", "license": "WTFPL", "maintainer": null, "maintainer_email": null, "name": "django-icetea", "package_url": "https://pypi.org/project/django-icetea/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/django-icetea/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://github.com/stargazer/django-icetea" }, "release_url": "https://pypi.org/project/django-icetea/0.5/", "requires_dist": null, "requires_python": null, "summary": "REST API Framework", "version": "0.5" }, "last_serial": 789836, "releases": { "0.3": [ { "comment_text": "", "digests": { "md5": "cf005ec42d2e7e75d1b6378d7a8ebe7f", "sha256": "434dddff6ae1a4b4dc14b11d4e0209632da563c9b62e67b943072fcbaa7a4d6f" }, "downloads": -1, "filename": "django-icetea-0.3.tar.gz", "has_sig": false, "md5_digest": "cf005ec42d2e7e75d1b6378d7a8ebe7f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32724, "upload_time": "2012-04-24T18:00:29", "url": "https://files.pythonhosted.org/packages/fe/ec/1ff62a3d93b8b9978d51d6e6bbb96b14560aef78d122046c2147833e720f/django-icetea-0.3.tar.gz" } ], "0.3.2": [ { "comment_text": "", "digests": { "md5": "7e6865282aa69eeade48dce9920a345b", "sha256": "77da5a9e08cc2356ecdf0a027001a93c25d64cb20eae04c2ce72dd8e1a22ebc8" }, "downloads": -1, "filename": "django-icetea-0.3.2.tar.gz", "has_sig": false, "md5_digest": "7e6865282aa69eeade48dce9920a345b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33111, "upload_time": "2012-05-06T11:49:25", "url": "https://files.pythonhosted.org/packages/17/60/be42c8437ad3c237e9378b61705e62ef646b3be47e9c95a576563b7b3ca4/django-icetea-0.3.2.tar.gz" } ], "0.3.3": [ { "comment_text": "", "digests": { "md5": "cedf280dc4ac859eac9a5f2f57a31c38", "sha256": "e99a86266efa8aeeb2a3ee187315013c7e4cfa8d577bd8ff2e7ef0c622674ca0" }, "downloads": -1, "filename": "django-icetea-0.3.3.tar.gz", "has_sig": false, "md5_digest": "cedf280dc4ac859eac9a5f2f57a31c38", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33259, "upload_time": "2012-05-08T12:15:34", "url": "https://files.pythonhosted.org/packages/a7/4b/50b4bc75c9460c981640405ca05e4d42bf3c092bb3a4c7650c4becdca192/django-icetea-0.3.3.tar.gz" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "240d603aeadc00b5948fe55c39addb88", "sha256": "b7d5ec62af985742747700b6f1661fd346c78330bbcc1b889977fc1c3a6fc8ad" }, "downloads": -1, "filename": "django-icetea-0.4.1.tar.gz", "has_sig": false, "md5_digest": "240d603aeadc00b5948fe55c39addb88", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35652, "upload_time": "2012-07-23T20:22:06", "url": "https://files.pythonhosted.org/packages/80/8f/7f8f5acc2f7ded7a37ca989e585cfdb1f8889187b6418116a57f88b3f822/django-icetea-0.4.1.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "9de4f8ebb26b19a10abf23cdb48a7957", "sha256": "5433c85ab50d16f9a340029fd84099ef0ef09c69094f5882f0cde5c22f155205" }, "downloads": -1, "filename": "django-icetea-0.5.tar.gz", "has_sig": false, "md5_digest": "9de4f8ebb26b19a10abf23cdb48a7957", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33836, "upload_time": "2013-01-22T16:14:37", "url": "https://files.pythonhosted.org/packages/d0/b9/a1fe7817f8559920240c76c175aa29556251fb644f42f1a2f77cbd1fdeb7/django-icetea-0.5.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "9de4f8ebb26b19a10abf23cdb48a7957", "sha256": "5433c85ab50d16f9a340029fd84099ef0ef09c69094f5882f0cde5c22f155205" }, "downloads": -1, "filename": "django-icetea-0.5.tar.gz", "has_sig": false, "md5_digest": "9de4f8ebb26b19a10abf23cdb48a7957", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33836, "upload_time": "2013-01-22T16:14:37", "url": "https://files.pythonhosted.org/packages/d0/b9/a1fe7817f8559920240c76c175aa29556251fb644f42f1a2f77cbd1fdeb7/django-icetea-0.5.tar.gz" } ] }