2
2
3
3
Timestamps and Soft Delete Patterns in Django Models.
4
4
5
+ ## ✅ Summary
5
6
6
- ## How to install
7
+ - Add timestamps and soft delete to any model with a single line of code.
8
+ - Manage deleted objects effortlessly with built-in managers and custom querysets.
9
+ - Seamlessly integrate into existing models without breaking changes or refactoring your app/project
10
+ - ⭐ no need to modify the default objects manager or queryset ⭐
11
+ - Hook into lifecycle events with signals.
12
+ - Get full CRUD support in DRF, including restore endpoints.
13
+ - Configure safety features such as bulk hard delete and bulk responses.
14
+
15
+ ## 🚀 Installation
16
+
17
+ ### Without DRF
7
18
8
- ### Option a)
9
19
```` bash
10
20
$ pip install django-timestampable
11
21
````
12
22
23
+ ### With DRF support
13
24
14
- ### Option b)
15
25
To install django-timestampable with [ Django Rest Framework] ( https://www.django-rest-framework.org/ ) included:
26
+
16
27
```` bash
17
28
$ pip install " django-timestampable[drf]"
18
29
````
30
+
19
31
* You can use the first option if you have Django Rest Framework already installed.*
20
32
21
33
  ;
22
34
23
- #### And add "timestamps" to your INSTALLED_APPS settings
35
+ ## ⚙️ Configuration
36
+
37
+ Add ` timestamps ` to your ` INSTALLED_APPS ` in your Django settings:
24
38
25
39
``` python
26
40
INSTALLED_APPS = [
@@ -29,7 +43,8 @@ INSTALLED_APPS = [
29
43
]
30
44
```
31
45
32
- #### Or, if you installed with [ Django Rest Framework] ( https://www.django-rest-framework.org/ ) :
46
+ ### Or, if you installed with [ Django Rest Framework] ( https://www.django-rest-framework.org/ ) :
47
+
33
48
``` python
34
49
INSTALLED_APPS = [
35
50
# ...
@@ -38,63 +53,60 @@ INSTALLED_APPS = [
38
53
]
39
54
```
40
55
41
- ## Usage
56
+ ## 🛠 Usage
42
57
43
- a) For models you want timestamps, just inherit Timestampable:
58
+ a) For Timestamps
44
59
45
60
``` python
46
61
from timestamps.models import models, Timestampable
47
62
48
-
49
63
class YourModel (Timestampable ):
50
64
# your fields here ...
51
65
52
66
```
53
67
54
- b) For models you want soft-delete, just inherit SoftDeletes:
68
+ b) For Soft Deletes
55
69
56
70
``` python
57
71
from timestamps.models import models, SoftDeletes
58
72
59
-
60
73
class YourModel (SoftDeletes ):
61
74
# your fields here ...
62
75
63
76
```
64
77
65
- c) If you want both, you can also inherit from Model for shorter convenience:
78
+ c) Combine both Timestamps and Soft Deletes
66
79
67
80
``` python
68
81
# to this:
69
- from timestamps.models import models, Model # explicit import Model (which contains timestamps)
70
-
71
- # instead of:
72
- # from django.db import models
73
-
74
- # Explicitly import of "Model" is required
75
- # because models.Model is the original from Django models module
76
-
82
+ from timestamps.models import models, Model # shortcut including both
77
83
78
84
class YourModel (Model ):
79
85
# your fields here ...
80
86
81
87
```
82
88
89
+ ** Note** : Always import Model from timestamps.models explicitly. models.Model from Django is untouched.
90
+
91
+ ## 🧹 Soft Deleting
83
92
84
- ### Soft Deleting
93
+ ### Query managers
85
94
86
- - To get all objects without the deleted ones:
95
+ When you use SoftDeletes or Model, you have 3 query managers available:
96
+
97
+ - Active objects only (without soft deleted objects):
87
98
88
99
``` queryset = YourModel.objects ```
89
100
90
- - To get only deleted objects :
101
+ - Deleted objects only:
91
102
92
103
``` queryset = YourModel.objects_deleted ```
93
104
94
- - To get all the objects, including deleted ones :
105
+ - All objects (active + deleted) :
95
106
96
107
``` queryset = YourModel.objects_with_deleted ```
97
108
109
+ ### Operations
98
110
99
111
#### To soft delete an instance
100
112
@@ -138,15 +150,9 @@ qs = MyModel.objects_deleted # ... bulk restore a subset: qs = MyModel.objects_
138
150
qs.restore() # or qs.delete(hard=False)
139
151
```
140
152
141
-   ;
142
-
143
- ---
144
-
145
-   ;
146
-
147
- ### Signals for Soft Deleting and Restoring
153
+ ## 📡 Signals
148
154
149
- You have 4 signals available that you can listen in your project :
155
+ Four signals are available :
150
156
151
157
- pre_soft_delete
152
158
- post_soft_delete
@@ -155,7 +161,7 @@ You have 4 signals available that you can listen in your project:
155
161
156
162
To use them, just import the signals and register listeners for them. Eg:
157
163
158
- #### Pre Soft Delete
164
+ ### Pre Soft Delete
159
165
160
166
``` python3
161
167
from timestamps.signals import pre_soft_delete
@@ -166,7 +172,7 @@ def on_pre_soft_delete(sender, instance, **kwargs):
166
172
print (f " Model { sender} with id { instance.pk} will be deleted! " )
167
173
```
168
174
169
- #### Post Soft Delete
175
+ ### Post Soft Delete
170
176
171
177
``` python3
172
178
from timestamps.signals import post_soft_delete
@@ -177,7 +183,7 @@ def on_post_soft_delete(sender, instance, **kwargs):
177
183
print (f " Model { sender} with id { instance.pk} was deleted at { instance.deleted_at} ! " )
178
184
```
179
185
180
- #### Pre Restore
186
+ ### Pre Restore
181
187
182
188
``` python3
183
189
from timestamps.signals import pre_restore
@@ -188,7 +194,7 @@ def on_pre_restore(sender, instance, **kwargs):
188
194
print (f " Model { sender} with id { instance.pk} deleted at { instance.deleted_at} will be restored! " )
189
195
```
190
196
191
- #### Post Restore
197
+ ### Post Restore
192
198
193
199
``` python3
194
200
from timestamps.signals import post_restore
@@ -199,13 +205,7 @@ def on_post_restore(sender, instance, **kwargs):
199
205
print (f " Model { sender} with id { instance.pk} restored! " )
200
206
```
201
207
202
-   ;
203
-
204
- ---
205
-
206
-   ;
207
-
208
- ### If you're using DRF
208
+ ## 🌐 Using with DRF
209
209
210
210
You can use the SoftDeleteModelViewSet along with DefaultRouter present in this package
211
211
and you will have access to a complete CRUD on soft deleted objects as well.
@@ -258,7 +258,7 @@ falsely_options = [
258
258
]
259
259
```
260
260
261
- #### How to expose all CRUD operations
261
+ ### How to expose all CRUD operations
262
262
263
263
``` python
264
264
# dummy/views.py
@@ -287,7 +287,9 @@ urlpatterns = router.urls
287
287
288
288
````
289
289
290
- #### Note A
290
+ ## ⚠️ Notes & Settings
291
+
292
+ ### Note A - About Bulk Hard Delete
291
293
292
294
For security reasons, by default, if you pass to the query parameter "?permanent=true" on a bulk destroy,
293
295
the view will not let you hard-delete, raising a PermissionDenied.
@@ -304,7 +306,7 @@ In production, you can set this flag to True and manage hard-deleting using DRF
304
306
305
307
  ;
306
308
307
- #### NOTE B
309
+ ### Note B - About Bulk Response
308
310
309
311
Bulk actions of restoring and deleting returns no content (status code 204) by default.
310
312
If you want to return a response with the number of deleted/restored objects, just add this setting:
@@ -317,15 +319,15 @@ Example of returned response: ```{"count": 3 }```
317
319
318
320
  ;
319
321
320
- #### Note C
322
+ ### Note C - Selective Routes
321
323
322
324
If you don't want to expose all the crud operations, be free to register as:
323
325
324
326
``` python
325
327
router.register(r ' dummy' , DummyModelViewSet.as_view({' get' : ' list_with_deleted' })) # e.g.
326
328
```
327
329
328
- And you can always use the mixins instead and create your APIViews :
330
+ Or use DRF mixins instead:
329
331
330
332
```` python
331
333
from rest_framework import generic
@@ -350,6 +352,10 @@ to the correct queryset the view needs.
350
352
If you don't inherit from generic.GenericAPIView, you must be aware that, for this type of scenarios,
351
353
you need to override the method get_queryset() to return the objects that matches your needs.
352
354
355
+   ;
356
+
353
357
---
354
358
355
- Thank you for reading!
359
+   ;
360
+
361
+ Thanks for using ` django-timestampable ` ! 🎉
0 commit comments