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?
Related
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.
Disclaimer: I'm new to Django, so I'm still learning the Django way of doing things.
The project I'm working on has a uUser model and a Student model. Previously, the UserAdmin was doing double duty for both models, but now the time has come to create an independent StudentAdmin that allows admins to easily edit/create/etc students.
User contains base info for Student (i.e. first and last name, email, phone, etc.) and Student contains more info (i.e. parent phone, class, todos, etc.). Related models like 'Class', 'Grade', etc. have FK relationships to User, which at first didn't strike me as an issue.
But when I went to reuse the inline classes created for UserAdmin in the new StudentAdmin, I've run into this error: <class 'my_project.admin.TodoInline'>: (admin.E202) 'my_project.Todo' has no ForeignKey to 'my_project.Student. This isn't surprising, so I thought I could merely override the _init_ method on TodoInline to use parent_model.user, but then I ran into errors relating to ForwardOneToOneDescriptor and missing _meta fields. Given this use case, I believe there has to be a solution to this, but I'm currently at a loss for Django-related vocabulary for researching this issue further.
class TodoInline(admin.TabularInline):
model = Todo
fields = ['content', 'completed', 'category', 'due_date']
verbose_name_plural = "To-do's"
extra = 0
# This doesn't work:
def __int__(self, parent_model, admin_site):
super(TodoInline, self).__init__(parent_model.user, admin_site)
If the answer to this issue is to redefine the FK relationships between models, that's something I can't do at the moment. I need a solution that will allow me to reorient the inline classes (TodoInline is just one of many that I need for StudentAdmin) to use the related User model on the Student model. Thank you in advance!
Despite of your restriction not to reconfigure your foreign key, I see only one possible solution with two different modellings here.
But first of all: If you get the error, a foreign key to your model student is required, why can't you reconfigure that foreign key?
Second: You want to access the parentmodel of student and you've got an foreign key to the model user. From a modelling point of view I wonder, how that is useful - as you want to operate with a student, not with a user? Additionally, if your studentis related to user you can access all userattributes through student. So there is really no point in avoiding the reconfiguration of your foreign key.
So the only two possible (and meaningful) modellings I see are:
(1) Let the model student inherit from user, so student has all field's and methods which are implemented to user. Then reconfigure the foreign key to student (which requires $ python manage.py makemigrations <app> and $ python manage.py migrate <app>)
or
(2)
Define a one-to-one relation between student and user, so a student is uniquely to a user. This also requires to reconfigure your foreign key as describbed in (1).
If student does not differ from user there is an (3) option: Proxy models. With proxy models you can implement multiple modelAdmin for one model.
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.
Here's the scenario:
I have a Student Model with subject field connected with Many-to-Many relationship to Subject Model
class Student(models.Model):
(...)
subject = models.ManyToManyField(Subject)
In Subject Model i have a program field connected with Many-to-Many relationship to Programs Model. Subject Model got also CharField name.
class Subject(models.Model):
(...)
program = models.ManyToManyField(Programs)
In Programs Model i have a field:
class Programs(models.Model):
name = models.CharField(max_length=40)
(...)
Django creates additional table for many-to-many fields. In my application I create (with form) a Program and Subject corresponding to Program. Then i create some Students and choose some subjects.
How can i access program name field (Student.objects.all()) and display what program name Student is using ? Is it possible, or i need to create additional fields in Student Model connected with Many-to-Many relationships with Program Model ?
Am I right in thinking that you want to return a list of the program names for the programs belonging to each subject in student.subject?
If so you could use this as a method of the Student model:
def get_program_names(self)
programs = []
for subject in self.subjects:
programs += [program.name for program in subject.program]
return programs
It sounds from your question, however, that you expect only one program name to be returned. if this is the case then maybe you should replace your manyToMany fields with ForeignKey fields, which give a one to one relationship between two models, meaning that this method should work:
def get_program_name(self):
return self.subject.program.name
(either way, there is no reason you should have to create a direct link between your student model and the program model, unless you wish to reduce the number of database calls, in which case you could add a field to Student which is updated with the latest program name or similar.)
I hope that I haven't misunderstood your question.
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.