Django - how do I _not_ dispatch a signal? - python

I wrote some smart generic counters and managers for my models (to avoid select count queries etc.). Therefore I got some heavy logic going on for post_save.
I would like to prevent handling the signal when there's no need to.
I guess the perfect interface would be:
instance.save(dispatch_signal=False)
How can I accomplish this?
Update
More information about what I'm doing, if anyone's interested:
Generic counters are stored in a separate table
Every time Django paginates an object list, it calls overriden count() method of my custom manager, which basically retrieves the static counter value for appropriate object class.
Signals trigger the logic of counters update, which is a bit complicated since it checks many aspects of related models (i.e. it has to generate a visibility property based on a nested category tree). I can't put this logic in Model.save() because one counter depends on many different models. I'd like to have that logic in one piece, instead of fragments spread around.
I am denormalizing some of my models, so I rewrite (duplicate) certain values across tables.
For testing purposes I run my little command-extension -- Dilla, to populate random data around.
I've noticed unwanted signals triggering, therefore I'd like them to run conditionally.
Hope it's clear enough. Excuse my language mistakes.

I found simple and easy solution:
MyModel.objects.filter(pk=instance.id).update(**data)
It is due to (https://docs.djangoproject.com/en/1.5/ref/models/querysets/#update):
Finally, realize that update() does an update at the SQL level and,
thus, does not call any save() methods on your models, nor does it
emit the pre_save or post_save signals (which are a consequence of
calling Model.save()).

You can disconnect and reconnect the signal. Try using a with: statement with this utility class:
class SignalBlocker(object):
def __init__(self, signal, receiver, **kwargs):
self.signal = signal
self.receiver = receiver
self.kwargs = kwargs
def __enter__(self, *args, **kwargs):
self.signal.disconnect(self.receiver)
def __exit__(self, *args, **kwargs):
self.signal.connect(self.receiver, **self.kwargs)
You can now use:
with SignalBlocker(post_save, my_post_save_handler):
instance.save()

A quick and dirty solution would be:
from django.db.models.signals import post_save
from somewhere_in_my_app import my_post_save_handler
post_save.disconnect(my_post_save_handler)
instance.save()
post_save.connect(my_post_save_handler)
But otherwise i strongly recommend moving your logic into the save() method of your model.

You can also call instance.save_base(raw=True) and check for the raw argument in your pre_save or post_save signal handler:
def my_post_save_handler(instance, raw, **kwargs):
if not raw:
heavy_logic()
You can add some sugar and get your perfect interface:
class MyModel:
def save(self, dispatch_signal=True, **kwargs):
self.save_base(raw=not dispatch_signal, **kwargs)
Note that save_base() is not part of the public API of Django, so it might change in a future version.

Related

django get view method name on permission classes like actions

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)

Is it possible to register an instance method as a Signal receiver via decorators?

There are multiple ways to register class methods as Signal receivers in Django. Using the built-in receiver decorator is an obvious solution, but can work only with static methods or simple functions:
from django.db.models.signals import post_save
from django.dispatch import receiver
class SignalCollection:
#receiver(post_save)
def some_signal(sender, instance, created, **kwargs):
# code
pass
With instance methods, this decorator won't work as such methods require self as a first parameter. In this case, instantiating after defintion is what works:
class SignalCollection:
def some_signal_a(self, sender, instance, created, **kwargs):
# code
pass
def some_signal_b(self, sender, instance, created, **kwargs):
# code
pass
collection = SignalCollection()
post_save.connect(collection.some_signal_a)
post_save.connect(collection.some_signal_b)
The potential issue with this is that it's not well encapsulated, and in case of many methods contains a lot of repeation.
For solving this issue, I intended to apply custom decorators. My question is: is it possible to create such a decorator, either for the class itself, or for its methods, that can perform connections without instantiating the class?
My research yielded no results, and many of my attempts were failed - adding receiver through method_decorator can work, but in none of my snippets were the signals triggered, meaning the connection did not happen.
Creating a class decorator that decorates all its methods seemed promising:
def signal_collection(target_class):
def _is_acceptable_callable(func):
return (inspect.ismethod(func) or inspect.isfunction(func)) and not inspect.isbuiltin(func)
for name, target_method in inspect.getmembers(target_class):
if not _is_acceptable_callable(target_method):
continue
decorated_method = some_extra_decorator(target_method)
setattr(target_class, name, decorated_method)
return target_class
#signal_collection
class SignalCollection:
def some_signal(self, sender, instance, created, **kwargs):
pass
The advantage of this pattern is that you can easily modify signal_collection to contain wrappers, and then use args and kwargs as inputs similar to the function-based receiver (that is, specifying signal, sender, etc), but still, no such some_extra_decorator could I make that would run without explicitly calling <signal>.connect.
I can't be exactly sure but my theory is that it's not possible at all. A fragment of a sentence suggests that Signals can't be connected arbitrarily on this level.
It has to do something with the fact that Signals should be loaded explicitly in AppConfig.ready, similar to the way Models are registered to Sites.
I'm not confident enough about it, hence the question.
I had a similar problem. method_decorator won't work on signal receivers. They are designed to decorate methods accessed by their instance name. It can be customized to work with signal receivers but I don't think it's worth the effort. I switched to regular functions

