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 in the docs, is that there a signal dispatcher allows decoupled applications to be notified when actions occur elsewhere in the framework.
So that's not that uh illuminating.
Uh basically the think about it is when you have multiple pieces of code interested in the same event, such as a save on a model or a user logging in, you can do something else.
Uh so a good example would be for authentication, let's say that you have uh so this 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 happened that also goes in each and every time.
So that's 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 uh if you know exactly where the change is gonna happen, maybe you don't need it.
Uh you have a you've said this more eloquently than I have.
Yeah, no.
Well, I I have I have a sort of strong view on this.
I'm like signals, um 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 answer, if if you can't get past this, then you're definitely not allowed to use a signal here.
But the minimum test is um, do I know at call at runtime, at the when I'm calling this, when I'm writing, you know, when I've got access to the code that I'm calling, um, do I know both the the sender of the signal, both i.e., you know, the model that I'm saving, for instance, and the receiver of it.
Now, so to go back to that user, in 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 I don't need this whole weird, yeah, there's no mysteries, 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 dot models.user.
Right?
So when I'm writing that, I don't have access to the to to my my project.userprofiles.models dot profile.
Right.
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 um 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 you the saving the 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 we've you've rolled yourself where it'll be less clear uh the receiver or something?
Okay, so one thing people might do is they might say, Um, in a view, I want to send an email.
So they might use a uh a signal there, and um, so you've got a view and you save your order, you create an order, right?
So the the view comes in, they post their user data, you you get your 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, 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 gonna use to dispatch the email and don't use a signal.
You 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 I just want to mention the the reason why you would uh do that with email is a lot of times you want to put that into a keyboard.
But but don't use a signal there.
If you've got a task delay defer thing which will put it onto the queue, call that task defer function at the point where you save it, pull 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 from every.
But don't, in with 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 it.
Head time app.
You know, we had the plugin, yeah, actually.
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 is is great.
It's a nice way of extending it.
Right.
So those would be two obvious places.
And 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.
You find these monitoring packages that you pull in, um, try and hook into signals as well.
When they could just wrap your application in the 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 into my hooks on 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 on to 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.
No, no, but relatively speaking, right?
It's like if 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 uh huh?
Yeah.
And you have to go and open the code of the of the of the um of the the service that you're using, and you have to search through the course by the call um they're you know, somewhere far deep in it will be their Django integration, and you have to say, oh, they're hooking into the request and start and request mode signals.
Oh, that now it makes sense.
Yeah, too much mystery.
I wanted to ask you, Carlton.
So uh 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 started?
You might use it for any, you know, logging some data or something.
Yeah, maybe a logging thing, I guess, if it was a third-party package.
Like 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 you know.
But typically it's around uh uh saving some data um or an action like sending uh sending an email if if you're justified in the past Carlson's test.
Yeah, 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 uh there's a number of built-in signals that Django gives us around um pre-save, post-save, uh pre-delete, post-delete.
I think those are well named.
Um actually this is an 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 is tricky here because um it 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 would 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 um 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 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 that.
I like that.
Um so another thing, actually, and since you would know this, Carlton, uh, so where to place the signals.
Um so historically, people said, uh I think like around 1.6 or something, um 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 uh 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 it will automatically pull in signals.
Um, 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
I don't know about all the history of the channel, I'd have to go and read up about the channel.
Well, the advice is changing.
I do know that.
I guess that's 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 in through app.ply.
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 um by using a signal, you take a relation between say two models in your project and you hide it somewhere.
And you you you you you 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 insight 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 your 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 um your app config, and then you you call it that in the ready handler.
That ready handler is only called once the um 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 of process.
Makes sense to me.
Um so in general, in terms of ramping up, uh 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 um it's it's fun to do, but uh 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 uh too often.
Well, but the but the the the evolution is natural as well because you 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 and 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 forget about.
And this is where people get into all sorts of troubles managing state.
And one signal, it's not gonna kill you.
Two signals they're not gonna kill you, 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 it yeah, where to put the logic is just one of those tough questions.
Um But yeah, don't put it on the don't
don't don't abuse the signal.
Yeah.
Yeah, like a signal, use them, you know, like uh um the sprinklings on the top of a ferry cake, not you know, first resort.
Yeah.
Uh what else?
Is there anything else to say?
I mean, signals are I
mean, they're a nice well, I would I would say look, signals are a nice pattern in that they 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 publish subscribe kind of thing.
It's like, yeah, okay, I could fan out by using signals.
I could fan out a message by using signals.
That's not a bad meta uh way of using it.
Um but again, just be conscious.
Could you maintain a list and call those at the same point where you called the whatever triggered the signal?
Yeah, you probably could.
Cool.
All right, well, hopefully that will that will help folks again.
We're 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 uh rules of thumb for when to reach for these tools.
Because Django has a lot of cool tools.
Which batteries include it, isn't it?
Anyway, right, that'll do.
Thanks for joining us uh with um Chat Django, Django Chat, with Chat Django on Twitter and Django chat everywhere else.
All right.
All right, see you all next time.
See you next time.
Bye bye.