How To Operate Foreign Key And Many To Many Field In Django Model

This example is based on the article Django Bootstrap3 Example, it reuse the Department and Employee model class. Suppose Department and Employee model are many to many relationships, that means one department can have multiple employees and one employee can belong to multiple departments. Then we need to do the following changes.

1. Change models.py File.

  1. Just change the Employee model class definition in DjangoHelloWorld / dept_emp / models.py file. We set Employee model class’s dept field type to models.ManyToManyField(Department) now.
    # create Employee model, this model will be mapped to table user_register_login_employee in sqlite db.
    class Employee(models.Model):
    
        # Employee has two foreign key, user(Django built-in auth_user table) and department.
        user = models.ForeignKey(User, on_delete=models.CASCADE,)
    
        # support department and employee table are many to many relationships.
        dept = models.ManyToManyField(Department)
    
        emp_mobile = models.CharField(max_length=100)
        # should add the () after IntegerField, otherwise the emp_salary column will not be created.
        emp_salary = models.IntegerField()
        # if do not specify the auto_now=True attribute with value then this field can not be created.
        emp_onboard_date = models.DateTimeField(auto_now=True)
    
        # this method will be called when other model reference Employee model as foreign key.
        def __str__(self):
            return self.user.username + ',' + str(self.emp_salary) + ',' + str(self.emp_mobile)
    
        class Meta:
            unique_together = ['emp_mobile']

2. Change admin.py File.

  1. We also need to change the EmployeeAdmin model admin class in the admin.py file, we need to remove ‘dept’ from the EmployeeAdmin‘s list_display list values. Otherwise, you will get the below error.
    ERRORS:
    <class 'dept_emp.admin.EmployeeAdmin'>: (admin.E109) The value of 'list_display[2]' must not be a ManyToManyField.
  2. DjangoHelloWorld / dept_emp / admin.py
    class EmployeeAdmin(admin.ModelAdmin):
        
        # a list of displayed columns name, the user and dept foreign key will display related model's __str__(self) method returned string value.
        
        list_display = ['id', 'user', 'emp_mobile','emp_salary','emp_onboard_date']
    
    # Register your models to admin site, then you can add, edit, delete and search your models in Django admin site.
    admin.site.register(Employee, EmployeeAdmin)

3. Migrate The New Models To Create Related Database Tables.

  1. After the model class makes changes, we need to migrate the changes to back-end database tables follow the below steps.
  2. Open a terminal and go to the DjangoHelloWorld project root folder.
  3. Execute $ python3 manage.py makemigrations dept_emp in terminal to get the model class changes.
  4. Then run $ python3 manage.py migrate dept_emp to apply the model class changes to the database.
  5. Use your favorite SQLite3 database file explorer to open sqlite3.db file in the DjangoHelloWorld project root folder.
  6. Then you will find there are three tables that have been created, they are dept_emp_departmentdept_emp_employee, and dept_emp_employee_dept.
    django-model-class-many-to-many-sqlite3-db-tables

4. Populate Test Data.

  1. Before continue, we need to add some test data for demo usage.
  2. Add below users in this Django project admin website like below.
    add-users-in-django-admin-web-site
  3. Add three departments to the Django project admin website also.
    add-departments-in-djang-admin-web-site
  4. Add some employees also, please note the employee is identified by mobile number ( each employee’s mobile number is unique ), and there are two employees who use the same user name jack. And each employee can attend multiple departments.
    add-employees-in-django-admin-web-site

5. Operate On Model Class Foreign Key Field.

From the above Employee model class definition, we see that it has a foreign key type field, and the foreign key model class is django.contrib.auth.models.User. We can manage the foreign key field as below.

5.1 Retrieve Employee Foreign Key Field Information.

  1. Open a terminal and go to the Django project root folder, then run the command $ python3 manage.py shell to go to Django shell console.
  2. Run the below command to get employee user information.
    >>>  from dept_emp.models import Employee
    
    >>>  Employee.objects.get(id=1)
    <Employee: jack,100000,13901234567>
    
    >>>  emp = Employee.objects.get(id=1)
    
    >>>  emp.user
    <User: jack>
    
    >>>  emp.user.email
    '[email protected]'

5.2 Get Same User Mapped Employee Objects.

  1. In the Django shell console run below command, we can get the multiple Employee objects that use the same user name. Please note the user.employee_set attribute usage, this is a QuerySet object which can run all QuerySet functions. Because User is a foreign key of Employee, so the user object has the employee_set attribute, this is done by Django automatically.
    >>>  from django.contrib.auth.models import User
    
    >>>  user = User.objects.get(username='jack')
    
    >>>  user.employee_set.all()
    <QuerySet [<Employee: jack,100000,13901234567>, <Employee: jack,100000,13690909099>]>
    
    >>>  user.employee_set.all().filter(emp_mobile=13690909099)
    <QuerySet [<Employee: jack,100000,13690909099>]>

6. Operate On Model Class Many To Many Fields.

6.1 Get One Employee Related Departments.

  1. Below source code will get one employee-related department object.
    >>>  from dept_emp.models import Department, Employee
    
    >>>  emp = Employee.objects.get(emp_mobile=1369090909)
    
    >>>  emp.dept.all()
    <QuerySet [<Department: Quality Assurance,Responsible for company website quality and test.>, <Department: Market,Market plan and implement.>]>
    
    >>>  emp.dept.filter(dept_name__contains='Market')
    <QuerySet [<Department: Market,Market plan and implement.>]>

