0

edit- updated models.py models.py:

from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver

class Department(models.Model):
    departmentName = models.CharField(max_length=100)

    def __str__(self):
        return self.departmentName

class Designation(models.Model):
    designationName = models.CharField(max_length=100, null=True)

    def __str__(self):
        return self.designationName
 
class Employee(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True, blank=False)
    department = models.ManyToManyField(Department, blank=True)
    designation = models.ForeignKey(Designation, null=True, on_delete=models.SET_NULL)

    def __str__(self):
        return self.name
    
    def departments(self):
        return ",".join([d.departmentName for d in self.department.all()])

#Using post receiver signal as suggested by @Daniel in answer and comments

@receiver(post_save, sender=Employee, dispatch_uid='set_department')
def SetDefaultDeparment(**kwargs):

    employee = kwargs['instance']
       
    if not employee.department.all(): 

        resource_pool = Department.objects.get(departmentName="RP")

        employee.department.add(resource_pool)
        employee.save()
                 

My query/requirement: Employee and Department are linked by M2M. I set blank=True to overcome the form validation of no department being chosen. (null=True doesn't seem to be working with M2M)

Let's say,

scenario 1: while creating a new Emp, if there is no department provided then it should set to RP(Resource pool) by default.

scenario 2: An existing Emp is from the HR department; assuming for some reason the HR dept is being deleted or Emp is being kicked out, now that Emp has no department linked to it, it should move to RP(Resource pool) by default!!

How to arrange the default department- RP in Django such that both the scenarios are passing!?

Thanks in advance :)

Dueces

Tathya Kapadia
  • 67
  • 1
  • 1
  • 9

1 Answers1

1

As suggested here The right way to set the default value of a ManyToMany field in django, you have a few options.

Here is how to do so by overriding the save method:

class Employee(models.Model):

    ...
    
    def save(self): 

        # set department if field is empty:        
        if not self.department.all(): 

            # get the resource pool department:
            resource_pool = Department.objects.get(name='Resource Pool')

            # add to instance:
            self.department.add(resource_pool)
        
        super(Employee, self).save(*args, **kwargs)

Here is how to do so using a post_save receiver:

from django.db.models.signals import post_save
from django.dispatch import receiver

# this decorator will execute the function after an Employee is saved:
@receiver(post_save, sender=Employee, dispatch_uid='set_department')
def SetDefaultDeparment(**kwargs):
    
    ''' sets a default department for an Employee where needed '''
    
    # unpack kwargs:
    employee = kwargs['instance']

    # set department if field is empty:        
    if not employee.department.all(): 

         # get the resource pool department:
         resource_pool = Department.objects.get(name='Resource Pool')

         # add to instance:
         employee.department.add(resource_pool)
         employee.save()
Daniel
  • 3,228
  • 1
  • 7
  • 23
  • Exception Value: "" needs to have a value for field "id" before this many-to-many relationship can be used. – Tathya Kapadia Oct 08 '20 at 04:56
  • 1
    Great catch - that happens on a new employee as the id is only created after the new employee instance is saved (not before, hence the error). We can use a post_save receiver to add a default department in this case (and all cases) - see my updated answer. – Daniel Oct 08 '20 at 15:18
  • Hey @Daniel, I tried the update. Throwing no exception now but unfortunately, the department is still not getting added! (coz no new M2M relation created) While debugging found this- 'Manager isn't accessible via Employee instances'. Added this line- print(employee.department) before save(), it prints employee_app.Department.None – Tathya Kapadia Oct 09 '20 at 08:19
  • Can you share your edited code? Are you using the post save receiver? – Daniel Oct 09 '20 at 15:08
  • Hey, @Daniel I Updated the code in the question itself. – Tathya Kapadia Oct 12 '20 at 05:23
  • Oddly enough, employee.department.all() (in post save receiver) returns an empty queryset even though the departments are set for an employee. I tried using employee.department.exists(), no luck with that! – Tathya Kapadia Oct 12 '20 at 08:34
  • 1
    For an employee without any departments it should come up as an empty queryset and work like `False` - for employees with at least one department it should not be an empty queryset and work like `True` – Daniel Oct 12 '20 at 14:51
  • Yes, even I expected the same. But it's not working that way. Is there any other way like using post_add_receiver or maybe handling the requirement on DRF side? – Tathya Kapadia Oct 12 '20 at 14:56
  • I'm not aware of other solutions - you can try changing the overridden save method to also check for empty primary keys (new employee instance) to handle the previous error - likely you'll run into the same issue. Where are you saving the employee object and how do you know it definitely has a department? – Daniel Oct 12 '20 at 15:02
  • I am using SQLite as db. An employee obj may or may not has a department linked to it while creation; as we have kept blank=True for it. – Tathya Kapadia Oct 13 '20 at 05:09
  • Where as in where in your code? From the admin panel, in a view? Can you please share your code if possible. – Daniel Oct 13 '20 at 14:14
  • Sure. Here is the link to the code: https://github.com/tathya1/Django_Cybage_Assignment Post save receiver is yet to be pushed to repo. Everything else is there. Have a look, let me know if you find anything. Kudos :) – Tathya Kapadia Oct 13 '20 at 14:38
  • @TathyaKapadia did you find a solution? – Kwaku Biney Apr 03 '21 at 00:43