Django model, default records

I am a beginner in Django, and I am learning models for now.
I have two tables in the backend, one a child of another (1-to-many relationship).
So far, so good.
What I want to do is set Django, so that if a record is created in the parent table, the child table will automatically create 3 records.
How do I program this?
Thanks.
You may be interested in something like this:
# ... other imports ...
from django.db.models.signals import post_save, pre_save
class Parent(models.Model)
# this decorator & this method go inside your model
#staticmethod
def create_children(sender, instance=None, **kwargs):
for x in range(3):
# I'm assuming you want the child to be linked to the parent immediately.
# You can set any other attributes you want here too, of course.
child = Child(parent = instance)
child.save()
pre_save.connect(Parent.create_children, Parent)
Note that in the pre_save.connect() call, you can call any [SomeClass].[SomeMethodOfThatClass] (this is the first argument) on the save of some other class (this is the second argument). In practice, though, I don't think I've actually ever done that, and I'm not sure that you need to do that here.

Cancel saving model when using pre_save in django

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.

Change a django form class before it is instantiated

I'm wondering if it's possible to alter or change a form class before it's instantiated.
As a concrete example, I have a payment form which needs to be modified based on the payment system being used.
Ideally I'd rather not create different form classes and then choose different ones based on the payment system; instead the payment system object will "tell" the form class what changes it need to make -- for example, making certain fields optional or instructing them to use different widgets.
Is this possible? The alternative is to pass an object representing the payment system into the form, and then having it modify the form after instantiation, but that just seems clumsy somehow to have it run in the form class rather than the view. I feel like the Django "view" is closer to a controller, and it seems like this is where something like this should happen. I also feel like modifying it'd be better to modify the form_class object rather than the form instance; I'm not even sure if when you add fields after the fact like this it will handle validation and form fill-in correctly. Will it?
Anywhere, here's some sample code of how it would work passing the payment object into a form instantiation call:
payment_system.py:
class ExamplePaymentSystem(BasePaymentSystem):
def modify_form(self, form):
for fld in self.optional_fields:
form.fields[fld].required = False
…etc…
forms.py:
class ModifiablePaymentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.payment_system = kwargs.pop("payment_system", None)
super(ModifiablePaymentSystem, self).__init__(*args, **kwargs)
self.payment_system.modify_form(self)
You should not modify global static data (classes defined at module scope are static), because if you run your code in many threads per process (which is often done) one thread may modify form class used by the other threads.
If you your payment systems are static (you do not add new on the fly, while server is running) I'd define one form per payment system.
If not, you can always, define new form types on the fly like that:
def get_form_type(payment_system):
class DynamicForm(BasePaymentForm):
... add change fields etc...
return DynamicForm
or modify instances like that:
class PaymentForm(BasePaymentForm):
def __init__(self, ..., payment_system):
self.fields['foo'].required = False # <--- I'm writing code
#from the memory, so possibly you'll need t edit it
#but this is doable and easy to do.
How to remove field in forms (per OP request).
When you subclass:
This is hard and I think you'll need to browse through form internals and modify them by hand after subclass creation. This is a wild guess...
def get_form_type(payment_system):
class DynamicForm(BasePaymentForm):
... add change fields etc...
del DynamicForm.base_fields['foo']
return DynamicForm
When you modify instance:
I'm not 100% sure, but I peeked into django source code (unfortunately these details are not in docs). But i guess that you should:
class PaymentForm(BasePaymentForm):
def __init__(self, ..., payment_system):
del self.fields['foo']
The fields are a dict (or I guess -- OrderedDict for that matter) and to delete field you need to remove whole key-vaule mapping.

Categories