My Experience With Django Signals
I have been using django signals for quite sometime and I absolutely love it the way it works. Its an extremely useful tool when used can create beautiful algo-flow. I will explain use of django signals with a practical scenario.
Definition: In telephony, signaling is the exchange of information between involved points in the network. We can apply the same logic here in Django as well but a little more than that in practical. When you want to execute a chunk of code when a condition is met we can use signals. Eg: Lets say you want to email a user when he creates a new account in your application. You can use django signals to do that. There are a couple of tutorials on the web that explain how to send an email.
My Scenario: I am developing an application which tracks the expenses among a group of users. So basically I have a User model with Group having ManytoMany relation with User. So I created a Debt model which looks like this.
class Debt(models.Model): """The debt class for account management among a group""" group = models.ForeignKey(Group, related_name="belongs to") payer = models.ForeignKey(User, related_name="paid group expense") description = models.TextField('description') tags = TagField() amount = models.DecimalField('amount', max_digits=10, decimal_places=2) # money $ 34.45 created = models.DateTimeField('created', default=datetime.now) individual_share = models.DecimalField('your share', editable=False, max_digits=10, decimal_places=2) cleared = models.BooleanField('is cleared', default = False)
It looks pretty self explanatory where payer is one among the group who pays for the whole group and individual share is calculated by (amount / group.members.count()). When a payer fill out the form we check if he is member of the group, if so I write into the Debt model filling in all the details. After the record is written to Debt, we need a way to track the individual share of the group expense. So I created a new model Userdebt which keeps record of each user and the amount he owes to each group.
class Userdebt(models.Model): from_user = models.ForeignKey(User, related_name="from user") to_user = models.ForeignKey(User, related_name="to user") tribe = models.ForeignKey(Tribe) amount = models.DecimalField('amount', max_digits=10, decimal_places=2) created = models.DateTimeField('created', default=datetime.now) cleared = models.BooleanField('is cleared', default = False) reason = models.TextField('purpose')
In this model from_user will be member who needs to pay and to_user is the payer in Debt model. I created a cleared boolean field where I can set to true once User clears his share.
So when ever a record is written into Debt model we need to create individual records for all the users in that group in Userdebt model. I used signals for this. I simply place this chunk if code under _Debt_model.
So whenever a record is written into Debt model the above piece of code calls create_userdebt function. signals is a class in django.db.models which sends signal when a criteria is met. In this scenario the criteria is post_save, which means call the function after saving the record to database. In the same way we can user pre_save, post_syncdb etc. You can check a list of all signals here. You can also use pre_delete/ post_delete to delete all the Foreign related objects in other models when an object is deleted in a model.
def create_userdebt_item(sender, instance, signal, *args, **kwargs): if 'created' in kwargs: if kwargs['created']: for member in instance.tribe.members.all().exclude(pk=instance.payer.id): u = Userdebt(from_user=member, to_user=instance.payer, tribe=instance.tribe, amount=instance.individual_share, created=instance.created, reason=instance.description) u.save() # Add logic to email each user as well.
This function handles the logic to create individual share in Userdebt model. With the help of instance we can access the attributes of the record which was saved in Debt model. You can see the I am excluding the payer from the logic. (It does n’t make sense to pay to himself.) We can also check if from_user = to_user condition but I felt excluding payer from queryset would make more sense. In this way we can apply some logic as well with signals to create some useful applications.
Another useful tip I encountered via Magus- was limiting choices of a ForeignKey based on the logged in User. I have a ForeignKey relation from BankAccount to User. While clearing debts I need to show list of a BankAccounts a user possess. A ForeignKey would simply show all the accounts of all the users. We need to limit this by querying.
class DebtclearForm(forms.ModelForm): def __init__(self, user, *args, **kwargs): super(DebtclearForm, self).__init__(*args, **kwargs) self.fields['bank'].queryset = Bank.objects.filter(user=user) debt = forms.CharField() class Meta: model = Ledger fields = ('bank', 'description', 'debt',)
Define an init function which will overide the default behavior of the Form. Here I am inheriting all the features of the base class(DebtclearForm) but just over-riding the behaviour of bank attribute which should show only accounts related to the user. Simply modify the queryset to your requirements. One other thing to note is when instantiating a form in a view, remember to pass in the user argument.
# In views.py debtclear_form = DebtclearForm(request.user) # Just instantiating the form and passing it to template debt_form = DebtclearForm(request.user, request.POST) # Grabbing the post data from the form.
You can also create your own custom signals and use the django dispatcher to call a function when a criteria is met. Chris Pratt wrote a wonderful tutorial on django custom signals and asynchronous signaling. Do check that out.