{ "info": { "author": "Cedric RICARD", "author_email": "ricard33@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP" ], "description": "My Django Tweaks\n========================\n|travis|_ |pypi|_ |requiresio|_ |codecov|_\n\n--------------\n\nA collection of various tools used to improve `Django `_\nand `Django Rest Framework `_ developments\n\nThis project is intended to contain a set of improvements/addons for Django and DRF that I've used and/or developed during using DRF.\n\nCredits\n-------\n\nSome of these tweaks have been partially (or largely...) inspired by others toolsets.\nI've tried to always respect the code licensing (all third party code were under MIT\nlicensing or equivalent).\n\nHere follows the list of contributors:\n\n* `DRF Tweaks `_\n\n\nCurrent tweaks\n--------------\n* `Extended Serializers`_\n* `Auto filtering and ordering`_\n* `Pagination without counts`_\n* `Versioning extensions`_\n* `Autodocumentation`_ - extension for `Django Rest Swagger `_\n* `Autooptimization`_\n* `Linting database usage`_\n* `Bulk edit API mixin`_\n\n\n--------------------\n\nExtended Serializers\n--------------------\n\nThere are a few improvements that the standard DRF Serializer could benefit from. Each improvement, how to use it\n& rationale for it is described in the sections below.\n\nOne-step validation\n~~~~~~~~~~~~~~~~~~~\n\nStandard serializer is validating the data in three steps:\n* field-level validation (required, blank, validators)\n* custom field-level validation (method validate_fieldname(...))\n* custom general validation (method validate(...))\n\nSo for example if you have a serializer with 4 required fields: first_name, email, password & confirm_password and you\npass data without first_name and with wrong confirm_password, you'll get first the error for first_name, and then, after\nyou correct it you'll get error for confirm_password, instead of getting both errors at once. This results in bad user\nexperience, and that's why we've changed all validation to be run in one step.\n\nValidation of our Serializer runs all three phases, and merges errors from all of them. However if a given field\ngenerated an error on two different stages, it returns the error only from the former one.\n\nWhen using our Serializer/ModelSerializer, when writing \"validate\" method, you need to remember that given field may\nnot be in a dictionary, so the validation must be more sophisticated:\n\n.. code:: python\n\n def validate(self, data):\n errors = {}\n # wrong - password & confirm_password may raise KeyError\n if data[\"password\"] != data[\"confirm_password\"]:\n errors[\"confirm_password\"] = [_(\"This field must match\")]\n\n # correct\n if data.get(\"password\") != data.get(\"confirm_password\"):\n errors[\"confirm_password\"] = [_(\"Passwords\")]\n\n if errors:\n raise serializer.ValidationError(errors)\n\n return data\n\n\nMaking fields required\n~~~~~~~~~~~~~~~~~~~~~~\n\nStandard ModelSerializer is taking the \"required\" state from the corresponding Model field. To make not-required model\nfield required in serializer, you have to declare it explicitly on serializer, so if the field first_name is not\nrequired in the model, you need to do:\n\n.. code:: python\n\n class MySerializer(serializers.ModelSerializer):\n first_name = serializers.CharField(..., required=True)\n\n\nThis is quite annoying when you have to do it often, that's why our ModelSerializer allows you to override this by simple\nspecifying the list of fields you want to make required:\n\n.. code:: python\n\n from my_django_tweaks.serializers import ModelSerializer\n\n class MySerializer(ModelSerializer):\n required_fields = [\"first_name\"]\n\n\nCustom errors\n~~~~~~~~~~~~~\n\nOur serializers provide a simple way to override blank & required error messages, by either specifying default error for\nall fields or specifying error for specific field. To each error message \"fieldname\" is passed as format parameter.\nExample:\n\n.. code:: python\n\n from my_django_tweaks.serializers import ModelSerializer\n\n class MySerializer(ModelSerializer):\n required_error = blank_error = \"{fieldname} is required\"\n custom_required_errors = custom_blank_errors = {\n \"credit_card_number\": \"You make me a saaaad Panda.\"\n }\n\n\nPassing context to subserializers\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nRationale: In DRF context is not passed to sub-serializers. So for example, in the standard serializer, you will have \"request\" in the context for the main object (say, Message), but the context for a sub-serializer (say, sender's Account) context will be empty. To workaround this you could for example re-initialize sub-serializers on the serializer's init, or instead of using a sub-serializer use a SerializerMethodField and initialize a sub-serializer inside it, etc. The problem is described here: https://github.com/encode/django-rest-framework/issues/2471\n\nOur serializers includes a mechanism to pass context to sub-serializers, workarounding the problem stated above.\n\nIf for any reason you are using SerializerMethodField with a Serializer inside, and you want to pass context, use pass_context method to filter the fields & include fields properly.\n\n.. code:: python\n\n from my_django_tweaks.serializers import pass_context\n\n class SomeSerializer(Serializer):\n some_field = serializers.SerializerMethodField()\n\n def get_some_field(self, obj):\n return OtherSerializer(obj, context=pass_context(\"some_field\", self.context)).data\n\n\n**WARNING: passing context may cause some unexpected behaviours, since sub-serializer will start receive the main context (and earlier they were not getting it).**\n\n\nControl over serialized fields\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOur serializers provide control over serialized fields. It may be useful in following cases:\n* You have quite heavy serializer (many fields, foreign keys, db calls, etc.), that you need in one place, but in the\nother place you just need some basic data from it - say just name & id. You could provide separate serializer for such\ncase, or even separate endpoint, but it would be easier if the client can have control over which fields get serialized.\n* You have some fields that should be serialized only for some state of the serialized object, and not for other.\n\nBoth things can be achieved with our serializer. By default they check if the \"fields\" were passed in the context or if\n\"fields\" were passed as a GET parameter (in such case \"request\" must be present in the context), but you can define\ncustom behaviour by overriding the followin method in the Serializer:\n\n.. code:: python\n\n def get_fields_for_serialization(self, fields): # fields must be in (\"fields\", \"include_fields\")\n return {\"name\", \"id\"}\n\nThis works also with sub-serializers (using context-passing). Here is an example usage:\n\n.. code::\n\n https://your.url?fields=some_field,other_field,nested_serializer__some_field,nested_serializer__other_field\n\n\nMaking fields available only on demand\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nRationale: it is a good practice to minimize the number of APIs, by making them as generic as possible. This however creates a performance problem when the amount of data being serialized grows by including sub-serializers (which can include sub-serializers themselves). Using control over serialized fields, as described above should be sufficient. However, in practice this mechanism will not be used as frequent as it should. That's why we've introduced another mechanism: on demand fields. Those are fields, specified in the serializer, that will be returned only if requested either by passing their name in \"fields\" (see the previous chapter) or in \"include_fields\" parameter.\n\n\n.. code:: python\n\n class MySerializer(serializers.ModelSerializer):\n some_subserializer = OtherSerializer()\n\n class Meta:\n model = MyModel\n fields = [\"some_property\", \"some_subserializer\"]\n on_demand_fields = [\"some_subserializer\"]\n\n.. code::\n\n https://your.url?include_fields=some_subserializer\n\n\nAuto filtering and ordering\n---------------------------\n\nRationale\n~~~~~~~~~\n\nThere are nice OrderingFilter and DjangoFilterBackend backends in place, however sorting and filtering fields have to be declared explicitly, which is sometimes time consuming. That's why we've created a decorator that allows to sort & filter (with some extra lookup methods by default) by all the indexed fields present in model and in serializer class (as non write-only). Non-indexed fields may also be added to sorting & filtering, but it must be done explicitly - the idea is, that ordering or filtering by non-indexed field is not optimal from the DB perspective, so if the field is not included in sorting/filtering you should rather create index on it than declare it explicitly.\n\nDecorator works with explicitly defined FilterBackends, as well as with explicitly defined ordering_fields, filter_fields or filter_class. In order to work, it requires ModelSerializer (obtainable either serializer_class or get_serializer_class), from which fields & model class are extracted.\n\nUsage\n~~~~~\n\n.. code:: python\n\n @autofilter()\n class SomeAPI(...):\n serializer_class = SomeModelSerializer\n\n # it works well with autodoc:\n @autodoc() # autodoc should be before autofilter, so it operates on the result from autofilter\n @autofilter()\n class SomeAPI(...):\n serializer_class = SomeModelSerializer\n\n # you can add some extra fields to sort or filter\n @autofilter(extra_filter=(\"non_indexed_field\", ), extra_ordering=(\"non_indexed_field\", ), exclude_fields=(\"some_field\", ))\n class SomeAPI(...):\n serializer_class = SomeModelSerializer\n ordering_fields = (\"other_non_indexed_field\", )\n filter_fields = (\"other_non_indexed_field\", )\n\n # it works also when you have a custom filter_class set\n class SomeFilter(filters.FilterSet):\n class Meta:\n model = SomeModel\n fields = (\"non_indexed_field\", )\n\n @autofilter()\n class SomeAPI(...):\n serializer_class = SomeModelSerializer\n filter_class = SomeFilter\n\n\nPagination without counts\n-------------------------\n\nRationale\n~~~~~~~~~\n\nCalling \"count\" each time a queryset gets paginated is inefficient - especialy for large datasets. Moreover, in most\ncases it is unnecessary to have counts (for example for endless scrolls). The fastest pagination in such case is\nCursorPaginator, however it is not as easy to use as LimitOffsetPaginator/PageNumberPaginator and does not allow\nsorting.\n\nUsage\n~~~~~\n\n.. code:: python\n\n from my_django_tweaks.pagination import NoCountsLimitOffsetPagination\n from my_django_tweaks.pagination import NoCountsPageNumberPagination\n\n\nUse it as standard pagination - the only difference is that it does not return \"count\" in the dictionary. Page indicated\nby \"next\" may be empty. Next page url is present if the current page size is as requested - if it contains less items\nthen requested, it means we're on the last page.\n\nNoCountsLimitOffsetPagination\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nA limit/offset based pagination, without performing counts. For example:\n* http://api.example.org/accounts/?limit=100 - will return first 100 items\n* http://api.example.org/accounts/?offset=400&limit=100 - will returns 100 items starting from 401th\n* http://api.example.org/accounts/?offset=-50&limit=100 - will return first 50 items\n\nHTML is not handled (no get_html_context).\n\nPros:\n* no counts\n* easier to use than cursor pagination (especially if you need sorting)\n* works with angular ui-scroll (which requires negative offsets)\n\nCons:\n* skip is a relatively slow operation, so this paginator is not as fast as cursor paginator when you use large offsets\n\nNoCountsPageNumberPagination\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nA standard page number pagination, without performing counts.\n\nHTML is not handled (no get_html_context).\n\nPros:\n* no counts\n* easier to use than cursor pagination (especially if you need sorting)\n\nCons:\n* skip is a relatively slow operation, so this paginator is not as fast as cursor paginator when you use large page\nnumbers\n\nVersioning extensions\n---------------------\n\nRationale\n~~~~~~~~~\n\nDRF provides a nice `versioning mechanism `_, however there are two things that could be more automated,\nand this is the point of this extension:\n\n* Handling deprecation & obsoletion: when you don't have control over upgrading client app, it is best to set the deprecation/obsoletion mechanism at the very beginning of your project - something that will start reminding a user that he is using old app and he should update it, or in case of obsolition - information, that this app is outdated and it must be upgraded in order to use it. This extension adds warning to header if the API version client is using is deprecated and responds with 410: Gone error when the API version is obsolete.\n* Choosing serializer. In DRF you have to overwrite get_serializer_class to provide different serializers for different versions. This extension allows you to define just dictionary with it: versioning_serializer_classess. You may still override get_serializer_class however if you choose to.\n\nConfiguration\n~~~~~~~~~~~~~\n\nIn order to make deprecation warning work, you need to add DeprecationMiddleware to MIDDLEWARE or MIDDLEWARE_CLASSESS\n(depends on django version you're using):\n\n.. code:: python\n\n # django >= 1.10\n MIDDLEWARE (\n ...\n \"my_django_tweaks.versioning.DeprecationMiddleware\"\n )\n\nIt is highly recommended to add DEFAULT_VERSION along with DEFAUlt_VERSIONINg_CLASS to DRF settings:\n\n.. code:: python\n\n REST_FRAMEWORK = {\n ...\n \"DEFAULT_VERSIONING_CLASS\": \"rest_framework.versioning.AcceptHeaderVersioning\",\n \"DEFAULT_VERSION\": \"1\",\n }\n\n\nBy default the DEFAULT_VERSION is None, which will in effect work as \"latest\" - it is safer to make passing newer\nversion explicitly.\n\nApiVersionMixin\n~~~~~~~~~~~~~~~\nUse this as first in inheritance chain when creating own API classes, so for example:\n\n.. code:: python\n\n class MyApi(ApiVersionMixin, GenericApiView):\n ...\n\n\nReturns serializer depending on versioning_serializer_classess and version:\n\n.. code:: python\n\n versioning_serializer_classess = {\n 1: \"x\",\n 2: \"x\",\n }\n\n\nYou can set custom deprecated/obsolete versions on the class-level\n\n.. code:: python\n\n CUSTOM_DEPRECATED_VERSION = X\n CUSTOM_OBSOLETE_VERSION = Y\n\n\nIt can be also configured on the settings level as a fixed version\n\n.. code:: python\n\n API_DEPRECATED_VERSION = X\n API_OBSOLETE_VERSION = Y\n\n\nor as an offset - for example:\n\n.. code:: python\n\n API_VERSION_DEPRECATION_OFFSET = 6\n API_VERSION_OBSOLETE_OFFSET = 10\n\n\nOffset is calculated using the highest version number, only if versioning_serializer_classess is defined:\n\n.. code:: python\n\n deprecated = max(self.versioning_serializer_classess.keys() - API_VERSION_DEPRECATION_OFFSET)\n obsolete = max(self.versioning_serializer_classess.keys() - API_VERSION_OBSOLETE_OFFSET)\n\n\nIf neither is set, deprecation/obsolete will not work. Only the first applicable setting is taken into account\n(in the order as presented above).\n\nAutodocumentation\n-----------------\n\nRationale\n~~~~~~~~~\n\n[Django Rest Swagger][drs] is a awesome tool that generates swagger documentation out of your DRF API. There is however\none deficiency - it does not offer any hooks that would allow you to automaticaly generate some additional documentation.\nFor example, if you want pagination parameters to be visible in the docs, you'd have to set it explicitly:\n\n.. code:: python\n\n class SomeAPi(ListAPIView):\n def get(...):\n \"\"\" page_number -- optional, page number \"\"\"\n\n\nYou may also want to generate some part of description based on some fields in API and make it change automatically\neach time you update them. Django Rest Swagger does not offer any hooks for that, and that is why this extension was\ncreated.\n\nSince there are no hooks available to add custom documentation, this extension is made in a form of class decorator,\nthat creates facade for each API method (get/post/patch/put - defined on the Autodoc class level) and creates a\ndocstring for them based on original docstring (if present) & applicable Autodoc classess.\n\nUsage & Configuration\n~~~~~~~~~~~~~~~~~~~~~\n\n.. code:: python\n\n @autodoc(\"List or create an account\")\n class SomeApi(ApiVersionMixin, ListCreateAPIView):\n ...\n\n # you can skip certain classes:\n @autodoc(\"Base docstring\", skip_classess=[PaginationAutodoc])\n\n # or add certain classess:\n @autodoc(\"Base docstring\", add_classess=[CustomAutodoc])\n\n # you can also override autodoc classess - this one cannot be used with skip_classess or add_classess:\n @autodoc(\"Base docstring\", classess=[PaginationAutodoc])\n\n\nAvailable Classess\n~~~~~~~~~~~~~~~~~~\n\nClassess are applied in the same order they are defined.\n\nBaseInfo\n********\n\nThis one is adding basic info (the one passed to the decorator itself), as well as custom text or yaml if defined,\nas in following examples:\n\n.. code:: python\n\n @autodoc(\"some caption\")\n class SomeApi(RetrieveUpdateAPIView):\n\n @classmethod\n def get_custom_get_doc(cls):\n return \"custom get doc\"\n\n @classmethod\n def get_custom_patch_doc_yaml(cls):\n return \"some yaml\"\n\n\nPagination\n**********\n\nThis one is adding parameters to \"get\" method in swagger in following format:\n\n.. code:: python\n\n page_number -- optional, page number\n page_size -- optional, page size\n\n\nIt adds all \"\\*_query_param\" from pagination class, as long as they have name defined, so for standard\nPageNumberPagination, that has page_size_query_param defined as None it will not be enclodes.\n\nIf default pagination class is defined, and you don't want it to be added, you can simply:\n\n.. code:: python\n\n class SomeClassWithoutPagination(RetrieveAPIView):\n pagination_class = None\n\n\nOrderingAndFiltering\n********************\n\nThis one is adding ordering & filtering information, based on OrderingFilter and DjangoFilterBackend for \"get\" method in swagger in following format:\n.. code::\n\n Sorting:\n usage: ?ordering=FIELD_NAME,-OTHER_FIELD_NAME\n available fields: id, first_name, last_name, date_of_birth\n\n Filtering:\n id: exact, __gt, __gte, __lt, __lte, __in, __isnull\n date_of_birth: exact, __gt, __gte, __lt, __lte, __in\n first_name: exact, __gt, __gte, __lt, __lte, __in, __icontains, __istartswith\n last_name: exact, __gt, __gte, __lt, __lte, __in, __icontains, __istartswith\n\n\nVersioning\n**********\n\nAutodoc for versioning - applied only when ApiVersionMixin is present and the decorated class is using\nrest_framework.versioning.AcceptHeaderVersioning and has versioning_serializer_classess defined. It adds all available\nversions to a swagger, so you can make a call from it using different API versions.\n\nPermissions\n***********\n\nAutodoc for permissions - adds permission class name & it's docstring to the method description.\n\n\nAdding custom classess\n~~~~~~~~~~~~~~~~~~~~~~\n\nCustom class should inherit from AutodocBase:\n\n.. code:: python\n\n class CustomAutodoc(AutodocBase):\n applies_to = (\"get\", \"post\", \"put\", \"patch\", \"delete\")\n\n @classmethod\n def _generate_yaml(cls, documented_cls, method_name):\n return \"\" # your implementation goes here\n\n @classmethod\n def _generate_text(cls, documented_cls, base_doc, method_name):\n return \"\" # your implementation goes here\n\n\nAutooptimization\n----------------\n\nYou can discover select related & prefetch related structure just by using AutoOptimizeMixin mixin. It takes fields & include_fields parameters, so if the related object is not going to be serialized, it will not be queried.\n\nThe structure is discovered based on serializer that is retrieved by get_serializer_class() with context obtained by get_serializer_context().\n\nThe optimization discovery is run in get_queryset, and it obtains serializer_class thorugh get_serializer_class.\n\n.. code:: python\n\n from my_django_tweaks.optimizator import AutoOptimizeMixin\n\n class MyAPI(AutoOptimizeMixin, ListCreateAPIView):\n serializer_class = SerializerClassWithManyLevelsOfSubserializers\n\n\nLinting database usage\n----------------------\n\nRationale\n~~~~~~~~~\n\nIt is important to make sure your web application is efficient and can work well under high load. The ``my_django_tweaks.test_utils.DatabaseAccessLintingApiTestCase`` can detect two potential gotchas:\n* large number of queries: print out warnings and raise an Exception based on thresholds on query counts set via project settings,\n* multi-table `select_for_update`: raise an Exception if the code tries to lock more than one table, unless it's a combination whitelisted in project settings.\n\n\nUsage & Configuration\n~~~~~~~~~~~~~~~~~~~~~\n\n.. code:: python\n\n from django.urls import reverse_lazy\n from my_django_tweaks.test_utils import DatabaseAccessLintingApiTestCase\n\n class TestFoo(DatabaseAccessLintingApiTestCase):\n def test_bar():\n # the linter will raise an Exception or print out a warning when it detects one of gotchas, as configured in settings\n self.client.post(reverse_lazy(\"some-post-url\"))\n # ...\n\nTo configure, set in your settings:\n\nTEST_QUERY_NUMBER_SHOW_WARNING\n Print out a warning if the count of queries in a single view reaches this threshold. Default: 10.\n\nTEST_QUERY_NUMBER_RAISE_ERROR\n Raise an Exception if the count of queries in a single view reaches this threshold. Default: 15.\n\nTEST_QUERY_NUMBER_PRINT_QUERIES\n Set to True to print out queries stack (with tracebacks). Default: False.\n\nTEST_QUERY_COUNTER_IGNORE_PATTERNS\n Exclude some queries from counting. Set as a list of texts containing regular expressions. Default: [\".*SAVEPOINT.*\"].\n\nTEST_SELECT_FOR_UPDATE_LIMITER_ENABLED\n Raise an Exception if the view tries to select_for_update more than one table. Default: False.\n\nTEST_SELECT_FOR_UPDATE_WHITELISTED_TABLE_SETS\n Allow to perform select_for_update on specified combinations of multiple tables. Default: []. Example: [(\"table1\", \"table2\"), ...]\n\nTo override those settings in tests, use the ``django.test.override_settings`` decorator\n(check the `docs `_).\n\nTo temporarily disable query counting (for example, not to count queries executed in Celery tasks), use `TestQueryCounter.freeze`:\n\n.. code:: python\n\n with TestQueryCounter.freeze():\n # the query counter will ignore all queries executed within this block\n\n\nBulk edit API mixin\n-------------------\nBulk edit/create/delete can be easily enabled for any model. All you need to have are details and list serializer.\n\n.. code:: python\n\n class BulkEditAPI(BulkEditAPIMixin, ListCreateAPIView):\n queryset = SomeModel.objects.all()\n serializer_class = SomeModelSerializer\n details_serializer_class = SomeModelDetailsSerializer\n BULK_EDIT_ALLOW_DELETE_ITEMS = True # default: False\n BULK_EDIT_MAX_ITEMS = 10 # API will not be limited if set to None\n\n\nCreating\n~~~~~~~~\nTo create a new object, **\"temp_id\"** key must be passed along with object's data.\nTemporary id is required to match validation errors to appropriate object.\nThe create method uses the **serializer_class** serializer to create new objects.\nThe view must implement the **create** method to be able to add new items - if the method is not present, the view will\nstill work but adding new items will not be allowed.\n\nEditing\n~~~~~~~\nThe **details_serializer_class** is used for editing items. If one item does not pass validation, none of the items will\nbe editied.\n\nDeleting\n~~~~~~~~\nThe **BULK_EDIT_ALLOW_DELETE_ITEMS** flag must be set to **True** to enable deleting objects. To mark that object\nshould be deleted, add **\"delete_object\": True** next to it's **id** in the payload, for example:\n\n.. code:: json\n\n [{\"id\": 1, \"delete_object\": True}]\n\n\n\n.. |travis| image:: https://secure.travis-ci.org/ricard33/my_django_tweaks.svg?branch=master\n.. _travis: http://travis-ci.org/ricard33/my_django_tweaks?branch=master\n\n.. |pypi| image:: https://img.shields.io/pypi/v/my_django_tweaks.svg\n.. _pypi: https://pypi.python.org/pypi/my_django_tweaks\n\n.. |codecov| image:: https://img.shields.io/codecov/c/github/ricard33/my_django_tweaks/master.svg\n.. _codecov: http://codecov.io/github/ricard33/my_django_tweaks?branch=master\n\n.. |requiresio| image:: https://requires.io/github/ricard33/my_django_tweaks/requirements.svg?branch=master\n.. _requiresio: https://requires.io/github/ricard33/my_django_tweaks/requirements/?branch=master\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/ricard33/my_django_tweaks", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "my-django-tweaks", "package_url": "https://pypi.org/project/my-django-tweaks/", "platform": "", "project_url": "https://pypi.org/project/my-django-tweaks/", "project_urls": { "Homepage": "https://github.com/ricard33/my_django_tweaks" }, "release_url": "https://pypi.org/project/my-django-tweaks/0.0.3/", "requires_dist": [ "six (>=1.10)", "djangorestframework (>=3.7.0)", "django-rest-swagger (>0.3.9)", "django-filter (>=1.1.0)" ], "requires_python": "", "summary": "A collection of various tools used to improve Django and Django Rest Framework developments", "version": "0.0.3" }, "last_serial": 5322227, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "91339024977d1e071aa7b3c09296e9e5", "sha256": "ed763a6853a2c13ad02b57a8f0f44d8b0fe82dfd4e459207d8aef48a5b4b5dad" }, "downloads": -1, "filename": "my_django_tweaks-0.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "91339024977d1e071aa7b3c09296e9e5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 49641, "upload_time": "2019-05-17T13:36:04", "url": "https://files.pythonhosted.org/packages/cb/d7/8e2fac1c75467771c5edd0cd28ae55b82f79dabaa07f3c558bd2a79d9dc7/my_django_tweaks-0.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6bc2621e30aa8b7c264f82e37bd123d0", "sha256": "cb59f15e64294b5c987e2b9f0647657a743cd57fa16d4c2bfa2b86952dbc134e" }, "downloads": -1, "filename": "my_django_tweaks-0.0.1.tar.gz", "has_sig": false, "md5_digest": "6bc2621e30aa8b7c264f82e37bd123d0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31246, "upload_time": "2019-05-17T13:36:07", "url": "https://files.pythonhosted.org/packages/27/cf/fb270f218e303d123fb89921dfba4f6746943a350bbe92ca4e259c27d9d5/my_django_tweaks-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "67ae889a20d85e7fd955706cf4c5fc63", "sha256": "a1668b17f4d22f5922eb767100c14a497ef8ed1f444360bb79380c35f29f1f60" }, "downloads": -1, "filename": "my_django_tweaks-0.0.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "67ae889a20d85e7fd955706cf4c5fc63", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 50353, "upload_time": "2019-05-21T09:52:30", "url": "https://files.pythonhosted.org/packages/22/26/89dfdcb37d358720386804df9ea0f661fbc74c60408c97bf936aca1e6e43/my_django_tweaks-0.0.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "76f2dc15d404537c87cbee41c1373ece", "sha256": "199ff7af09e92d4d3e36524f5165bb250de923a596c8708852a76c6c054fc5d2" }, "downloads": -1, "filename": "my_django_tweaks-0.0.2.tar.gz", "has_sig": false, "md5_digest": "76f2dc15d404537c87cbee41c1373ece", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32377, "upload_time": "2019-05-21T09:52:32", "url": "https://files.pythonhosted.org/packages/33/1a/eca486d8a6d19c9faa35a0afb3df867395612fb95567dfdf1e80ecda5bca/my_django_tweaks-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "330e923fb713c693cf937bd61446afd1", "sha256": "48f7e9fd04928f7246c1492556db827f31151f810549b015d146727f51907d99" }, "downloads": -1, "filename": "my_django_tweaks-0.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "330e923fb713c693cf937bd61446afd1", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 51351, "upload_time": "2019-05-27T12:55:49", "url": "https://files.pythonhosted.org/packages/0b/68/ada691e2dd06f808c924953add58f83ddc2074f11000d6380596d7b41e7c/my_django_tweaks-0.0.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c3fa32e490d3cd5f617765e7851480db", "sha256": "784d1e4c2609322620e7fa59347cfcca556a554fcf8a6540849b55c5fe646b85" }, "downloads": -1, "filename": "my_django_tweaks-0.0.3.tar.gz", "has_sig": false, "md5_digest": "c3fa32e490d3cd5f617765e7851480db", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32376, "upload_time": "2019-05-27T12:55:51", "url": "https://files.pythonhosted.org/packages/9f/11/8d80837a2f862ed5dbab02636b69755e4cf675fb987b75490d839a0e158f/my_django_tweaks-0.0.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "330e923fb713c693cf937bd61446afd1", "sha256": "48f7e9fd04928f7246c1492556db827f31151f810549b015d146727f51907d99" }, "downloads": -1, "filename": "my_django_tweaks-0.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "330e923fb713c693cf937bd61446afd1", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 51351, "upload_time": "2019-05-27T12:55:49", "url": "https://files.pythonhosted.org/packages/0b/68/ada691e2dd06f808c924953add58f83ddc2074f11000d6380596d7b41e7c/my_django_tweaks-0.0.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c3fa32e490d3cd5f617765e7851480db", "sha256": "784d1e4c2609322620e7fa59347cfcca556a554fcf8a6540849b55c5fe646b85" }, "downloads": -1, "filename": "my_django_tweaks-0.0.3.tar.gz", "has_sig": false, "md5_digest": "c3fa32e490d3cd5f617765e7851480db", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32376, "upload_time": "2019-05-27T12:55:51", "url": "https://files.pythonhosted.org/packages/9f/11/8d80837a2f862ed5dbab02636b69755e4cf675fb987b75490d839a0e158f/my_django_tweaks-0.0.3.tar.gz" } ] }