django-timestampable


Namedjango-timestampable JSON
Version 1.0.1 PyPI version JSON
download
home_pagehttps://github.com/xgeekshq/django-timestampable/
SummaryTimestamps and Soft Delete Patterns in Django Models
upload_time2021-04-08 16:30:19
maintainer
docs_urlNone
authorDaniel Pinto
requires_python>=3.6
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django Timestamps

Timestamps and Soft Delete Patterns in Django Models.

## Add "timestamps" to your INSTALLED_APPS settings

```python
INSTALLED_APPS = [
    # ...
    'timestamps',
]
```

## Usage

a) For models you want timestamps, just inherit Timestample:

```python
from timestamps.models import models, Timestample


class YourModel(Timestample):
    # your fields here ...

```

b) For models you want soft-delete, just inherit SoftDeletes:

```python
from timestamps.models import models, SoftDeletes


class YourModel(SoftDeletes):
    # your fields here ...

```

c) If you want both, you can also inherit from Model for shorter convenience:

```python
# to this:
from timestamps.models import models, Model  # explicit import Model (which contains timestamps)

# instead of:
# from django.db import models

# Explicitly import of "Model" is required
# because models.Model is the original from Django models module


class YourModel(Model):
    # your fields here ...

```


### Soft Deleting

- To get all objects without the deleted ones:

```queryset = YourModel.objects```

- To get only deleted objects:

```queryset = YourModel.objects_deleted```

- To get all the objects, including deleted ones:

```queryset = YourModel.objects_with_deleted```


#### To soft delete an instance

```python
some_model = MyModel.objects.first()
some_model.delete()  # or some_model.delete(hard=False)
```

#### To restore an instance

```python
some_model = MyModel.objects_deleted.first()
some_model.restore()
```

#### To hard delete an instance

```python
some_model = MyModel.objects.first()
some_model.delete(hard=True)
```

#### To bulk soft delete a queryset

```python
qs = MyModel.objects  # you can also apply filters to bulk delete a subset: qs = MyModel.objects.filter(...)
qs.delete()  # or qs.delete(hard=False)
```

#### To bulk hard delete a queryset

```python
qs = MyModel.objects  # ... bulk hard delete a subset: qs = MyModel.objects.filter(...)
qs.delete(hard=True)
```

#### To bulk restore a queryset

```python
qs = MyModel.objects_deleted  # ... bulk restore a subset: qs = MyModel.objects_deleted.filter(...)
qs.restore()  # or qs.delete(hard=False)
```


---


### If you're using DRF
you can use the SoftDeleteModelViewSet along with DefaultRouter present in this package
and you will have access to a complete CRUD on soft deleted objects as well.
This 2 classes allows you to expose:

Consider a Dummy Model that inherits from SoftDelete.

You can have all routes for CRUD operations on this model:


| VERB | URL PATH | DESCRIPTION |
| ---- | -------- | ----------- |
| GET | /dummy/ | gets all the objects, without the deleted ones |
| POST | /dummy/ | creates a new object |
| DELETE | /dummy/[?permanent=\<true,false>] | deletes all objects (or a filtered subject). allows hard-delete. Default: soft-delete |
| GET | /dummy/\<pk\>/ | gets a non-deleted object (by primary key) |
| POST | /dummy/\<pk\>/ | updates an object (by primary key) |
| PATCH | /dummy/\<pk\>/ | partial updates an object (by primary key) |
| DELETE | /dummy/\<pk\>/[?permanent=\<true,false>] | deletes a non-deleted object (by primary key) |
| PATCH | /dummy/restore/ | restore all objects (or a filtered subject) |
| PATCH | /dummy/\<pk\>/restore/ | restores a soft-deleted object (by primary key) |
| GET | /dummy/deleted/ | gets all deleted objects |
| GET | /dummy/deleted/\<pk\>/ | gets a deleted object (by primary key) |
| GET | /dummy/with-deleted/ | get all objects, deleted included |
| GET | /dummy/with-deleted/\<pk\>/ | get an object (by primary key) |
-----------------------------------

The query parameter "permanent" it's case-sensitive and can also be one of the values:

