django-building-blocks


Namedjango-building-blocks JSON
Version 0.1.0 PyPI version JSON
download
home_pagehttps://github.com/kaoslabsinc/django-building-blocks
SummaryAbstract Django base models (and model class factories) to act as building blocks for rapid development of Django database models
upload_time2022-05-02 22:02:53
maintainer
docs_urlNone
authorKaos Labs Inc.
requires_python
licenseBSD-3-Clause
keywords django abstract models factories
VCS
bugtrack_url
requirements wheel sphinx sphinx-rtd-theme None
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django Building Blocks

Abstract Django base models (and model class factories) to act as building blocks for rapid development of Django
database models

## Quick start

```shell
pip install django-building-blocks
```

## Rationale

`django-building-blocks` provides common design patterns and behaviours that repeat themselves during the development of
django projects. The main aim of this package is to provide common interfaces for the development of django models. This
portion of this library is inspired by Kevin Stone's excellent blog
post [Django Model Behaviors](https://blog.kevinastone.com/django-model-behaviors). The secondary aim of this library is
to provide interfaces and mixins for Django admin classes, so you can add functionality to your admin pages really fast,
without the need to Google solutions and look at Stackoverflow for answers.

By using this library you can create models in such a way to make their fields standard across your entire project and
all your projects. For example:

```python
class BlogPost(
    HasNameFactory.as_abstract_model(),
    HasDescription,
    HasHTMLBody,
    Publishable,
    models.Model
):
    pass
```

Note that we did not need to add anything to the body of the above model. This model is entirely composed of abstract
models (and abstract model factories - more on this later). If you have another model:

```python
class WebPage(
    HasNameFactory.as_abstract_model(),
    HasAutoSlugFactory.as_abstract_model(),
    HasHTMLBody,
    Publishable,
    models.Model
):
    pass
```

Note the similarity between the two models. Now instead of having to code the fields, associated properties and
behaviors on each model individually, you can reuse them infinite times, and keep them standard across your project (and
all your projects).

The admin class for `BlogPost` would look something like this:

```python
@admin.register(BlogPost)
class BlogPostAdmin(
    PublishableAdmin,
    admin.ModelAdmin
):
    list_display = (
        *HasNameAdmin.list_display,
        *PublishableAdmin.list_display,
        ...
    )
    ...
```

Notice how we have composed each element in the admin using a concept called Admin Blocks. Each of the abstract classes
in this library, have an associated Admin block class (which looks like a normal admin class, but usually isn't meant to
be inherited), that enables you to define admin for their inheritors in a standard way. For example in the case
of `Publishable`, you would probably like to show the publication status and date in `list_display`. Instead of having
to remember to include both fields in all your admins, you can just include the Admin block in the way showed above and
have the fields show up in the list table for all the inheritors of `Publishable`.

## Details and classes provided

Note: Check the docstring under each class for their documentation.

### Abstract models

[Django Model Behaviors](https://blog.kevinastone.com/django-model-behaviors) describes a design pattern for developing
django models that makes them compositional. For example, your project might have the ability to post blog
posts (`BlogPost`), and each post goes through 3 stages: Draft, Published, and Archived. In your project, your users
might also have the ability to post shorter status updates (`StatusUpdate`), and you'd like those status updates to also
go through a similar publishing pipeline. Furthermore, both `BlogPost` and `StatusUpdate` are timestamped with the date
and time they are published.

As a seasoned developer, instead of coding the functionality on both models, you would create an abstract model, let's
call it `Publishable`, and have both `BlogPost` and `StatusUpdate` inherit from it. Now you have abstracted away common
functionality between your models, into the `Publishable` interface/abstract model. Not only this is DRY, but also if
you like to make updates to how your publishing pipeline works, you can just update `Publishable` and both `BlogPost`
and `StatusUpdate` would get updated with the new pipeline.

`django-building-blocks` provides a number of such abstract models:

- [`HasUUID`](building_blocks/models/abstracts.py)
- [`Archivable`](building_blocks/models/abstracts.py)
- [`Publishable`](building_blocks/models/abstracts.py)

Note: if you're coming from v0.0.x, take a look at this [migration file](example/sample/migrations/0014_int_status.py)
to help you with writing migrations for your `Archivable` or `Publishable` models, as in v0.1.0 they have switched to
use an integer status field.

### Abstract model factories

Abstract model factories enable you to create abstract models on the fly. Abstract model factories enable you to modify
the inherited fields dynamically from the same base class. For example, you might have a model that will have an `email`
field. You can use `HasEmailFactory.as_abstract_model()` that returns an abstract model that can be inherited from. Now
say you have another model that also has a email, but the email here is an optional field (`blank=True`). Instead of
creating a whole new abstract model (like `HasOptionalEmail`), you can inherit
from `HasEmailFactory.as_abstract_model(optional=True)` which will return an abstract model with the same `email` field,
but this time the `email` field is optional.

Abstract model factories provided:

- [`HasNameFactory`](building_blocks/models/factories.py)
- [`HasEmailFactory`](building_blocks/models/factories.py)
- [`HasDescriptionFactory`](building_blocks/models/factories.py)
- [`HasCoverPhotoFactory`](building_blocks/models/factories.py)
- [`HasIconFactory`](building_blocks/models/factories.py)
- [`HasUserFactory`](building_blocks/models/factories.py)
- [`HasAutoCodeFactory`](building_blocks/models/factories.py)
- [`HasAutoSlugFactory`](building_blocks/models/factories.py)

You can create your own abstract model factory by inheriting
from `building_blocks.models.factories.AbstractModelFactory`. Check some of the implementations for an example.

### Mixin model classes

- [`HasInitials`](building_blocks/models/mixins.py)
- [`HasAutoFields`](building_blocks/models/mixins.py)

### Admin block classes

The following admins (called admin blocks) aren't usually meant to be inherited by your model's admin class. Instead,
each field in the admin is used to create your desired admin class. For example:

```python
# example/sample/admin.py

@admin.register(ArchivableHasUUID)
class ArchivableHasUUIDAdmin(
    ArchivableAdmin,  # Inheritable admin to add common functionality. More on this later. 
    admin.ModelAdmin
):
    search_fields = (
        *HasUUIDAdmin.search_fields,  # AdminBlock to assist in shaping the admin
    )
    list_display = (
        *HasUUIDAdmin.list_display,
        *ArchivableAdmin.list_display,
    )
    list_filter = (
        *ArchivableAdmin.list_filter,
    )

    readonly_fields = (
        *HasUUIDAdmin.readonly_fields,
        *ArchivableAdmin.readonly_fields,
    )
    fieldsets = (
        *HasUUIDAdmin.fieldsets,
        *ArchivableAdmin.fieldsets,
    )
```

As its name suggests, the model `ArchivableHasUUID` inherits from both `Archivable` and `HasUUID` and thus has fields
form both. With admin blocks you can create `ArchivableHasUUIDAdmin` without mentioning individual fields from each
class, adding to the conciseness of your code. It'll also make it hard to miss a field since the AdminBlock has the
default and recommended fields for each admin field.

Available Admin Blocks:

- [`HasUUIDAdmin`](building_blocks/admin/admin.py)
- [`ArchivableAdmin`](building_blocks/admin/admin.py)
- [`PublishableAdmin`](building_blocks/admin/admin.py)
- [`HasInitialsAdmin`](building_blocks/admin/admin.py)
- [`HasNameAdmin`](building_blocks/admin/admin.py)
- [`HasEmailAdmin`](building_blocks/admin/admin.py)
- [`HasDescriptionAdmin`](building_blocks/admin/admin.py)
- [`HasCoverPhotoAdmin`](building_blocks/admin/admin.py)
- [`HasIconAdmin`](building_blocks/admin/admin.py)
- [`HasUserAdmin`](building_blocks/admin/admin.py)
- [`HasAutoSlugAdmin`](building_blocks/admin/admin.py)
- [`TimeStampedModelAdmin`](building_blocks/admin/admin.py)

### Inheritable Admin block classes

Some Admin block classes are also meant to be inherited by your admin class. The usually provide functionality such as
common admin actions to your admin.

- [`ArchivableAdmin`](building_blocks/admin/admin.py)
- [`PublishableAdmin`](building_blocks/admin/admin.py)

Please note that the majority of the inheritable admins
use [django-object-actions](https://github.com/crccheck/django-object-actions) to enable admin actions on objects'
`admin:change` pages. To enable this functionality add `'django_object_actions'` to your `INSTALLED_APPS` so your
project can find the templates from `django_object_actions` which are used in rendering the buttons for the actions.

### Mixin Admin classes

- [`EditReadonlyAdminMixin`](building_blocks/admin/mixins.py)
- [`PrepopulateSlugAdminMixin`](building_blocks/admin/mixins.py)
- [`DjangoObjectActionsPermissionsMixin`](building_blocks/admin/mixins.py)
- [`AreYouSureActionsAdminMixin`](building_blocks/admin/mixins.py)

### Admin inline mixins

When you need some fields on an inline admin to be readonly only for editing (equivalent of `EditReadonlyAdminMixin`),
you have to split the interface into two inlines, one for adding which doesn't show any objects, and one for listing
them, which has the readonly fields defined. The following classes facilitate this design pattern:

- [`AddInlineMixin`](building_blocks/admin/inlines.py)
- [`ListInlineMixin`](building_blocks/admin/inlines.py)
- [`ReadOnlyInlineMixin`](building_blocks/admin/inlines.py)

## HTML render utilities

They are used in conjunction with `@admin.display` to render html such as anchor tags, or images on the admin.

- [`json_field_pp`](building_blocks/admin/utils.py)
- [`render_element`](building_blocks/admin/utils.py)
- [`render_img`](building_blocks/admin/utils.py)
- [`render_anchor`](building_blocks/admin/utils.py)

## Forms

### `UnrequiredFieldsForm` and `unrequire_form`

Make fields that are usually required in a model form, be not required. Used in conjunction with `HasAutoFields`, since
the fields that aren't required are auto set. Also used in conjunction with `EditReadonlyAdminMixin` to allow manual
setting of a field upon creation.

## Fields

- [`CaseInsensitiveFieldMixin`](building_blocks/fields.py)
- [`ToLowerCaseFieldMixin`](building_blocks/fields.py)
- [`LowerCaseCharField`](building_blocks/fields.py)

## Development and Testing

### IDE Setup

Add the `example` directory to the `PYTHONPATH` in your IDE to avoid seeing import warnings in the `tests` modules. If
you are using PyCharm, this is already set up.

### Running the Tests

Install requirements

```
pip install -r requirements.txt
```

For local environment

```
pytest
```

For all supported environments

```
tox
```



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/kaoslabsinc/django-building-blocks",
    "name": "django-building-blocks",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "django,abstract,models,factories",
    "author": "Kaos Labs Inc.",
    "author_email": "keyvan@keyvanm.com",
    "download_url": "https://files.pythonhosted.org/packages/f7/80/2eed2fa16f5641b656bb0f8df7169d9fc1c6f80f6a4893dc7d09f768a032/django-building-blocks-0.1.0.tar.gz",
    "platform": null,
    "description": "# Django Building Blocks\n\nAbstract Django base models (and model class factories) to act as building blocks for rapid development of Django\ndatabase models\n\n## Quick start\n\n```shell\npip install django-building-blocks\n```\n\n## Rationale\n\n`django-building-blocks` provides common design patterns and behaviours that repeat themselves during the development of\ndjango projects. The main aim of this package is to provide common interfaces for the development of django models. This\nportion of this library is inspired by Kevin Stone's excellent blog\npost [Django Model Behaviors](https://blog.kevinastone.com/django-model-behaviors). The secondary aim of this library is\nto provide interfaces and mixins for Django admin classes, so you can add functionality to your admin pages really fast,\nwithout the need to Google solutions and look at Stackoverflow for answers.\n\nBy using this library you can create models in such a way to make their fields standard across your entire project and\nall your projects. For example:\n\n```python\nclass BlogPost(\n    HasNameFactory.as_abstract_model(),\n    HasDescription,\n    HasHTMLBody,\n    Publishable,\n    models.Model\n):\n    pass\n```\n\nNote that we did not need to add anything to the body of the above model. This model is entirely composed of abstract\nmodels (and abstract model factories - more on this later). If you have another model:\n\n```python\nclass WebPage(\n    HasNameFactory.as_abstract_model(),\n    HasAutoSlugFactory.as_abstract_model(),\n    HasHTMLBody,\n    Publishable,\n    models.Model\n):\n    pass\n```\n\nNote the similarity between the two models. Now instead of having to code the fields, associated properties and\nbehaviors on each model individually, you can reuse them infinite times, and keep them standard across your project (and\nall your projects).\n\nThe admin class for `BlogPost` would look something like this:\n\n```python\n@admin.register(BlogPost)\nclass BlogPostAdmin(\n    PublishableAdmin,\n    admin.ModelAdmin\n):\n    list_display = (\n        *HasNameAdmin.list_display,\n        *PublishableAdmin.list_display,\n        ...\n    )\n    ...\n```\n\nNotice how we have composed each element in the admin using a concept called Admin Blocks. Each of the abstract classes\nin this library, have an associated Admin block class (which looks like a normal admin class, but usually isn't meant to\nbe inherited), that enables you to define admin for their inheritors in a standard way. For example in the case\nof `Publishable`, you would probably like to show the publication status and date in `list_display`. Instead of having\nto remember to include both fields in all your admins, you can just include the Admin block in the way showed above and\nhave the fields show up in the list table for all the inheritors of `Publishable`.\n\n## Details and classes provided\n\nNote: Check the docstring under each class for their documentation.\n\n### Abstract models\n\n[Django Model Behaviors](https://blog.kevinastone.com/django-model-behaviors) describes a design pattern for developing\ndjango models that makes them compositional. For example, your project might have the ability to post blog\nposts (`BlogPost`), and each post goes through 3 stages: Draft, Published, and Archived. In your project, your users\nmight also have the ability to post shorter status updates (`StatusUpdate`), and you'd like those status updates to also\ngo through a similar publishing pipeline. Furthermore, both `BlogPost` and `StatusUpdate` are timestamped with the date\nand time they are published.\n\nAs a seasoned developer, instead of coding the functionality on both models, you would create an abstract model, let's\ncall it `Publishable`, and have both `BlogPost` and `StatusUpdate` inherit from it. Now you have abstracted away common\nfunctionality between your models, into the `Publishable` interface/abstract model. Not only this is DRY, but also if\nyou like to make updates to how your publishing pipeline works, you can just update `Publishable` and both `BlogPost`\nand `StatusUpdate` would get updated with the new pipeline.\n\n`django-building-blocks` provides a number of such abstract models:\n\n- [`HasUUID`](building_blocks/models/abstracts.py)\n- [`Archivable`](building_blocks/models/abstracts.py)\n- [`Publishable`](building_blocks/models/abstracts.py)\n\nNote: if you're coming from v0.0.x, take a look at this [migration file](example/sample/migrations/0014_int_status.py)\nto help you with writing migrations for your `Archivable` or `Publishable` models, as in v0.1.0 they have switched to\nuse an integer status field.\n\n### Abstract model factories\n\nAbstract model factories enable you to create abstract models on the fly. Abstract model factories enable you to modify\nthe inherited fields dynamically from the same base class. For example, you might have a model that will have an `email`\nfield. You can use `HasEmailFactory.as_abstract_model()` that returns an abstract model that can be inherited from. Now\nsay you have another model that also has a email, but the email here is an optional field (`blank=True`). Instead of\ncreating a whole new abstract model (like `HasOptionalEmail`), you can inherit\nfrom `HasEmailFactory.as_abstract_model(optional=True)` which will return an abstract model with the same `email` field,\nbut this time the `email` field is optional.\n\nAbstract model factories provided:\n\n- [`HasNameFactory`](building_blocks/models/factories.py)\n- [`HasEmailFactory`](building_blocks/models/factories.py)\n- [`HasDescriptionFactory`](building_blocks/models/factories.py)\n- [`HasCoverPhotoFactory`](building_blocks/models/factories.py)\n- [`HasIconFactory`](building_blocks/models/factories.py)\n- [`HasUserFactory`](building_blocks/models/factories.py)\n- [`HasAutoCodeFactory`](building_blocks/models/factories.py)\n- [`HasAutoSlugFactory`](building_blocks/models/factories.py)\n\nYou can create your own abstract model factory by inheriting\nfrom `building_blocks.models.factories.AbstractModelFactory`. Check some of the implementations for an example.\n\n### Mixin model classes\n\n- [`HasInitials`](building_blocks/models/mixins.py)\n- [`HasAutoFields`](building_blocks/models/mixins.py)\n\n### Admin block classes\n\nThe following admins (called admin blocks) aren't usually meant to be inherited by your model's admin class. Instead,\neach field in the admin is used to create your desired admin class. For example:\n\n```python\n# example/sample/admin.py\n\n@admin.register(ArchivableHasUUID)\nclass ArchivableHasUUIDAdmin(\n    ArchivableAdmin,  # Inheritable admin to add common functionality. More on this later. \n    admin.ModelAdmin\n):\n    search_fields = (\n        *HasUUIDAdmin.search_fields,  # AdminBlock to assist in shaping the admin\n    )\n    list_display = (\n        *HasUUIDAdmin.list_display,\n        *ArchivableAdmin.list_display,\n    )\n    list_filter = (\n        *ArchivableAdmin.list_filter,\n    )\n\n    readonly_fields = (\n        *HasUUIDAdmin.readonly_fields,\n        *ArchivableAdmin.readonly_fields,\n    )\n    fieldsets = (\n        *HasUUIDAdmin.fieldsets,\n        *ArchivableAdmin.fieldsets,\n    )\n```\n\nAs its name suggests, the model `ArchivableHasUUID` inherits from both `Archivable` and `HasUUID` and thus has fields\nform both. With admin blocks you can create `ArchivableHasUUIDAdmin` without mentioning individual fields from each\nclass, adding to the conciseness of your code. It'll also make it hard to miss a field since the AdminBlock has the\ndefault and recommended fields for each admin field.\n\nAvailable Admin Blocks:\n\n- [`HasUUIDAdmin`](building_blocks/admin/admin.py)\n- [`ArchivableAdmin`](building_blocks/admin/admin.py)\n- [`PublishableAdmin`](building_blocks/admin/admin.py)\n- [`HasInitialsAdmin`](building_blocks/admin/admin.py)\n- [`HasNameAdmin`](building_blocks/admin/admin.py)\n- [`HasEmailAdmin`](building_blocks/admin/admin.py)\n- [`HasDescriptionAdmin`](building_blocks/admin/admin.py)\n- [`HasCoverPhotoAdmin`](building_blocks/admin/admin.py)\n- [`HasIconAdmin`](building_blocks/admin/admin.py)\n- [`HasUserAdmin`](building_blocks/admin/admin.py)\n- [`HasAutoSlugAdmin`](building_blocks/admin/admin.py)\n- [`TimeStampedModelAdmin`](building_blocks/admin/admin.py)\n\n### Inheritable Admin block classes\n\nSome Admin block classes are also meant to be inherited by your admin class. The usually provide functionality such as\ncommon admin actions to your admin.\n\n- [`ArchivableAdmin`](building_blocks/admin/admin.py)\n- [`PublishableAdmin`](building_blocks/admin/admin.py)\n\nPlease note that the majority of the inheritable admins\nuse [django-object-actions](https://github.com/crccheck/django-object-actions) to enable admin actions on objects'\n`admin:change` pages. To enable this functionality add `'django_object_actions'` to your `INSTALLED_APPS` so your\nproject can find the templates from `django_object_actions` which are used in rendering the buttons for the actions.\n\n### Mixin Admin classes\n\n- [`EditReadonlyAdminMixin`](building_blocks/admin/mixins.py)\n- [`PrepopulateSlugAdminMixin`](building_blocks/admin/mixins.py)\n- [`DjangoObjectActionsPermissionsMixin`](building_blocks/admin/mixins.py)\n- [`AreYouSureActionsAdminMixin`](building_blocks/admin/mixins.py)\n\n### Admin inline mixins\n\nWhen you need some fields on an inline admin to be readonly only for editing (equivalent of `EditReadonlyAdminMixin`),\nyou have to split the interface into two inlines, one for adding which doesn't show any objects, and one for listing\nthem, which has the readonly fields defined. The following classes facilitate this design pattern:\n\n- [`AddInlineMixin`](building_blocks/admin/inlines.py)\n- [`ListInlineMixin`](building_blocks/admin/inlines.py)\n- [`ReadOnlyInlineMixin`](building_blocks/admin/inlines.py)\n\n## HTML render utilities\n\nThey are used in conjunction with `@admin.display` to render html such as anchor tags, or images on the admin.\n\n- [`json_field_pp`](building_blocks/admin/utils.py)\n- [`render_element`](building_blocks/admin/utils.py)\n- [`render_img`](building_blocks/admin/utils.py)\n- [`render_anchor`](building_blocks/admin/utils.py)\n\n## Forms\n\n### `UnrequiredFieldsForm` and `unrequire_form`\n\nMake fields that are usually required in a model form, be not required. Used in conjunction with `HasAutoFields`, since\nthe fields that aren't required are auto set. Also used in conjunction with `EditReadonlyAdminMixin` to allow manual\nsetting of a field upon creation.\n\n## Fields\n\n- [`CaseInsensitiveFieldMixin`](building_blocks/fields.py)\n- [`ToLowerCaseFieldMixin`](building_blocks/fields.py)\n- [`LowerCaseCharField`](building_blocks/fields.py)\n\n## Development and Testing\n\n### IDE Setup\n\nAdd the `example` directory to the `PYTHONPATH` in your IDE to avoid seeing import warnings in the `tests` modules. If\nyou are using PyCharm, this is already set up.\n\n### Running the Tests\n\nInstall requirements\n\n```\npip install -r requirements.txt\n```\n\nFor local environment\n\n```\npytest\n```\n\nFor all supported environments\n\n```\ntox\n```\n\n\n",
    "bugtrack_url": null,
    "license": "BSD-3-Clause",
    "summary": "Abstract Django base models (and model class factories) to act as building blocks for rapid development of Django database models",
    "version": "0.1.0",
    "split_keywords": [
        "django",
        "abstract",
        "models",
        "factories"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ad26dbdf3c5dc8e7b6acadad63a52bad44dd8c082fc190bd0b5bfb7c67d78873",
                "md5": "7ceb87d1950e8a9c566db0079e209130",
                "sha256": "b8d7873bde0b0ed14438ecca271f06e3cb62d4e1511da8fb55b5b1cb8a9a0f1c"
            },
            "downloads": -1,
            "filename": "django_building_blocks-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "7ceb87d1950e8a9c566db0079e209130",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 30979,
            "upload_time": "2022-05-02T22:02:51",
            "upload_time_iso_8601": "2022-05-02T22:02:51.525359Z",
            "url": "https://files.pythonhosted.org/packages/ad/26/dbdf3c5dc8e7b6acadad63a52bad44dd8c082fc190bd0b5bfb7c67d78873/django_building_blocks-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f7802eed2fa16f5641b656bb0f8df7169d9fc1c6f80f6a4893dc7d09f768a032",
                "md5": "90882e8b9a34db8e13120a7b1dc26c0b",
                "sha256": "5f0fa507a185f82cda2d383a20fb55cd9db76630263a7a1ce7049b75707d62a2"
            },
            "downloads": -1,
            "filename": "django-building-blocks-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "90882e8b9a34db8e13120a7b1dc26c0b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 23919,
            "upload_time": "2022-05-02T22:02:53",
            "upload_time_iso_8601": "2022-05-02T22:02:53.292068Z",
            "url": "https://files.pythonhosted.org/packages/f7/80/2eed2fa16f5641b656bb0f8df7169d9fc1c6f80f6a4893dc7d09f768a032/django-building-blocks-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-05-02 22:02:53",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "kaoslabsinc",
    "github_project": "django-building-blocks",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "wheel",
            "specs": []
        },
        {
            "name": "sphinx",
            "specs": [
                [
                    "~=",
                    "5.1"
                ]
            ]
        },
        {
            "name": "sphinx-rtd-theme",
            "specs": []
        },
        {
            "name": null,
            "specs": []
        }
    ],
    "tox": true,
    "lcname": "django-building-blocks"
}
        
Elapsed time: 0.03331s