I am deleting objects in different models with DeleteView in Django.
The problem is that I don't want the objects to be completely deleted but rather just hidden. First I thought that it made sense to keep my views as they are but instead overriding the delete method in each model to do as follows
def delete(self, force=False):
if force:
return super(ModelName, self).delete()
else:
self.is_deleted = True
self.save()
but then I noticed that the delete method wont be called in bulk deletion so this method will be too risky.
Can someone recommend a good way to do this? I still want to keep the normal behaviour of DeleteView but it should just 'deactivating' the objects rather than deleting them.
DeleteView is as follows:
def delete(self, request, *args, **kwargs):
"""
Calls the delete() method on the fetched object and then
redirects to the success URL.
"""
self.object = self.get_object()
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
Will it be sufficient if I replace self.object.delete() with
self.object.is_deleted = True
self.object.save()
When I have marked my objects as deleted, how can I make sure that my querysets wont contain the deleted objects? I could simply replace get_queryset() in my ListView but they should be left out of any queryset on the page so I wonder if I would get better results if I customize the objects manager instead?
I've been looking at django-reversion. Could I simply just delete all objects in the normal manner and then use django-reversion if I want to restore them? Are there any disadvantages of this solution?
When I have marked my objects as deleted, how can I make sure that my querysets wont contain the deleted objects?
As the comment states, the Django-only solution is writing a customer Manager that understands your is_deleted field.
I've been looking at django-reversion. Could I simply just delete all objects in the normal manner and then use django-reversion if I want to restore them?
Yes, as long as your wrap your deletions in reversions. This can be as simple as using the reversion middleware to wrap all deletes and saves:
MIDDLEWARE_CLASSES = (
'reversion.middleware.RevisionMiddleware',
# Other middleware goes here...
)
Are there any disadvantages of this solution?
None that I've found, its well supported and apart from just deletion support it also has version tracking.
but then I noticed that the delete method wont be called in bulk
deletion so this method will be too risky.
you can write your own QuerySet for that and use it as_manager. Same QuerySet can take care of hiding your deleted fields from displaying. Remember to leave some way for retrieving all of your deleted fields.
Related
My django-application requires a few model instances to always be present in the database to function properly.
I currently create the model instances that I require in the Appconfig.ready(self) method for the corresponding app. This way the instances are always present on boot of the django-application. This works but not as well as I'd like, I have to be careful when deleting objects so that I do not delete the required objects.
I would like the required model instances to be undeletable or preferably, be created whenever they are not present in the database.
I agree with your solution about protecting certain objects from deletion in you problem. To do this there are two ways I can think of:
When deletion is happening check to see if object is protected or not
Tweak admin permissions and do not let anyone delete the object from admin panel
The main difference is in first way your only way to delete protected objects is from your database command line interface, However in second way the model can be deleted in code.
For the first way you need to override delete method of the model and check something like this
assert pk != self.protected_objects
Where protected objects list is a property of your model class.
For second way you would do
class YourModel(admin.ModelAdmin):
protected_objects = [1,2,...]
def has_delete_permission(self, request, obj=None):
return obj.pk not in self.protected_objects
I am having an issue with the way Django class-based forms save a form. I am using a form.ModelForm for one of my models which has some many-to-many relationships.
In the model's save method I check the value of some of these relationships to modify other attributes:
class MyModel(models.Model):
def save(self, *args, **kwargs):
if self.m2m_relationship.exists():
self.some_attribute = False
super(MyModel, self).save(*args, **kwargs)
Even if I populated some data in the m2m relationship in my form, I self.m2m_relationship when saving the model and surprisingly it was an empty QuerySet. I eventually found out the following:
The form.save() method is called to save the form, it belongs to the BaseModelForm class. Then this method returns save_instance, a function in forms\models.py. This function defines a local function save_m2m() which saves many-to-many relationships in a form.
Here's the thing, check out the order save_instance chooses when saving and instance and m2m:
instance.save()
save_m2m()
Obviously the issue is here. The instance's save method is called first, that's why self.m2m_relationship was an empty QuerySet. It just doesn't exist yet.
What can I do about it? I can't just change the order in the save_instance function because it is part of Django and I might break something else.
Daniel's answer gives the reason for this behaviour, you won't be able to fix it.
But there is the m2m_changed signal that is sent whenever something changes about the m2m relationship, and maybe you can use that:
from django.db.models import signals
#signals.receiver(signals.m2m_changed, sender=MyModel.m2m_relationship.through)
def handle_m2m_changed(sender, instance, action, **kwargs):
if action == 'post_add':
# Do your check here
But note the docs say that instance "can be an instance of the sender, or of the class the ManyToManyField is related to".
I don't know how that works exactly, but you can try out which you get and then adapt the code.
But it would be impossible to do it any other way.
A many-to-many relationship is not a field on the instance, it is an entry in a linking table. There is no possible way to save that relationship before the instance itself exists, as it won't have an ID to enter into that linking table.
I'm currently implementing djangorestframework for my app RESTful API. After playing around with it, I still do not clearly understand what .create(self, validated_data) and .update(self, validated_data) used for in the serializer. As I understand, CRUD only calls the 4 main methods in viewsets.ModelViewSet: create(), retrive(), update(), and destroy().
I also have already tried to debug and print out stuff to see when the .create() and .update() methods are called in both ModelViewSet and ModelSerializer. Apparently, only the methods in ModelViewSet are called when I do the HTTP verbs. However, for ModelSerializer, I don't see any calls in those 2 methods. I just want to know what are those methods used for in ModelSerializer since I see that people override those methods a lot in the serializer.
You really must split things between the views and the serializer.
Serializers
The Serializer is a standalone object. It is used for converting a Django model (or any kind of python datastructure, actually) into a serialized form, and the other way around.
You may use it as such, wherever you want. It does not even need an actual HTTP request as long as you don't need URIs in your output.
The ModelSerializer subclass is a specialized kind of Serializer that adds "load-from-model" and "save-to-model" functionality.
The "save-to-model" entry point is the save() method. For easier overriding, its default implementation will delegate its work to either the create() or update() method of the serializer, depending on whether it is creating a new model instance, or updating one.
The purpose of that is customization: it gives you, the developer, the option to override just the create method, just the update method, or common behavior.
For instance, it allows you to do this kind of things:
def save(self, **kwargs):
# Will be done on every save
kwargs['last_changed'] = timezone.now()
return super().save(**kwargs)
def create(self, instance, data):
# Will only be done if a new object is being created
data['initial_creation'] = timezone.now()
return super().create(instance, data)
That's a basic example. There, the last_changed field will be set every time an object is saved, be it a creation or an update.
As a sidenote, you probably do not want to do that. Things such as setting "last_changed" fields should live in the view, not in the serializer.
Viewsets
In a completely different place, Django REST framework supplies Viewsets. Those are an organized collection of views, revolving around implementing a CRUD API for a model.
As such, it structures it functionality into a set of methods, namely create(), retrieve()/list(), update() and delete().
The main point being: there is no connection whatsoever between the viewset's create() method and the serializer's create() method.
It just happens that the default implementation of the viewset's methods uses a ModelSerializer and that the default implementation of that serializer's save() method delegates the job to methods that have the same name.
By the way, about the last_changed example, here is how you would do it in the view:
def perform_create(self, serializer):
now = timezone.now()
serializer.save(initial_creation=now, last_changed=now)
def perform_update(self, serializer):
serializer.save(last_changed=timezone.now())
That's functionally equivalent to the example above, but lives in the viewset.
Conclusion
So back to your question, the specific thing you should override depends on which object is responsible for the task you want to add.
If your custom behavior is part of the serialization process, that is, the process of converting raw data back into a proper Django model and saving it, then you should override the Serializer's methods.
If, on the other hand, your custom behavior is specific to your viewset, then you should override the Viewset's methods.
As a hint, you may ask yourself the following question: if I use the same serializer in another place (maybe another viewset), should it always display that behavior?
I finally understand how the .create() and .update() work in Serializer (especially ModelSerializer) and how they are connected to Viewsets (especially ModelViewSet). I just want clarify the concept more clearly if someone comes to this question.
Basically, the 4 methods CRUD in ModelViewSet: .create(), .retrieve(), .update(), and .destroy() will handle the calls from HTTP verbs. By default, the .create() and .update() from ModelViewSet will call the .create() and .update() from ModelSerializer by calling the .save() method from the BaseSerializer class.
The save() method will then determine whether it will call .create() or .update() in ModelSerializer by determining whether the object self.instance exists or not.
In the rest-api design create, read, update and delete is a standard.
There is not quite big difference in create and update.
Please refer to this and
see create() method will create an item.
and
update() method need to specify which item to be updated.
So every model comes with some commonly used functions such as save and delete.
Delete is often overridden to set a boolean field such as is_active to false, this way data is not lost. But sometimes a model exists that has information that, once created, should always exist and never even be "inactive". I was wondering what the best practice for handling this model's delete method would be?
ideas
make it simply useless:
def delete(self):
return False
but that just seems odd. Is there maybe a Meta option to disable deleting? is there any "nice" way to do this?
Well it depends, you cannot truly restrict deletion, because somebody can always call delete() on queryset or just plain DELETE sql command. If you want to disable delete button in django admin though, you should look here.
delete() on queryset can be restricted with this:
class NoDeleteQuerySet(models.QuerySet):
def delete(self, *args, **kwargs):
pass
class MyModel(models.Model):
objects = NoDeleteQuerySet.as_manager()
...
Django docs - link
I'm trying to find a way to filter down rows of objects within Django Admin, using a queryset.
e.g. Person.objects.filter(Q(name='John')|Q(surname='Doe'))
I'm finding quite complicated to figure out.
Any ideas?
You might be able to accomplish this by overriding the queryset() method on your modeladmin instance. See http://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py?rev=15347#L196
# untested code
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(MyModelAdmin, self).queryset(request)
return qs.filter(Q(name='John') | Q(surname='Doe'))
This would only affect results for the model registered with that ModelAdmin, but you could probably subclass it as a starting point for other ModelAdmin classes in order to stay DRY.
I'm not saying this is a good idea.