6.2 Add A New Department To Employee.

  1. If you want to add a new department to one employee, you can run the below source code.
    >>>  from dept_emp.models import Department, Employee
    
    >>>  emp = Employee.objects.get(emp_mobile=1369090909)
    
    >>> emp.dept.add(Department.objects.create(dept_name='IT', dept_desc='Information tech support'))
    
    >>> emp.dept.all()
    <QuerySet [<Department: Quality Assurance,Responsible for company website quality and test.>, <Department: Market,Market plan and implement.>, <Department: IT,Information tech support>]>

6.3 Create A New Employee Then Add A New Department To It.

  1. Below source code will create a new Employee object and then add a new Department to it.
    In [1]: from dept_emp.models import Employee, Department
    
    In [2]: from django.contrib.auth.models import User
    
    In [3]: emp = Employee.objects.create(user=User.objects.get(username='jerry'), emp_mobile=13919283928, emp_salary=80000, sex='F')
    
    In [4]: emp.dept.add(Department.objects.create(dept_name='Market',dept_desc='Markete department.'))
  2. Because Employee and Department are many to many relationships, so you can not set dept when creating the Employee, you can add Department after emp has been created otherwise you will encounter below errors.
    In [5]: emp = Employee.objects.create(user=User.objects.get(username='jerry'), dept=Department.objects.get(dept_name='Market'), emp_mobile=13919283928, emp_salary=80000, sex='F')
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-5-0bfb7fc93e05> in <module>()
    ----> 1 emp = Employee.objects.create(user=User.objects.get(username='jerry'), dept=Department.objects.get(dept_name='Market'), emp_mobile=13919283928, emp_salary=80000, sex='F')
    
    ~/anaconda3/lib/python3.6/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
         80         def create_method(name, method):
         81             def manager_method(self, *args, **kwargs):
    ---> 82                 return getattr(self.get_queryset(), name)(*args, **kwargs)
         83             manager_method.__name__ = method.__name__
         84             manager_method.__doc__ = method.__doc__
    
    ~/anaconda3/lib/python3.6/site-packages/django/db/models/query.py in create(self, **kwargs)
        409         and returning the created object.
        410         """
    --> 411         obj = self.model(**kwargs)
        412         self._for_write = True
        413         obj.save(force_insert=True, using=self.db)
    
    ~/anaconda3/lib/python3.6/site-packages/django/db/models/base.py in __init__(self, *args, **kwargs)
        477                     if prop in property_names or opts.get_field(prop):
        478                         if kwargs[prop] is not _DEFERRED:
    --> 479                             _setattr(self, prop, kwargs[prop])
        480                         del kwargs[prop]
        481                 except (AttributeError, FieldDoesNotExist):
    
    ~/anaconda3/lib/python3.6/site-packages/django/db/models/fields/related_descriptors.py in __set__(self, instance, value)
        535         raise TypeError(
        536             'Direct assignment to the %s is prohibited. Use %s.set() instead.'
    --> 537             % self._get_set_deprecation_msg_params(),
        538         )
        539 
    
    TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use dept.set() instead.

6.4 Remove Employee Department.

  1. If you want to remove the department from an employee, you can run the below source code.
    >>>  emp.dept.remove(Department.objects.get(dept_name='Market'))
    
    >>>  emp.dept.all()
    <QuerySet [<Department: Quality Assurance,Responsible for company website quality and test.>, <Department: IT,Information tech support>]>

6.5 Get Same Department’s Employees.

  1. Below source code will get the employees that belong to the same department.
    >>>  dept = Department.objects.get(dept_name='Develop')
    
    >>>  dept.employee_set.all()
    <QuerySet [<Employee: jack,100000,13901234567>, <Employee: richard,100000,1369090908>]>

6.6 Add One Exist Employee To Department.

  1. The below source code will add one existing employee to one department.
    >>>  dept.employee_set.add(Employee.objects.get(emp_mobile=1369090909))
    
    >>>  dept.employee_set.all()
    <QuerySet [<Employee: jack,100000,13901234567>, <Employee: richard,100000,1369090908>, <Employee: jerry,80000,1369090909>]>

6.7 Add A New Employee To Department.

  1. Below source code will add a new employee to an existing department.
    >>>  dept.employee_set.add(Employee.objects.create(user=User.objects.get(username='tom'), emp_mobile=13901234568, emp_salary=10000))
    
    >>>  dept.employee_set.all()
    <QuerySet [<Employee: jack,100000,13901234567>, <Employee: richard,100000,1369090908>, <Employee: jerry,80000,1369090909>, <Employee: tom,10000,13901234568>]>

6.8 Remove Employee From Department.

  1. If you want to remove an employee from a department, please run the below source code.
    >>>  dept.employee_set.remove(Employee.objects.get(user = User.objects.get(username='richard')))
    
    >>>  dept.employee_set.all()
    <QuerySet [<Employee: jack,100000,13901234567>, <Employee: jerry,80000,1369090909>, <Employee: tom,10000,13901234568>]>

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.