How to support user polymorphism in Django without raw sql? - django

Subtitle: when the student becomes the teacher
Imagine two classes, Student and Teacher, that each inherit from User. None of these models is abstract. How to you modify an instance of student so that they can also be a teacher, without just dropping into raw sql and inserting the teacher record?

First, you have to think about your design. Not in terms of Django, but generally.
If you have a User class, and a Student class and a Teacher class, then you can create instances of User, of Student and of Teacher. You cannot create an instance that is both a student and a teacher, unless you create a fourth class StudentTeacher that inherits from both Student and Teacher. This is a special case of multiple inheritance, called "diamond shape" inheritance and it is rarely a good idea.
Django does support inheritance for its models. If B inherits from A, and A isn't declared abstract to Django, then Django will create a table for A and a table for B with a foreign key to A and it will join the tables B and A if you access an instance of B.
But I don't think that Django supports multiple model inheritance, which you would need here. And even if it does, it will lead you into a world of pain.
The solution is, I think, to favor composition over inheritance. Instead of creating User, Student, Teacher and StudentTeacher, you create User, StudentRole and TeacherRole.
Then, you can compose the user with the student role or the teacher role, or both. The user has a teacher role or has a student role.
In Django, you use a OneToOneField for this:
class User(Model):
name = CharField()
class StudentRole(Model):
user = OneToOneField(User, related_name='student_role')
average_grade = FloatField()
class TeacherRole(Model):
user = OneToOneField(User, related_name='teacher_role')
age = IntegerField()
`And then you create objects like this:
user = User()
user.save()
student_role = StudentRole(user=user)
student_role.save()
teacher_role = TeacherRole(user=user)
teacher_role.save()
(Of course you can also use StudentRole.objects.create and so on.)
And you can use the object like this:
user.student_role.average_grade = 4
user.save()

user = User.objects.get(whatever_id)
student = Student.objects.create(user=user)
teacher = Teacher.objects.create(user=user)
Here you have the same user having an instance in Student object and Teacher object.
But as pointed in a comment, a Student instance can't be a Teacher one. That's impossible. It's not about django.

Related

Changing a ForeignKey to a be the parent class/model

I have a situation where there are various entities, with ForeignKeys to models that are subclasses of another model. I need to update these FKs to point to the parent, so that any of the various subclasses could be linked.
For example, the code might be:
class School(Model):
...
class University(School):
...
class Student(Model):
university = ForeignKey(University)
... and I need to change that FK from Student to University, to be an FK to School.
I think that all Universities will have the same ID as the School that is in the database for that university. So is it safe/reliable to define the Student:
class Student(Model):
university = ForeignKey(University)
school = ForeignKey(School)
Then make a migration that creates the new school attribute, and uses RunPython to copy the id from university into school, then delete the university attribute and makemigrations for that?
Is it ever possible that this method would break, or produce bad data?

Get Primary key of an element(Teacher) which is referred in student model django

I am new to Django and I don't know how to get access the primary key. I have a scenerio in which there are two users teacher and student one-to-one relationship to abstract user.In my case teachers create student accounts, I have created teachers accounts successfully,I also want to save students registered by the teachers which also know how to do it,but my problems is I also want to save the teacher in student model who registered that particular student, for this I want to get that teacher object(The teacher which is adding student at that time) which I can't,can someone provide suggestions or code how to do it?
Models.py File:
class User(AbstractUser):
is_student=models.BooleanField(default="False")
is_prof=models.BooleanField(default="False")
class Teacher(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE)
designation=models.CharField(max_length=30)
class Student(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE)
teacher=models.ForeignKey(Teacher,on_delete=models.CASCADE,default="")
batch=models.CharField(max_length=30)
Below my piece of approach to add student, user is created successfully but what to do in last line student= so I can teacher object through teacher_id:
obj=User.objects.create_user(
username=myids[iterate],password=passwords[iterate],
is_student=True,is_prof=False)
obj.save()
Student.objects.create(user=obj,teacher=(What to do?),batch="F-14")
If you create a field that is a ForeignKey, Django automatically creates a "twin field" named field_id, that stores the id. Updates to the some_object.field reflect in the some_object.field_id, and vice versa.
You can thus use the teacher_id field instead here:
obj= User.objects.create_user(
username=myids[iterate],
password=passwords[iterate],
is_student=True,
is_prof=False
)
obj.save()
Student.objects.create(user=obj, teacher_id=myteacherid, batch="F-14")
An alternative is to first fetch the Teacher object with the given id, and then use this, but this is less efficient, since it will result in two queries to construct the object: one to fetch the Teacher, and one to create the Student:
obj= User.objects.create_user(
username=myids[iterate],
password=passwords[iterate],
is_student=True,
is_prof=False
)
obj.save()
my_teacher = Teacher.objects.get(pk=myteacherid)
Student.objects.create(user=obj, teacher=myteacher, batch="F-14")
You can get the teacher object and assign it to 'teacher' field in Student model.
teacher = get_object_or_404(Teacher, pk=primary_key_of_teacher)
student = Student(user=user_object teacher=teacher batch=student_batch)
student.save()

Django through relationship

I have a Person class, a Project class and Contract class. One project leads to a contract where people are hired. Now I want a class with people related with their contracts so I create another class, ContractStaff, in order to implement a through relationship:
class Person(models.Model):
name = CharField
adress = CharField
#contract_staff is a list of all the Contracts a single Person object has
contract_staff = ManyToManyField(Contract, through = 'ContractStaff')
class ContractStaff(models.Model):
person = ForeignKeyField(Person)
class Contract(models.Model):
id_ref = IntegerField
starting_date = DateField
ending_date = DateField
contract_staff = ForeignKey(ContractStaff)
project = OneToOneField(Project)
class Project(models.Model):
title = CharField
id_ref = IntegerField
...
The thing is that I've seen in other examples that is the intermediate class which has the two FK, in my example is the Contract class which has one of them, linking the other two classes. As a ContractStaff could have many Contracts and one Contract has only one ContractStaff I guess the FK field should go in the Contract class. Am I wrong? Is this model correct?
A ManyToManyField is implemented by means of a through table that has two foreign keys, to the connected tables. If you don't have that, then what you have is not a many to many field.
For instance, if you want to make it so that one Person can be connected to several Contracts, and that a Contracts can be connected to several Persons, then you need a ManyToManyField. It would be implemented with one row in the through table per connection between a person and a contract, so every connection would have exactly 1 person and 1 contract, hence the two foreign keys.
A through model is used if you also want some extra information on the connection (say, one Person is connected to a Contract as "project leader", and another as "programmer"), then you can make the model implicit and put a field on that model for that role.
What you have is not a ManyToManyField.
Edit: specifically, if a Contract can have one ContractStaff, and a ContractStaff can have one Person, then there's a many-to-one relation from Contract to ContractStaff to Person, not a ManyToMany.

django: Switch the inherited model base class to different child

The base class model and inherited models are as follow:
class Profile(models.Model):
user = models.ForeignKey(User, unique=True)
...
class Student(Profile):
...
class Teacher(Profile):
...
I have object of Student class(the data for which are stored in two tables in db - Profile table and a Student table which has a pointer to Profile table). I need to assign that Profile table row to a Teacher object and delete the Student object, i.e. without losing the content of Profile table data and also retaining the same id for that row. How it should be done?
Thank you,
Any help would be appreciated..
Behind the scenes, Django creates a field on the Student and Teacher models called profile_ptr_id. So you'll probably have to manipulate that. Because of how Django's concrete inheritance works, each Student and Teacher object's primary key is actually this profile_ptr_id field. Thus, I am not sure having both a Student and Teacher object with the same profile_ptr_ids is allowed.
One way you can try to get around this is the following:
Create a new Profile object
Set the Student object's profile_ptr_id to the id of the new Profile object
Set the Teacher object's profile_ptr_id to the id of the old Profile object that the Student object was previously pointing to.
Delete the Student object
I've never tried this, so I really can't say if it will work or not...
If you have a student that you want to turn into a teacher with some or all fields, and you can afford to type out the fields, you can just do:
teacher = Teacher(id=student.id, user=student.user,
name=student.name, email=student.email, ...)
student.delete()
teacher.save()
It's important to set the ID and delete the old object before saving the new one with the same ID.

Django, need some specified groups of users

I need let's say Instructors, Students and so on.
Both groups are users, could login etc.
But for example Instructor need many to many relationship with model Subjects.
How to achieve that?
Create a class Instructors that would inherit from Users. Within this class provide the many-to-many relationship. You could also use the profile module to identify the separation.
There are good examples of both here.
EDIT: There is also a good post by James Bennett here.
I wouldn't use inheritors here. Just create models that point to User:
class Instructor(models.Model):
user = models.OneToOneField(User)
# other fields
subjects = models.ManyToManyField('Subject')
class Student(models.Model):
# other fields
user = models.OneToOneField(User)
class Subject(models.Model):
name = models.CharField(max_length=40)
This has the benefit of keeping the common user functionality separate from the Instructor and Student functions. There's really no reason to actually treat Instructors or Students as Users.
You can't point Subject to User-who-is-Instructor model, it can't be expresed in SQL.
What you can do is e.g. to point Subject to User model, making sure in your code that you only create Subject instances for User-s that are members of Instructors group.