Here is one piece from Django documentation:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
do_something()
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
do_something_else()
My hesitation is focus on the save method.
Why the author separates do_something from do_something_else?
Because of the existence of 'Call the "real" save() method',what's the meaning of do_something(),which seems to be "false" ?I even can do the manipulation:
def save(self, *args, **kwargs):
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
do_something()
do_something_else()
Am I right?
Yes, you're right.
There is no deep meaning.
The sentences just mean "You can write some methods which you want to do before super().save or after super().save()."
However, I slightly doubt that you know super() in detail, which is one of the syntax in python.
do_something() and do_something_else() aren't real functions. They aren't defined. They are just hints for you to do something there, then, if you need, do something else, implementing even functions and calling them there, if you need.
Usually you need some field to automatically update/get a value when another field is saved. One way to do this is by overriding the model save method. And usually you do this before you call super().save().
Well, it is just showing that you can fall functions (or write code) before or after calling a parent or sibling method with super.
But first of all, you must know what super is...
In your example, you e calling save with super, which triggers the django based methods and functions and do many other stuff that is within ModelBase.save().
You are doing this to follow standart django save procedure.
But, you may want to make some checks or make some pre-save work, so you can call a method to do stuff that should be done before saving the model instance. Maybe you want to log the record time to a file and you just write a method and call it to log timestamp before you call super().save()
The same is also valid as post-save actions.
Related
I have a custom permission like:
def has_permission(self, request, view):
print("view", view)
I am calling list() method of ListModelMixin.
Here when I print the value of veiw it gives me class name of the view. But what I want is the name of the method that is being called, in this case list.
In view set we can get method name from action attribute.
Is there anyway I can get name of the method not the class ??
You probably can't do what you want to do...
I think it's unlikely you're going to be able to achieve what you want here without some kind of a hack. The reason is, that list isn't really the view that is called. Look at for example ListAPIView:
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
get is fundamentally the view that is called, and it then calls list. You could try and emulate what the dispatch method does to find the view that gets called (it uses request.method.lower()) but that's still just going to give you essentially the http-method.
But you probably don't want to do it either...
My senses tell me that, you probably don't want to decide what kind of permissions are appropriate based off of if you are doing list or retrieve. Even if at the moment, it so happens that all of your lists require a certain behaviour, and all of you retrieves require a different behaviour. Almost certainly, a little bit down the road this will no longer be the case and this permission will become a complicated unmaintainable mess.
Alternatives
Abstract away all of the logic about permissions into helper functions, and then write DRY permission classes for each View as needed.
Alternatively, you can always check permissions as you call the appropriate list and retrieve methods. Write a decorator for each behaviour, and just decorate the relevant methods. e.g
class MyView(ListAPIView):
#my_permission_decotator
def list(request, *args, **kwargs):
return super().list(*args, **kwargs)
In a Django model, I want to avoid doubles so I wrote this:
class Points(TimeStampedModel):
....
def save(self, *args, **kwargs):
if self.pk:
super(Points, self).save(*args, **kwargs)
else: # save() is a creation here, not an update
if Points.objects.filter(benef_card=self.benef_card,
spendable_at=self.spendable_at).exists():
pass
else:
super(Points, self).save(*args, **kwargs)
I was very surprised to find this result in my database:
I suppose there is something wrong with my code, but I'd like to know how these doubles could exist inspite of the protection I wrote in my save() method?
I think what you want instead is:
class Points(TimeStampedModel):
# ...
class Meta:
unique_together = ('benef_card', 'spendable_at')
Then you don't need to override save -- the uniqueness will be handled by a DB constraint and it is generally the way to go. This approach is better because save is not always called (example: bulk operations) so you might get different behavior across your app.
You might also want to check out update_or_create which just returns an object with attributes you need, creating it if it doesn't exist.
You could use Django signals instead to check before save.
I have a model:
class A(models.Model):
number = models.IntegerField()
But when I call A.save(), I want to ensure that number is a prime (or other conditions), or the save instruction should be cancelled.
So how can I cancel the save instruction in the pre_save signal receiver?
#receiver(pre_save, sender=A)
def save_only_for_prime_number(sender, instance, *args, **kwargs):
# how can I cancel the save here?
See my another answer: https://stackoverflow.com/a/32431937/2544762
This case is normal, if we just want to prevent the save, throw an exception:
from django.db.models.signals import pre_save, post_save
#receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
# some case
if case_error:
raise Exception('OMG')
I'm not sure you can cancel the save only using the pre_save signal. But you can easily achieve this by overriding the save method:
def save(self):
if some_condition:
super(A, self).save()
else:
return # cancel the save
As mentioned by #Raptor, the caller won't know if the save was successful or not. If this is a requirement for you, take look at the other answer which forces the caller to deal with the "non-saving" case.
If the data's always coming from a Form and you have a straightforward test for whether or not the save should occur, send it through a validator. Note, though, that validators aren't called for save() calls originating on the backend. If you want those to be guarded as well, you can make a custom Field, say class PrimeNumberField(models.SmallIntegerField) If you run your test and raise an exception in the to_python() method of that custom field, it will prevent the save. You can also hook into the validation of a specific field by overriding any of several other methods on the Field, Form, or Model.
I have a model with some customizations in the save method.
def SomeModel(models.Model):
def save(self, *args, **kwargs):
if not kwargs.pop('skip_expensive_processing', False):
do_expensice_processing()
return super(SomeModel, self).save(*args, **kwargs)
Basically, whenever the save method gets called, I want some expensive process to be executed
But when doing a bunch a saves together (a mass import), I don't want to do the expensive processing on each save. I want to do the expensive process once after all the objects are saved.
In in case of a mass save, the objects are being created through a ModelForm. I need to find some way to modify the form so that when the form calls the save method on SomeModel, it pases on that skip_expensive_processing keyword arg. How do I do this?
I loked through te source of the ModelForm.save() method, but it doesn't seem to be caling the model save method in a too straight forward manner...
You probably don't need to override the modelform's save method. You should just be able to pass commit=True, and then the model save won't be called at all.
This is supposed to be a Django-specific, but I guess it's Python anyway.
Basically, I don't want to override the work of the original method in the class I am inheriting (could be a Model class), but I'd like to add additional validation. Is this possible? Any hint?
class MyUserAdminForm(forms.ModelForm):
class Meta:
model = User
def clean(self):
// do some additional work even though it's cleaned by parent's clean method
Call the super classes clean method:
def clean(self):
super(MyUserAdminForm, self).clean()
# more cleaning
This is a common python thing to do when you subclass something and redefine functionaly but want to make sure you keep the super class functionality. Extremely common when you do an init method, as you always need to ensure the super class constructor gets called to set up the instance.
class ContactForm(forms.Form):
message = forms.CharField()
def clean_message(self):
num_words = len(message.split())
if num_words<4:
raise forms.ValidationError("Too short a message!")
return message
This is the way you add a validation method on a field, and this does ensure that the default cleanup happens. There is no need to call the default cleanup method again.
Source: www.djangobook.com
How it works:
When is_valid() is called on the form object, the system looks for any methods in the class that begin with clean_ and ends with an attribute name. If they do, it runs them after running the default cleanup methods.