```python
truthful_options = [
    't', 'T',
    'y', 'Y', 'yes', 'Yes', 'YES',
    'true', 'True', 'TRUE',
    'on', 'On', 'ON',
    '1', 1,
    True
]
```

```python
falsely_options = [
    'f', 'F',
    'n', 'N', 'no', 'No', 'NO',
    'false', 'False', 'FALSE',
    'off', 'Off', 'OFF',
    '0', 0,
    'null',
    False
]
```

#### How to expose all CRUD operations

```python
# dummy/views.py
from timestamps.drf import viewsets  # instead of: from rest_framework import viewsets
from .models import Dummy
from .serializers import DummySerializer


class DummyModelViewSet(viewsets.ModelViewSet):
    queryset = Dummy.objects.all()
    serializer_class = DummySerializer

```

````python
# dummy/urls.py
from timestamps.drf import routers  # instead of: from rest_framework import routers
from .views import DummyModelViewSet


router = routers.DefaultRouter()
router.register(r'dummy', DummyModelViewSet)


urlpatterns = router.urls

````

#### Note A
For security reasons, by default, if you pass to the query parameter "?permanent=true" on a bulk destroy, 
the view will not let you hard-delete, raising a PermissionDenied.
If you want to enable it on your project, just add to the project settings:

```python
TIMESTAMPS__BULK_HARD_DELETE = True
```

It's here to prevent users of "forgetting" that the routes also expose bulk hard-delete by default.
In production, you can set this flag to True and manage hard-deleting using DRF permissions.

*Hard-deleting one object at time is allowed by default.*


&nbsp;


#### NOTE B
Bulk actions of restoring and deleting returns no content (status code 204) by default.
If you want to return a response with the number of deleted/restored objects, just add this setting:

```python
TIMESTAMPS__BULK_RESPONSE_CONTENT = True
```

Example of returned response: ```{"count": 3 }```


&nbsp;


#### Note C
If you don't want to expose all the crud operations, be free to register as:

```python
router.register(r'dummy', DummyModelViewSet.as_view({'get': 'list_with_deleted'}))  # e.g.
```

And you can always use the mixins instead and create your APIViews:

````python
from rest_framework import generic
from timestamps.drf.mixins import ListDeletedModelMixin
from .models import Dummy
from .serializers import DummySerializer

class MyView(ListDeletedModelMixin, generic.GenericAPIView):
    queryset = Dummy.objects.all()
    serializer_class = DummySerializer
    
    def list_deleted(self, request, *args, **kwargs):
        # optional. your code goes here...

````


Internally, the ListDeletedModelMixin just calls the method ListModelMixin.list(self, request, *args, **kwargs).
The method of determining if the queryset must get all objects, only the deleted or all with deleted is done using AOP,
which means that the method GenericAPIView.get_queryset() is advised at runtime to map the current action
to the correct queryset the view needs.

