Name | django-atris JSON |
Version |
2.0.3
JSON |
| download |
home_page | |
Summary | Django history logger that keeps track of changes on a global level. |
upload_time | 2023-11-14 15:33:47 |
maintainer | |
docs_url | None |
author | |
requires_python | >=3.8 |
license | Copyright (c) [2023] [Bogdan Andrei Pop] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
keywords |
django
database
history
logging
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
django-atris
============
Django-atris stores a snapshot of each tracked model on create/update/delete operations.
Snapshots are available in a global form as well.
This app requires:
- Django:
- for Django < 1.9 please use django-atris < 1.0.0
- for Django < 1.10 please use django-atris < 1.2.0
- for Django > 2.0.0 please use django-atris > 1.2.1
- for Django >= 3.2 < 4.0 please use django-atris = 2.0.1
- for Django >= 3.2.19 <= 4.2.6 please use django-atris = 2.0.2
- for Django >= 4 < 5 please use django-atris = 2.0.3
- Postgresql
- Python:
- for django-atris < 2.0.0 please use Python >= 2.7 or Python >= 3.4 (after Django 2)
- for django-atris >= 2.0.0 please use Python >= 3.6
- for django-atris == 2.0.1 please use Python >= 3.7
- for django-atris >= 2.0.2 please use Python >= 3.8
Integration guide
-----------------
In order to use the app you must do the following:
* Add 'atris' to INSTALLED_APPS in settings
* You MUST have 'django.contrib.postgres' in your INSTALLED_APPS
* Add 'atris.middleware.LoggingRequestMiddleware' to MIDDLEWARE in order for the app to be able to get the user which made the changes
* Put a field (named as you wish) in the model class that you desire to track that contains a HistoryLogging instance ( i.e. history = HistoryLogging() )
Additional features:
- Additional data -
if you wish to store some additional data regarding
an instance of your model, you can do so by adding a
dict to your model, which contains said additional data.
After creating that dict, use it when instantiating the
HistoryLogging field::
additional_data = {'changed_from': 'djadmin'}
history = HistoryLogging(additional_data_param_name='additional_data')
- Exclude fields -
if you wish not to track some fields, all you need to do
is add a list to your model which contains the fields you
do not wish to track in a string format and use that list
when instantiating the HistoryLogging field::
exclude_fields = ['last_modified'] # as it would always appear to have been updated
history = HistoryLogging(excluded_fields_param_name='exclude_fields')
- Ignore changes by user -
if you wish not to track changes made by a specific user,
such as a user specially set up for smoke tests, you can declare
an additional field called however you like and pass it on
to the HistoryLogging object. This field must be a dictionary
that contains the keys 'user_ids' and 'user_names', both values
for these keys must be lists containing the appropriate information::
ignore_history_for_users = {'user_ids': [1010101], 'user_names': ['ignore_user']}
history = HistoryLogging(ignore_history_for_users='ignore_history_for_users')
- Interested related fields -
**(added in version 1.1.0)**
if you wish to add history for the objects related to a model
when the model changes, you can do so by declaring a list with the names of
the related fields names. This is applicable to 1-to-1, 1-to-many and
many-to-many fields::
poll = ForeignKey('Poll')
...
interested_related_fields = ['poll']
history = HistoryLogging(interested_related_fields='interested_related_fields')
Usage guide
-----------
After integrating the app in your own app, you can make use of it in several different ways.
For starters, the fields made available to you when inspecting a history instance are the following:
* content_type = django contenttype
* object_id = the model instance id that the history is kept of
* history_date = the date that the history instance was created
* history_user = the user that triggered the history instance (taken from middleware); For this string, the value it takes is prioritised in this order: fullname > email > username, if none are available it remains None.
* history_user_id = the id of the user that triggered the history instance (taken from middleware)
* history_type = type of history, +: Create, ~: Update, -:Delete (the method 'get_history_type_display()' gets you the string interpretation)
* data = JSON field, contains a snapshot (in the form of a dict) of the model instance that the history is being kept of, doesn't contain excluded fields nor additional data fields.
All field values are converted to strings. The values of foreign keys are represented by the object ID as a string. The values of ManyToManyFields are represented by a string
containing a comma-separated list of IDs.
**New in version 1.1.0: changed key of ForeignKey fields from <FK_FIELD_NAME>_id to <FK_FIELD_NAME>; added entry for many-to-many field**
* additional_data = JSON field, contains additional data of the model instance in the form of a dict
**NOTE #1**: A historical record will be generated only if there has been a change in the local model fields. *(New in version 1.1.0)*
**NOTE #2**: You may implement your own `HistoricalRecord` class and specify it in your project's
settings.py via `ATRIS_HISTORY_MODEL` as `<APP_NAME>.<MODEL_NAME>`. *(New in version 1.1.0)*
Example of usage in code:
* Classes we will use in example::
>>> class Foo(models.Model):
... field_1 = models.CharField(max_length=255)
... field_2 = models.IntField()
... last_modified = models.DateTimeField(auto_now=True)
... excluded_fields = ['last_modified']
... ignore_history_for_users = {
... 'user_ids': [1010101],
... 'user_names': ['ignore_user'],
... }
... history = HistoryLogging(
... excluded_fields='excluded_fields',
... ignore_history_for_users='ignore_history_for_users,
... )
>>> class Bar(models.Model):
... field_1 = models.CharField(max_length=255)
... field_2 = models.IntField()
... last_modified = models.DateTimeField(auto_now=True)
... fk_field = models.ForeignKey(Foo)
# setting this specifies the default value for your additional data
... additional_data = {'modified_from': 'code'}
... excluded_fields = ['last_modified']
... interested_related_fields = ['fk_field']
... history = HistoryLogging(
... 'additional_data',
... 'excluded_fields',
... interested_related_fields='interested_related_fields',
... )
>>> foo = Foo.objects.create(field_1='aaa', field_2=0)
>>> foo_1 = Foo.objects.create(field_1='bar', field_2=1)
* Get all the history information for the first model instance that was just created::
>>> foo.history.all()
[<HistoricalRecord: Create foo id=1>]
* Get all the history information for the Foo model::
>>> Foo.history.all()
[<HistoricalRecord: Create foo id=1>, <HistoricalRecord: Create foo id=2>]
* Get the global history information (ordered by history_date desc)::
>>> from atris.models import HistoricalRecord
>>> HistoricalRecord.objects.all()
[<HistoricalRecord: Create bar id=1>, <HistoricalRecord: Create foo id=2>]
* Get all the history information for the Bar model::
Bar.objects.create(field_1='aaa', field_2=0, fk_field=foo)
>>> Bar.history.all()
[<HistoricalRecord: Create bar id=1>]
* Get the global history information again::
>>> HistoricalRecord.objects.all()
[<HistoricalRecord: Update foo id=1>, <HistoricalRecord: Create bar id=1>,
<HistoricalRecord: Create foo id=2>, <HistoricalRecord: Create foo id=1>]
Note that an "update" historical record has been created for `foo` when a
bar object was linked to it.
* Another way of getting history for a model::
>>> HistoricalRecord.objects.by_model(Foo)
[<HistoricalRecord: Update foo id=1>, <HistoricalRecord: Create foo id=1>,
<HistoricalRecord: Create foo id=2>]
* Another way of getting history for an instance of a model useful for deleted objects that you still want a history for::
>>> HistoricalRecord.objects.by_model_and_model_id(Foo, foo.id)
[<HistoricalRecord: Update foo id=1>, <HistoricalRecord: Create foo id=1>]
* Get the snapshot of the bar instance created::
>>> bar.history.first().data
{'field_1': 'aaa', 'field_2': '0', 'fk_field': '1'}
* Get the additional data of the bar instance::
>>> bar.history.first().additional_data
{'modified_from': 'code'}
* If you have a situation where the user cannot be determined from the django middleware you can also do the following::
>>> bar.history_user = User(username='username') # where User is the django User model
>>> # Some other changes to bar so that a historical record will be generated.
>>> bar.save()
>>> bar.history.first().history_user
'username'
* You can also mark a user such that the history for that user does not get saved. You can do so either by user name(KEEP IN MIND: user name is considered the full name or email or user name of the user instance associated with the history, depending on which is available first, in that order) or ID. You can use this to tell atris to ignore changes made by certain users such as a smoke test user::
>>> bar.history_user = User(username='ignore_user') # where User is the django User model
>>> bar.save()
>>> bar.history.filter(history_user='ignore_user').count()
0
Changelog
-----------
1.2.2:
* Django 1.10 compatible
1.3.0:
* Django 2 compatible
1.3.1:
* suppress approximate count. TODO
1.3.2:
* Django 2.1 compatible
1.3.3:
* Evaluate translation lazy translation text for a field's verbose name
1.3.4:
* Add support for Django 2.2
2.0.0:
* Dropped support for Django < 2.2 and Python < 3.6
* Fixed history generation issue after saving an instance for the first time after a new field was added to the model
- This issue was causing historical records to be generated when saving (without any changes) existing instances of tracked models
2.0.1:
* Dropped support for Python < 3.7
* Added support for Django 3.2
* Move away from setup.py to pyproject.toml
2.0.2:
* Dropped support for Django 2
* Added support for Django 4 (tested up to 4.2.6)
* Dropped support for python < 3.8
2.0.3:
* Dropped support for Django 3
* Extended support beyond 4.2.6 up to less than Django 5 (tested up to 4.2.7)
Raw data
{
"_id": null,
"home_page": "",
"name": "django-atris",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "django,database,history,logging",
"author": "",
"author_email": "Bogdan Andrei Pop <bpop2232@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/4d/65/820ec0eb7728afdee6e02d0e5f4c976603d56a3c3828f0a0a3874912341a/django-atris-2.0.3.tar.gz",
"platform": null,
"description": "django-atris\n============\n\nDjango-atris stores a snapshot of each tracked model on create/update/delete operations.\n\nSnapshots are available in a global form as well.\n\nThis app requires:\n\n- Django:\n - for Django < 1.9 please use django-atris < 1.0.0\n - for Django < 1.10 please use django-atris < 1.2.0\n - for Django > 2.0.0 please use django-atris > 1.2.1\n - for Django >= 3.2 < 4.0 please use django-atris = 2.0.1\n - for Django >= 3.2.19 <= 4.2.6 please use django-atris = 2.0.2\n - for Django >= 4 < 5 please use django-atris = 2.0.3\n- Postgresql\n- Python:\n - for django-atris < 2.0.0 please use Python >= 2.7 or Python >= 3.4 (after Django 2)\n - for django-atris >= 2.0.0 please use Python >= 3.6\n - for django-atris == 2.0.1 please use Python >= 3.7\n - for django-atris >= 2.0.2 please use Python >= 3.8\n\nIntegration guide\n-----------------\n\nIn order to use the app you must do the following:\n\n* Add 'atris' to INSTALLED_APPS in settings\n* You MUST have 'django.contrib.postgres' in your INSTALLED_APPS\n* Add 'atris.middleware.LoggingRequestMiddleware' to MIDDLEWARE in order for the app to be able to get the user which made the changes\n* Put a field (named as you wish) in the model class that you desire to track that contains a HistoryLogging instance ( i.e. history = HistoryLogging() )\n\nAdditional features:\n\n- Additional data -\n if you wish to store some additional data regarding\n an instance of your model, you can do so by adding a\n dict to your model, which contains said additional data.\n After creating that dict, use it when instantiating the\n HistoryLogging field::\n\n additional_data = {'changed_from': 'djadmin'}\n history = HistoryLogging(additional_data_param_name='additional_data')\n\n- Exclude fields -\n if you wish not to track some fields, all you need to do\n is add a list to your model which contains the fields you\n do not wish to track in a string format and use that list\n when instantiating the HistoryLogging field::\n\n exclude_fields = ['last_modified'] # as it would always appear to have been updated\n history = HistoryLogging(excluded_fields_param_name='exclude_fields')\n\n- Ignore changes by user -\n if you wish not to track changes made by a specific user,\n such as a user specially set up for smoke tests, you can declare\n an additional field called however you like and pass it on\n to the HistoryLogging object. This field must be a dictionary\n that contains the keys 'user_ids' and 'user_names', both values\n for these keys must be lists containing the appropriate information::\n\n ignore_history_for_users = {'user_ids': [1010101], 'user_names': ['ignore_user']}\n history = HistoryLogging(ignore_history_for_users='ignore_history_for_users')\n\n- Interested related fields -\n **(added in version 1.1.0)**\n\n if you wish to add history for the objects related to a model\n when the model changes, you can do so by declaring a list with the names of\n the related fields names. This is applicable to 1-to-1, 1-to-many and\n many-to-many fields::\n\n poll = ForeignKey('Poll')\n ...\n interested_related_fields = ['poll']\n history = HistoryLogging(interested_related_fields='interested_related_fields')\n\nUsage guide\n-----------\n\nAfter integrating the app in your own app, you can make use of it in several different ways.\n\nFor starters, the fields made available to you when inspecting a history instance are the following:\n\n* content_type = django contenttype\n* object_id = the model instance id that the history is kept of\n* history_date = the date that the history instance was created\n* history_user = the user that triggered the history instance (taken from middleware); For this string, the value it takes is prioritised in this order: fullname > email > username, if none are available it remains None.\n* history_user_id = the id of the user that triggered the history instance (taken from middleware)\n* history_type = type of history, +: Create, ~: Update, -:Delete (the method 'get_history_type_display()' gets you the string interpretation)\n* data = JSON field, contains a snapshot (in the form of a dict) of the model instance that the history is being kept of, doesn't contain excluded fields nor additional data fields.\n All field values are converted to strings. The values of foreign keys are represented by the object ID as a string. The values of ManyToManyFields are represented by a string\n containing a comma-separated list of IDs.\n\n **New in version 1.1.0: changed key of ForeignKey fields from <FK_FIELD_NAME>_id to <FK_FIELD_NAME>; added entry for many-to-many field**\n* additional_data = JSON field, contains additional data of the model instance in the form of a dict\n\n**NOTE #1**: A historical record will be generated only if there has been a change in the local model fields. *(New in version 1.1.0)*\n\n**NOTE #2**: You may implement your own `HistoricalRecord` class and specify it in your project's\nsettings.py via `ATRIS_HISTORY_MODEL` as `<APP_NAME>.<MODEL_NAME>`. *(New in version 1.1.0)*\n\nExample of usage in code:\n\n* Classes we will use in example::\n\n >>> class Foo(models.Model):\n ... field_1 = models.CharField(max_length=255)\n ... field_2 = models.IntField()\n ... last_modified = models.DateTimeField(auto_now=True)\n ... excluded_fields = ['last_modified']\n ... ignore_history_for_users = {\n ... 'user_ids': [1010101],\n ... 'user_names': ['ignore_user'],\n ... }\n ... history = HistoryLogging(\n ... excluded_fields='excluded_fields',\n ... ignore_history_for_users='ignore_history_for_users,\n ... )\n\n >>> class Bar(models.Model):\n ... field_1 = models.CharField(max_length=255)\n ... field_2 = models.IntField()\n ... last_modified = models.DateTimeField(auto_now=True)\n ... fk_field = models.ForeignKey(Foo)\n # setting this specifies the default value for your additional data\n ... additional_data = {'modified_from': 'code'}\n ... excluded_fields = ['last_modified']\n ... interested_related_fields = ['fk_field']\n ... history = HistoryLogging(\n ... 'additional_data',\n ... 'excluded_fields',\n ... interested_related_fields='interested_related_fields',\n ... )\n\n >>> foo = Foo.objects.create(field_1='aaa', field_2=0)\n >>> foo_1 = Foo.objects.create(field_1='bar', field_2=1)\n\n* Get all the history information for the first model instance that was just created::\n\n >>> foo.history.all()\n [<HistoricalRecord: Create foo id=1>]\n\n* Get all the history information for the Foo model::\n\n >>> Foo.history.all()\n [<HistoricalRecord: Create foo id=1>, <HistoricalRecord: Create foo id=2>]\n\n* Get the global history information (ordered by history_date desc)::\n\n >>> from atris.models import HistoricalRecord\n >>> HistoricalRecord.objects.all()\n [<HistoricalRecord: Create bar id=1>, <HistoricalRecord: Create foo id=2>]\n\n* Get all the history information for the Bar model::\n\n Bar.objects.create(field_1='aaa', field_2=0, fk_field=foo)\n >>> Bar.history.all()\n [<HistoricalRecord: Create bar id=1>]\n\n* Get the global history information again::\n\n >>> HistoricalRecord.objects.all()\n [<HistoricalRecord: Update foo id=1>, <HistoricalRecord: Create bar id=1>,\n <HistoricalRecord: Create foo id=2>, <HistoricalRecord: Create foo id=1>]\n\n Note that an \"update\" historical record has been created for `foo` when a\n bar object was linked to it.\n\n* Another way of getting history for a model::\n\n >>> HistoricalRecord.objects.by_model(Foo)\n [<HistoricalRecord: Update foo id=1>, <HistoricalRecord: Create foo id=1>,\n <HistoricalRecord: Create foo id=2>]\n\n* Another way of getting history for an instance of a model useful for deleted objects that you still want a history for::\n\n >>> HistoricalRecord.objects.by_model_and_model_id(Foo, foo.id)\n [<HistoricalRecord: Update foo id=1>, <HistoricalRecord: Create foo id=1>]\n\n* Get the snapshot of the bar instance created::\n\n >>> bar.history.first().data\n {'field_1': 'aaa', 'field_2': '0', 'fk_field': '1'}\n\n* Get the additional data of the bar instance::\n\n >>> bar.history.first().additional_data\n {'modified_from': 'code'}\n\n* If you have a situation where the user cannot be determined from the django middleware you can also do the following::\n\n >>> bar.history_user = User(username='username') # where User is the django User model\n >>> # Some other changes to bar so that a historical record will be generated.\n >>> bar.save()\n >>> bar.history.first().history_user\n 'username'\n\n* You can also mark a user such that the history for that user does not get saved. You can do so either by user name(KEEP IN MIND: user name is considered the full name or email or user name of the user instance associated with the history, depending on which is available first, in that order) or ID. You can use this to tell atris to ignore changes made by certain users such as a smoke test user::\n\n >>> bar.history_user = User(username='ignore_user') # where User is the django User model\n >>> bar.save()\n >>> bar.history.filter(history_user='ignore_user').count()\n 0\n\n\n\nChangelog\n-----------\n\n1.2.2:\n * Django 1.10 compatible\n\n1.3.0:\n * Django 2 compatible\n\n1.3.1:\n * suppress approximate count. TODO\n\n1.3.2:\n * Django 2.1 compatible\n\n1.3.3:\n * Evaluate translation lazy translation text for a field's verbose name\n\n1.3.4:\n * Add support for Django 2.2\n\n2.0.0:\n * Dropped support for Django < 2.2 and Python < 3.6\n * Fixed history generation issue after saving an instance for the first time after a new field was added to the model\n - This issue was causing historical records to be generated when saving (without any changes) existing instances of tracked models\n\n2.0.1:\n * Dropped support for Python < 3.7\n * Added support for Django 3.2\n * Move away from setup.py to pyproject.toml\n\n2.0.2:\n * Dropped support for Django 2\n * Added support for Django 4 (tested up to 4.2.6)\n * Dropped support for python < 3.8\n\n2.0.3:\n * Dropped support for Django 3\n * Extended support beyond 4.2.6 up to less than Django 5 (tested up to 4.2.7)\n",
"bugtrack_url": null,
"license": "Copyright (c) [2023] [Bogdan Andrei Pop] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
"summary": "Django history logger that keeps track of changes on a global level.",
"version": "2.0.3",
"project_urls": {
"Homepage": "https://github.com/pbs/django-atris"
},
"split_keywords": [
"django",
"database",
"history",
"logging"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "54a36e4f0cf1515098a3768fe2af6e777fe00d192954c13a9bf99fa5cf8f230f",
"md5": "268c9fbd74b5823f7dcc7af0c5dfdefd",
"sha256": "9314f8921059d0c0a9fa34b1616a1a16c19f425ce84560ff190e42a94678bbd8"
},
"downloads": -1,
"filename": "django_atris-2.0.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "268c9fbd74b5823f7dcc7af0c5dfdefd",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 29697,
"upload_time": "2023-11-14T15:33:45",
"upload_time_iso_8601": "2023-11-14T15:33:45.916274Z",
"url": "https://files.pythonhosted.org/packages/54/a3/6e4f0cf1515098a3768fe2af6e777fe00d192954c13a9bf99fa5cf8f230f/django_atris-2.0.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "4d65820ec0eb7728afdee6e02d0e5f4c976603d56a3c3828f0a0a3874912341a",
"md5": "83ece13cfbe11bf8cbfee2fee9903a67",
"sha256": "4934a62bb663ceb9505066edbbb889aefac2cf7dd41b64d39982407f4644d329"
},
"downloads": -1,
"filename": "django-atris-2.0.3.tar.gz",
"has_sig": false,
"md5_digest": "83ece13cfbe11bf8cbfee2fee9903a67",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 24819,
"upload_time": "2023-11-14T15:33:47",
"upload_time_iso_8601": "2023-11-14T15:33:47.354015Z",
"url": "https://files.pythonhosted.org/packages/4d/65/820ec0eb7728afdee6e02d0e5f4c976603d56a3c3828f0a0a3874912341a/django-atris-2.0.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-14 15:33:47",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "pbs",
"github_project": "django-atris",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"test_requirements": [
{
"name": "Django",
"specs": [
[
"==",
"4.2.7"
]
]
},
{
"name": "psycopg2-binary",
"specs": [
[
"==",
"2.9.9"
]
]
},
{
"name": "pytest",
"specs": [
[
"==",
"7.4.2"
]
]
},
{
"name": "pytest-cov",
"specs": [
[
"==",
"4.1.0"
]
]
},
{
"name": "pytest-django",
"specs": [
[
"==",
"4.5.2"
]
]
},
{
"name": "pytest-html",
"specs": [
[
"==",
"4.0.2"
]
]
},
{
"name": "pytest-mock",
"specs": [
[
"==",
"3.11.1"
]
]
}
],
"lcname": "django-atris"
}