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

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

django project app dept_emp source files structure

1. Change models.py File.

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 relationship.
    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.

We also need to change the EmployeeAdmin model admin class in admin.py file, we need to remove ‘dept’ from the EmployeeAdmin‘s list_display list values. Otherwise you will get bellow error.

ERRORS:
<class ‘dept_emp.admin.EmployeeAdmin’>: (admin.E109) The value of ‘list_display[2]’ must not be a ManyToManyField.

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.

After model class make changes, we need to migrate the changes to back-end database tables follow below steps.

  1. Open terminal and go to DjangoHelloWorld project root folder.
  2. Execute $ python3 manage.py makemigrations dept_emp in terminal to get the model class changes.
  3. Then run $ python3 manage.py migrate dept_emp to apply the model class changes to database.
  4. Use your favorite SQLite3 database file explorer to open sqlite3.db file in the DjangoHelloWorld project root folder.
  5. Then you will find there are three tables has been created, they are dept_emp_departmentdept_emp_employee and dept_emp_employee_dept.
    django model class many to many sqlite3 db tables
READ :   How To Return Html File As Django View Response

4. Populate Test Data.

Before continue, we need to add some test data for demo usage.

  1. Add below users in this django project admin web site like below.
    add users in django admin web site
  2. Add three departments in the django project admin web site also.
    add departments in djang admin web site
  3. Add some employees also, please note the employee is identified by mobile number ( each employee mobile number is unique ), and there are two employee use same user jack. And each employee can attend multiple departments.
    add employees in django admin web site

5. Operate On Model Class Foreign Key Field.

From 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 terminal and go to Django project root folder, then run $ python3 manage.py shell command to go to django shell console.
  2. Run 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]'

4.2 Get Same User Mapped Employee Objects.

  1. In Django shell console run below command, we can get the multiple Employee objects that use same user. Please note the user.employee_set attribute usage, this is a QuerySet object which can run all QuerySet functions. Because User is 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 Field.

6.1 Get One Employee Related Departments.

>>>  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.

>>>  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.

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.'))

Because Employee and Department are many to many relationship, so you can not set dept when create 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.

>>>  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.

>>>  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.

>>>  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.

>>>  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.

>>>  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 Reply

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.