Transcript: Signals
hi welcome to another episode of django chat i'm carlton gibson joined as ever by will vincent hi
will hi carlton and today we're going to talk about signals signals what are they will they
are something that i think a lot of django developers have heard of and find a little
bit scary but until you've used them you don't really know why they're there so we're going to
demystify signals and talk about why you would use them so the technical definition if you look
the docs is that their signal dispatcher allows decoupled applications to be notified when actions
occur elsewhere in the framework. So that's not that illuminating. Basically, think about it is
when you have multiple pieces of code interested in the same event, such as a save on a model or
user logging in, you can do something else. So a good example would be for authentication. Let's
say that you have, so Django has a built-in user model, and you can either create a custom user
model or create a user profile approach, which we've talked about. Both are fine. So maybe you
want to, and you could override the save method on user to create this user profile whenever it's
created, but that's a little intimate because you're touching core auth stuff, which I would
say don't do. Instead, you can have a signal that says, when I see that a new user has been created,
Do this additional step of creating a user profile via a signal.
So you see that it's happened, and then you can do an action that also goes in each and every time.
So that's a good use of signals.
I think the thing we'll get to with signals is you don't want to abuse them.
You can use them all over the place, but you want to think about when do you actually need signals.
And usually it's when there's multiple things happening on an event, and you don't know.
If you know exactly where the change is going to happen, maybe you don't need it.
You've said this more eloquently than I have.
Yeah, well, I have a sort of strong view on this.
I'm like signals, the sort of test, am I allowed to use a signal here?
And it's not that you should use a signal here.
This is like the minimum.
If you can't get past this, then you're definitely not allowed to use a signal here.
But the minimum test is, do I know at runtime, when I'm calling this, when I'm writing, you know, when I've got access to the code that I'm calling, do I know both the sender of the signal, both, you know, the model that I'm saving, for instance, and the receiver of it?
Now, to go back to that user, if I do, then I'm not allowed to use a signal.
I have to just import them and I have to just call them straight, right?
Because I've got the sender and I've got the receiver and I can just do that.
Yeah, there's no mystery.
I don't need this whole weird, yeah, there's no mystery.
So there's no decoupling here.
Now, the user example you just gave is quite good because it does pass this test
because I'm not writing django.contrib.auth.models.user, right?
So when I'm writing that, I don't have access to my project.userprofiles.models.profile.
You want the decoupling that this is core Django versus something you've written yourself.
So in that case, it is allowable,
by Carlton's am I allowed to use signals test,
to use a signal to create my user profile model
or my profile model when a user is saved.
Because I don't, at the point where I'm sort of
wanting the signal to dispatch, the saving,
user at that point in the code i don't have access to both because one's in django core and one's in
my project or django.com drip so what would be an example of something you've rolled yourself
where it'll be less clear uh the receiver okay so one thing people might do is they might say
um interview i want to send an email so they might use a a signal there and um so you've got a view
and you save your order you create an order right so the view comes in they post their user data you
You use your order model, you save the order model to the database, and then you attach to the post-save signal, hey, let's send an email.
No, no, no, don't do that.
Because in the view, at the point where you save the order, you know everything you need to know to just pull in whatever function it is that you're going to use to dispatch the email.
And don't use a signal at that point where you call save.
You know, you know.
So just pull it in and do it directly.
does that make sense i just want to mention the reason why you would do that with email is a lot
of times you want to put that into a queue but don't use a signal there if you've got a task
defer thing which will put it onto the queue call that task defer function at the point where you
save it put it in line and do it right next to where you do the save so there's a and if you
want to if you want if those two calls go next to each other create a utility function that wraps
them both and calls them both and just call that right right but don't in wait because you know
both the sender and the receiver at runtime at call time you know those people right don't use
the signals mechanism to hide that connection somewhere else in your code base out of sight
right so i was just trying to think of an example where it would make sense i mean the obvious ones
is when there's a django core thing auth or something else where you you want to decouple
Or a plug-in to a third-party app.
You know, we had the edX folks on, and they've got a plug-in system.
And the plug-in authors need to know when certain things happen,
but they can't obviously come in and edit the edX code base
and pull in their models in there.
So a signals mechanism in that case is great.
It's a nice way of extending it.
Right, so those would be two obvious places.
And we hammer on this because signals are often abused.
It's like you don't know about them, then once you learn about them,
you just throw them everywhere, and that's not the way to do it.
You'll find these monitoring packages that you put in try and hook into signals as well.
And they could just wrap your application in a middleware or something.
They don't want to do that because it's too hard to configure.
So they use these signals.
But then it's like totally mysterious how this monitoring application hooks onto my project.
Whereas if it just sort of said, oh, you know, put a middleware at the top around all your other middlewares, you'd know exactly how that monitoring application hooked on.
Now, it does the same thing because it hooks onto signals, request started, request finished, or wherever.
But it shouldn't be a mystery because it's not that hard.
But by using signals, you make it hard.
Says the Django fellow.
It's not that hard to understand.
No, but relatively speaking, right?
If it's like, hey, I've got a middleware and I've wrapped it around your application
and you can see where it is in the code base, at least that's comprehensible.
But if it's just like, oh, you know, pip install this and magically it will work,
it's like, well, hang on, how?
Yeah.
And you have to go and open the code of the service that you're using, and you have to
search through their calls somewhere far deep in, it will be their Django integration, and
you have to say,
they're hooking into the request and started request one finish signals oh now it makes sense
yeah too much mystery i wanted to ask you carlton so is there a case where a get request would need
a signal because generally it's almost always a post where data comes in from the user and you
want to modify it in some way before storing it in the database that's the classic example of
a signal um is would there be a get example maybe one of those monitoring ones there might
yeah i mean so when would you use request starter you might use it for any you know logging some
data yeah maybe a logging thing i guess if it was a third-party package maybe that you want
something to happen at this point in the request response cycle and for some reason you weren't
able to put it in a middleware or in a view or like right but typically it's around exactly why
saving some data um or an action like sending sending an email if if you're justified in the
past carlson's test yeah the the am i allowed to use a signal test yeah and you well and so
and just in terms of logistics so there are um four there's a number of built-in signals that
jango gives us around um pre-save post-save uh pre-delete post-delete i think those are well
named um actually this interesting one so uh m2m change so when a many-to-many field has been
changed because that sort of gets tricky when you have many-to-many's yeah and it's also tricky the
signal is tricky because um if you call add with a model that's already on the relation
django has to filter that model out um beat that instance out because otherwise you get an
integrity error because you say it's already in the relation you get an integrity error so
when the signal is fired any um any instances that were already on the relation get filtered
out and you don't see them as being submitted but if for a remove you can you can click remove even
if it doesn't you can try and delete something from relation even if it doesn't exist and the
database won't complain so you get all the things that were submitted so people do get confused
because they get for when things are removed they get all of the primary keys that they
were that were submitted for the instances that were removed from the relation but when they're
added they only get the ones that were actually added i like it's tricky um so another thing
actually and since you would know this carlton uh so where to place the signals um so historically
people said i think like around one six or something um the folks said oh put it in your
models.py file but now the advice is no don't do that at all you want to avoid avoid import issues
and generally people will create a signals.py file within the app and then uh hook it in and
also if you're wondering why do you do the longer app config dance on installed apps it's because
because it will automatically pull in signals and if you don't you have to update the init.py file
so i guess my question to you carlton is why the change what's sort of the history and thought
around where to put well i don't know about all the history of the chat i'd have to go
read up about well the advice is what i would say to folks is don't put it in your models.py file
that used to be how you would do it and there are still examples and tutorials talking about that
But you want to create a dedicated file and hook it in through app.py.
So let's think about it from first principles, right?
Why would you put it in your models file?
Because the great danger here is that you take, by using a signal, you take a relation between, say, two models in your project.
And you hide it somewhere.
And you forget about it.
And then you do something else.
at the same time say and they're called there's a state management project and it becomes difficult
to manage so one way of keeping this kind of um this kind of decoupling in sight is by putting
it right next to your models so you can't forget that this signal does this thing at post save
because it's right next to the source code but if you're importing a model from you know another
part of your application you might hit that whole app registry not ready thing and doing things
before that's all finished so if you put it in a separate file and then you import that into your
your app config and then you you call it that in the ready handler with that that ready handler is
only called once the app registry is set up once the apps are populated and the ORM is ready to
use so you can do whatever you need to do there that would be my sort of guess as to the evolution
yeah makes sense to me so in general in terms of ramping up probably you'll start with a built-in
signal if you need one again make sure you need one and then you can write custom signals but
it's it's fun to do but again use use some caution like this is the this is the thing
that everyone goes through don't know what they are find them they're great and then just reach
for them too often well but the evolution is natural as well because you you start building
you start building a nice simple model and it's all going great and then you add more logic and
you add more logic it gets really complicated and you're like oh but every time i do this i've got
to update all these other bits and bobs so you want to extract that sort of gnarliness into a
separate place and the signal the signal system allows you to decouple right you know that's the
idea it's decoupling your code and it looks as if it's neater but what you've created is instead of
a dependency that's on the surface that you can at least it might be difficult it might be in your
face but at least you can see it you've created a hidden one somewhere else that's really easy
to get about and this is where people get into all sorts of troubles managing state and one signal
it's not going to kill you two signals they're not going to get but eventually it's like actually
my project is unmanaged right explicit over implicit and actually we talk and then this
larger question of business logic we talk about this uh with the edX folks uh which will have
come out by the time you're listening to this one so go listen to that so yeah where to put the
logic is just one of those tough questions um but yeah don't don't don't abuse the signal yeah
yeah like a signal use them you know like a the sprinklings on the top of a fairy cake no
you know first resort yeah uh what else is there anything else to say i mean signals are i mean
they're a nice what i would say look signals are a nice pattern in that there's this decoupling
there's this notification so if you've got multiple receivers who all want to tune into it
like it's a published subscribe kind of thing it's like yeah okay i could fan out by using signals
I could fan out a message by using it.
That's not a bad way of using it.
But again, just be conscious.
Could you maintain a list and call those at the same point
where you called whatever triggered the signal?
Yeah, you could.
Cool. All right.
Well, hopefully that will help folks.
Again, we're diving into more of the internals of Django
and hopefully going to show across the board.
They're not scary.
They're helpful and give some rules of thumb
for when to reach for these tools
because Django has a lot of cool tools.
There's batches included, isn't there?
Anyway, right.
That'll do.
Thanks for joining us.
We're ChatDjango, DjangoChat.
We're ChatDjango on Twitter
and DjangoChat everywhere else.
All right.
All right.
See you all next time.
See you next time.
Bye-bye.