If you don't inherit from generic.GenericAPIView, you must be aware that, for this type of scenarios,
you need to override the method get_queryset() to return the objects that matches your needs.
            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/xgeekshq/django-timestampable/",
    "name": "django-timestampable",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": "",
    "keywords": "",
    "author": "Daniel Pinto",
    "author_email": "dmp593@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/ea/2f/76cf59d6bbf758e0a554d0edd3cca12723faec090f02fc8dcc5a22f712d0/django-timestampable-1.0.1.tar.gz",
    "platform": "",
    "description": "# Django Timestamps\n\nTimestamps and Soft Delete Patterns in Django Models.\n\n## Add \"timestamps\" to your INSTALLED_APPS settings\n\n```python\nINSTALLED_APPS = [\n    # ...\n    'timestamps',\n]\n```\n\n## Usage\n\na) For models you want timestamps, just inherit Timestample:\n\n```python\nfrom timestamps.models import models, Timestample\n\n\nclass YourModel(Timestample):\n    # your fields here ...\n\n```\n\nb) For models you want soft-delete, just inherit SoftDeletes:\n\n```python\nfrom timestamps.models import models, SoftDeletes\n\n\nclass YourModel(SoftDeletes):\n    # your fields here ...\n\n```\n\nc) If you want both, you can also inherit from Model for shorter convenience:\n\n```python\n# to this:\nfrom timestamps.models import models, Model  # explicit import Model (which contains timestamps)\n\n# instead of:\n# from django.db import models\n\n# Explicitly import of \"Model\" is required\n# because models.Model is the original from Django models module\n\n\nclass YourModel(Model):\n    # your fields here ...\n\n```\n\n\n### Soft Deleting\n\n- To get all objects without the deleted ones:\n\n```queryset = YourModel.objects```\n\n- To get only deleted objects:\n\n```queryset = YourModel.objects_deleted```\n\n- To get all the objects, including deleted ones:\n\n```queryset = YourModel.objects_with_deleted```\n\n\n#### To soft delete an instance\n\n```python\nsome_model = MyModel.objects.first()\nsome_model.delete()  # or some_model.delete(hard=False)\n```\n\n#### To restore an instance\n\n```python\nsome_model = MyModel.objects_deleted.first()\nsome_model.restore()\n```\n\n#### To hard delete an instance\n\n```python\nsome_model = MyModel.objects.first()\nsome_model.delete(hard=True)\n```\n\n#### To bulk soft delete a queryset\n\n```python\nqs = MyModel.objects  # you can also apply filters to bulk delete a subset: qs = MyModel.objects.filter(...)\nqs.delete()  # or qs.delete(hard=False)\n```\n\n#### To bulk hard delete a queryset\n\n```python\nqs = MyModel.objects  # ... bulk hard delete a subset: qs = MyModel.objects.filter(...)\nqs.delete(hard=True)\n```\n\n#### To bulk restore a queryset\n\n```python\nqs = MyModel.objects_deleted  # ... bulk restore a subset: qs = MyModel.objects_deleted.filter(...)\nqs.restore()  # or qs.delete(hard=False)\n```\n\n\n---\n\n\n### If you're using DRF\nyou can use the SoftDeleteModelViewSet along with DefaultRouter present in this package\nand you will have access to a complete CRUD on soft deleted objects as well.\nThis 2 classes allows you to expose:\n\nConsider a Dummy Model that inherits from SoftDelete.\n\nYou can have all routes for CRUD operations on this model:\n\n\n| VERB | URL PATH | DESCRIPTION |\n| ---- | -------- | ----------- |\n| GET | /dummy/ | gets all the objects, without the deleted ones |\n| POST | /dummy/ | creates a new object |\n| DELETE | /dummy/[?permanent=\\<true,false>] | deletes all objects (or a filtered subject). allows hard-delete. Default: soft-delete |\n| GET | /dummy/\\<pk\\>/ | gets a non-deleted object (by primary key) |\n| POST | /dummy/\\<pk\\>/ | updates an object (by primary key) |\n| PATCH | /dummy/\\<pk\\>/ | partial updates an object (by primary key) |\n| DELETE | /dummy/\\<pk\\>/[?permanent=\\<true,false>] | deletes a non-deleted object (by primary key) |\n| PATCH | /dummy/restore/ | restore all objects (or a filtered subject) |\n| PATCH | /dummy/\\<pk\\>/restore/ | restores a soft-deleted object (by primary key) |\n| GET | /dummy/deleted/ | gets all deleted objects |\n| GET | /dummy/deleted/\\<pk\\>/ | gets a deleted object (by primary key) |\n| GET | /dummy/with-deleted/ | get all objects, deleted included |\n| GET | /dummy/with-deleted/\\<pk\\>/ | get an object (by primary key) |\n-----------------------------------\n\nThe query parameter \"permanent\" it's case-sensitive and can also be one of the values:\n\n```python\ntruthful_options = [\n    't', 'T',\n    'y', 'Y', 'yes', 'Yes', 'YES',\n    'true', 'True', 'TRUE',\n    'on', 'On', 'ON',\n    '1', 1,\n    True\n]\n```\n\n```python\nfalsely_options = [\n    'f', 'F',\n    'n', 'N', 'no', 'No', 'NO',\n    'false', 'False', 'FALSE',\n    'off', 'Off', 'OFF',\n    '0', 0,\n    'null',\n    False\n]\n```\n\n#### How to expose all CRUD operations\n\n```python\n# dummy/views.py\nfrom timestamps.drf import viewsets  # instead of: from rest_framework import viewsets\nfrom .models import Dummy\nfrom .serializers import DummySerializer\n\n\nclass DummyModelViewSet(viewsets.ModelViewSet):\n    queryset = Dummy.objects.all()\n    serializer_class = DummySerializer\n\n```\n\n````python\n# dummy/urls.py\nfrom timestamps.drf import routers  # instead of: from rest_framework import routers\nfrom .views import DummyModelViewSet\n\n\nrouter = routers.DefaultRouter()\nrouter.register(r'dummy', DummyModelViewSet)\n\n\nurlpatterns = router.urls\n\n````\n\n#### Note A\nFor security reasons, by default, if you pass to the query parameter \"?permanent=true\" on a bulk destroy, \nthe view will not let you hard-delete, raising a PermissionDenied.\nIf you want to enable it on your project, just add to the project settings:\n\n```python\nTIMESTAMPS__BULK_HARD_DELETE = True\n```\n\nIt's here to prevent users of \"forgetting\" that the routes also expose bulk hard-delete by default.\nIn production, you can set this flag to True and manage hard-deleting using DRF permissions.\n\n*Hard-deleting one object at time is allowed by default.*\n\n\n&nbsp;\n\n\n#### NOTE B\nBulk actions of restoring and deleting returns no content (status code 204) by default.\nIf you want to return a response with the number of deleted/restored objects, just add this setting:\n\n```python\nTIMESTAMPS__BULK_RESPONSE_CONTENT = True\n```\n\nExample of returned response: ```{\"count\": 3 }```\n\n\n&nbsp;\n\n\n#### Note C\nIf you don't want to expose all the crud operations, be free to register as:\n\n```python\nrouter.register(r'dummy', DummyModelViewSet.as_view({'get': 'list_with_deleted'}))  # e.g.\n```\n\nAnd you can always use the mixins instead and create your APIViews:\n\n````python\nfrom rest_framework import generic\nfrom timestamps.drf.mixins import ListDeletedModelMixin\nfrom .models import Dummy\nfrom .serializers import DummySerializer\n\nclass MyView(ListDeletedModelMixin, generic.GenericAPIView):\n    queryset = Dummy.objects.all()\n    serializer_class = DummySerializer\n    \n    def list_deleted(self, request, *args, **kwargs):\n        # optional. your code goes here...\n\n````\n\n\nInternally, the ListDeletedModelMixin just calls the method ListModelMixin.list(self, request, *args, **kwargs).\nThe method of determining if the queryset must get all objects, only the deleted or all with deleted is done using AOP,\nwhich means that the method GenericAPIView.get_queryset() is advised at runtime to map the current action\nto the correct queryset the view needs.\n\nIf you don't inherit from generic.GenericAPIView, you must be aware that, for this type of scenarios,\nyou need to override the method get_queryset() to return the objects that matches your needs.",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Timestamps and Soft Delete Patterns in Django Models",
    "version": "1.0.1",
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "md5": "4e010d260aac5a5572e00f723364fb50",
                "sha256": "25289670119fcbff17d4450b1278a0e5151ff7e3218b167feadbeef6e5b018e0"
            },
            "downloads": -1,
            "filename": "django-timestampable-1.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "4e010d260aac5a5572e00f723364fb50",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 8079,
            "upload_time": "2021-04-08T16:30:19",
            "upload_time_iso_8601": "2021-04-08T16:30:19.047370Z",
            "url": "https://files.pythonhosted.org/packages/ea/2f/76cf59d6bbf758e0a554d0edd3cca12723faec090f02fc8dcc5a22f712d0/django-timestampable-1.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2021-04-08 16:30:19",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": null,
    "github_project": "xgeekshq",
    "error": "Could not fetch GitHub repository",
    "lcname": "django-timestampable"
}
        
Elapsed time: 0.30917s