Signals are a powerful but poorly understood and widely abused feature in Django. We explain how they work and introduce Carlton's "Am I allowed to use signals" test.
Carlton Gibson 0:06
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 gonna talk about signals signals. What are they, Will?
Will Vincent 0:16
They are something that I think a lot of 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 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 a user logging in, you can do something else. So a good example would be for authentication, let's say that you have so there's Django has a built in user mode. 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 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 if you know exactly where the change is gonna happen. Maybe you don't need it. You have it you've said this more eloquent eloquently than I have, you know, will will
Carlton Gibson 2:00
I've I've a sort of strong view on this. I'm like signals, the sort of test I am I allowed to use signal here. And it's not that you should use a signal. This is like the minimum. If you can't get past this, and you're definitely not allowed to use a signal here, but the minimum test is, do I know at call 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 I, you know, the model that I'm saving, for instance, and the receiver of it. Now, let's 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, you know, I don't need this whole weird. Yeah, there's no mystery. There's no decoupling if the user example you just gave is quite good, because Thank you, it does pass this test because I'm not writing Django contrib dot auth dot models dot user, right. So when I'm writing That I don't have access to that to to my my project dot user profiles dot models dot profile,
Will Vincent 3:08
right? You want the decoupling that this is core Django versus something
Carlton Gibson 3:11
yourself. So in that case it is allowable by by cons Am I allowed to use signals test? To use a signal to create my user profile model or my profile model when a user say because I don't at the point where I'm sort of wanting the signal to this match the saving the user at that point in the code, I don't have access to both because ones in Django core and ones in my project, or Django Condor.
Will Vincent 3:36
So what would be an example of something we've rolled yourself where it'd be less clear? The receiver, okay, so
Carlton Gibson 3:42
one thing people might do is they might say, interview I want to send an email. So they might use a sim there. And 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 get 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, 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 is that you're going to 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.
Will Vincent 4:28
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
Carlton Gibson 4:34
queue. But don't use a signal that if you've got a task delay defer thing, which will put it on to 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 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 right right right. But don't wait because you know, both the sender and receiver at runtime that call time you know those people, right? Don't use the signals magnetic mechanism to hide that connection somewhere else in your codebase. Out of sight, right.
Will Vincent 5:11
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 or something else where you you want to decouple into a third time app, you
Carlton Gibson 5:21
know, we had the plug a plug in system and the plug in authors need to know when certain things happen,
but they can't obviously come in and write the
code base and pulling their models in there. So a signals mechanism, in that case is great. It's a nice way of extending,
Will Vincent 5:39
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.
Carlton Gibson 5:48
And that's how we find these monitoring packages that you put it in or try and hook into signals as well when they could just wrap your application in the middleware because I mean, they don't want to do that because it's too hard to configure. So they usually signals but then it's like totally mysterious. How this monitoring application hooks into my hooks 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 hook don't know it does the same thing because it hooks onto signals, request started requests finished or wherever. But it shouldn't be a mystery, because it's not that hard. But by using signals, you make it hard.
Will Vincent 6:22
Says the Jenga, fellow. It's not that hard to
Carlton Gibson 6:25
know. Yeah, but I know. Yeah, relatively speaking, right. It's like, it's like, hey, I've got a middleware, and I 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?
Will Vincent 6:43
Carlton Gibson 6:45
And you have to go and open the, the code of the of the of the, of the service that you're using, and you have to search through the course but there, you know, somewhere far deep, then it will be that Django integration. And you have to say, oh, they're hooking into the request, started working Oh, that now it makes sense.
Will Vincent 7:03
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 a classic example of a signal. Is would there be a good example? Maybe one of those monitoring ones demo?
Carlton Gibson 7:26
Yeah, I mean, so when would you use request started? You might use it for any you know, logging some data. Yeah,
Will Vincent 7:31
maybe a logging thing? I guess if it was a third party package.
Carlton Gibson 7:34
Maybe 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 interview or like
Will Vincent 7:42
wrangling but typically it's around exactly why saving some data or an action like sending sending an email if, if you're justified you pass Carlson's test.
Carlton Gibson 7:52
Yeah, the Am I allowed to use a signal too
Will Vincent 7:55
wide. So in just in terms of logistics, so there are four others a number of Built in signals that Django gives us around, pre save, post, save, pre delete, post delete. I think those are well named. Actually this interesting one. So, MTM changed. So when a many to many field has been changed, because that sort of gets
Carlton Gibson 8:14
tricky when you have nine too many's Yeah, and it's also tricky. The signal is tricky, okay, because if you call ad with a model that's already on the relation, Django has to filter that model out. That instance out because otherwise you get an integrity error, because you say it was already in the relation you get an integrity error. So when the signal is fired, any any instances that were already on the relation get filtered out and you don't see them as being submitted. But with for 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 in the database. one complaint 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 was for the instances that was removed from the relation. But when they're added, they only get the ones that were actually
Will Vincent 9:03
like, in strict. So another thing, actually, and since you would know this, Carlton, so where to place the signals? So historically, people said, I think like around one six or something that 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 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. And if you don't, you have to update the init.pi 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
Carlton Gibson 9:51
Well, I don't know about all the history of the town. You don't know about what the advice is, I don't know.
Will Vincent 9:57
What I would say to folks is don't put it in your model.py file that used to 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. Okay, let's run through app. So let's
Carlton Gibson 10:09
think about 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 caught, there's a state management project, and it becomes difficult to manage. So one way of keeping this kind of this kind of decoupling InSite 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 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 app support related, and the orange ready to use, so you can do whatever you need to do there. That would be my sort of guess as to the evolution.
Will Vincent 11:13
Yeah. Thanks. This makes sense to me. So in general 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, you 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.
Carlton Gibson 11:38
But with with the evolution is natural as well, because if you start building, you start building a nice, simple model. It's all going great. And then you add more logic and you have more logic, it gets really complicated. 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 is 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 leave, 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 it's really forget about and this is where people get into all sorts of troubles managing state and one signal. It's not going to kill you to signals they're not going to get but eventually it's like, actually, my project is on man, right?
Will Vincent 12:22
explicit over implicit. And actually, we talk and then it's larger question of business logic. We talked about this with the edX folks, 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.
Carlton Gibson 12:39
But yeah, don't put don't
Will Vincent 12:40
don't don't abuse the signal. Yeah.
Carlton Gibson 12:43
Yeah, like a signal. Use them. You know, like a sprinkling is on the top of a fairy cake. No, you know, first result,
Will Vincent 12:49
yeah. What else? Is there anything else to say? I mean, signals are,
Carlton Gibson 12:54
I mean, they were nice. What I would, I would say look, signals are a nice pattern in that there's this the coupling This is 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 can fan out by using signals good find out a message by using it. That's not a bad method way of using it. But again, just be conscious. Could you maintain the list and color those at the same point where you called whatever triggered the signal? Yeah.
Will Vincent 13:24
Cool. All right. Well, hopefully that will 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,
Carlton Gibson 13:41
especially is included. Anyway, right, that'll do. Thanks for joining us with Django Django chat. We chat Django on Twitter, and Django chat everywhere else.
Will Vincent 13:51
Alright, see y'all next time. Bye.