Django ORM: Models with 2 table referencing each other - python

I have 2 tables. User and Group. 1:Many relationship. Each user can only belong to a single group.
here's the model.py.
class Group(models.Model):
group_name = models.CharField(max_length=150, blank=True, null=True)
group_description = models.TextField(blank=True, null=True)
group_creator = models.ForeignKey(User, models.DO_NOTHING)
class User(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
...
group = models.ForeignKey(Group, models.DO_NOTHING)
The issue I have is that they are both referencing each other which is acceptable in MySQL and Oracle, but, I get an error when migrating:
group_creator = models.ForeignKey(User, models.DO_NOTHING)
NameError: name 'User' is not defined
Now when I reverse the order (so, User first than Group), I get
group = models.ForeignKey(Group, models.DO_NOTHING, blank=True, null=True)
NameError: name 'Group' is not defined
This is getting quite frustrating. I have a few work around (make it a many:many and keep creator on Group class), but before I start destroying my datamodel and move data move all the data around, I wonder if anyone has this issue before. How did you solve this? Do you really have to change your datamodel?

as Pourfar mentioned in a comment, you may avoid the NameError via the quoting the model object as string. also it is safe to set related_name for accessing this relation.
class Group(models.Model):
...
group_creator = models.ForeignKey('User', related_name='creator_set')
and then, with your constraint,
Each user can only belong to a single group.
in that case, OneToOneField is more appropriate.
class User(models.Model):
...
group = models.OneToOneField(Group)
then you can access the relations as follows:
# USER is a User object
GROUP_BELONGED = USER.group # access to 1-1 relation
GROUP_CREATED = USER.creator_set.all() # reverse access to foreignkey relation
# now GROUP_BELONGED is a Group object
CREATOR = GROUP_BELONGED.group_creator # access to foreignkey relation

Add related_name to your ForeignKey fields:
class Group(models.Model):
group_name = models.CharField(max_length=150, blank=True, null=True)
group_description = models.TextField(blank=True, null=True)
group_creator = models.ForeignKey('User',related_name='myUser')
class User(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
group = models.ForeignKey('Group', related_name='MyGroup')

Related

Django OneToOneField allow online one reference

I am trying to create a one to one reference and want to make sure that that reference is not allowed to be used for another model or instance.
For example
Say I have an address model, Person Model and Company Model
Person has a OneToOneField field to Address
Company also has a OneToOneField field to Address
address=Address(data="some address")
company=Company(name="some company",address=address)
person=Person(name="my name",address=address)
Models:
class Address(models.Model):
data = models.CharField(max_length=255, blank=True, null=True)
class Company(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
address=models.OneToOneField(Address,on_delete=models.CASCADE)
class Person(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
address=models.OneToOneField(Address,on_delete=models.CASCADE)
I would like the system to throw an error on this since I am setting the same address to 2 different models.
Also this would delete both person and company if I delete address.
Usually you catch this with code and not make a stupid mistake like this.
But can system catch it since it is one to one ?
In the case of the deletion you could use on_delete=models.PROTECT. In the other case you could add unique=True so a person id = 1 will have a address id = 1, a person id = 2 can't have a address id = 1 anymore. But it would only solve for one model:
address=models.ForeignKey(Address, unique=True, on_delete=models.PROTECT)
A new approach would be create a model to reference the address of both company and person and be able to forbid the creation with the same address id:
class AddressExample(models.Model):
id_address = models.ForeignKey(Address, unique=True,on_delete=models.PROTECT)
id_person = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
id_company = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
Note that I used blank=True, null=True so you can create an instance only with a Person or a Company, without the need to create a instance with both. There is a Meta to use combination of primary keys too.
class AddressExample(models.Model):
id_address = models.ForeignKey(Address, unique=True,on_delete=models.PROTECT)
id_person = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
id_company = models.ForeignKey(Person, blank=True, null=True, unique=True, on_delete=models.PROTECT)
class Meta:
unique_togther = ('id_address', 'id_person', 'id_company')
# Not sure if it will throw a error here because `id_person` and `id_company` can be blank
# or null. But the use of `unique together` is for cases where you want to guarantee
# the combination of the primary keys will be unique.
Hope it helps.

Adding additional attributes to a Django field

Consider this Family model in Django:
class Family(models.Model):
EMPLOYEE = 'Employee'
PARTNER = 'Partner'
BIRTH_PARENT_CHOICES = (
(EMPLOYEE, EMPLOYEE),
(PARTNER, PARTNER),
)
employee_user = models.OneToOneField(User, blank=True, null=True, related_name='employee_family')
partner_user = models.OneToOneField(User, blank=True, null=True, related_name='partner_family')
employee_first_name = models.CharField(max_length=255, blank=True)
employee_last_name = models.CharField(max_length=255, blank=True)
employee_email = models.CharField(max_length=255, blank=True)
employee_phone = models.CharField(max_length=255, blank=True)
partner_first_name = models.CharField(max_length=255, blank=True)
partner_last_name = models.CharField(max_length=255, blank=True)
partner_email = models.CharField(max_length=255, blank=True)
partner_phone = models.CharField(max_length=255, blank=True)
point_of_contact = models.CharField(max_length=255, choices=BIRTH_PARENT_CHOICES)
A Family consists of an employee and a partner, both of which have various attributes (user, first name, last name, email, phone). There is also a point_of_contact field which is either 'Employee' or 'Partner'.
What I'd like to be able to do is to, on an instance family of Family, do something like
family.point_of_contact.phone_number
which would resolve to family.employee_phone_number if family.point_of_contact == Family.EMPLOYEE and family.partner_phone_number otherwise, and similarly for first_name, last_name, etc.
As far as I can tell from https://docs.djangoproject.com/en/2.0/ref/models/fields/, however, it isn't possible to define additional attributes on Django fields. Is there some other way I could do this?
No, in order to do that, you would need to create a separate model Contact and join to it from Family using a OneToOneField if there can only be one contact per family, or using a ForeignKey in your Contact model if there can be more than one contact per family.
Django doesn't provide a way to do this, but you can do it with some simple Python:
from types import SimpleNamespace
class Family(SimpleNamespace):
EMPLOYEE = 'employee'
PARTNER = 'partner'
#property
def contact(self):
return SimpleNamespace(**{
attr: getattr(self, '%s_%s' % (self.point_of_contact, attr))
for attr in 'first_name last_name'.split()
})
family = Family(
employee_first_name='Kurt',
employee_last_name='Peek',
partner_first_name='Jane',
partner_last_name='Doe',
point_of_contact=Family.EMPLOYEE,
)
print(family.contact.first_name)
print(family.contact.last_name)
Here SimpleNamespace is used in two ways:
As a superclass of Family to make this example easy to test - skip that and stick to models.Model.
In the contact property, keep that.

Fetching model instance from a multiple direct relationship

Can anyone help me fetch data from this model structure? because i have a hard time doin this for hours now.
First I would like to get all distinct SubSpecialization from all Doctor which has a given Specialization.title
Secondly I would like to get all Doctor which has a specific Specialization.title and has no SubSpecialization.
Here is the Doctor model
class Doctor(models.Model):
name = models.CharField(max_length=50)
room_no = models.IntegerField()
floor_no = models.IntegerField()
contact_no = models.CharField(max_length=50, blank=True, null=True)
notes = models.CharField(max_length=70, blank=True, null=True)
This is the model Doctor relationship is connected to Specializationand SubSpecialization.
class DoctorSpecialization(models.Model):
doc = models.ForeignKey(Doctor, models.DO_NOTHING)
spec = models.ForeignKey('Specialization', models.DO_NOTHING)
class DoctorSubSpecialization(models.Model):
doc = models.ForeignKey(Doctor, models.DO_NOTHING)
sub_spec = models.ForeignKey('SubSpecialization', models.DO_NOTHING)
This is where i would make a criteria.
class Specialization(models.Model):
title = models.CharField(unique=True, max_length=45)
point = models.IntegerField()
class SubSpecialization(models.Model):
title = models.CharField(max_length=100)
There is no direct relationship between the Specialization and SubSpecialization please help.
Firstly, your specialization and subspecialization are both many-to-many relationships with Doctor. You should declare that explicitly, and drop those intervening models unless you need to store other information on them.
class Doctor(models.Model):
...
specializations = models.ManyToManyField('Specialization')
subspecializations = models.ManyToManyField('SubSpecialization')
Now you can query for all the subspecializations for doctors who have a specific specialization:
SubSpecialization.objects.filter(doctor__specialization__title='My Specialization')
Your second query doesn't make sense given the fact there is no relationship between specialization and subspecialization, you'll need to clarify what you mean by "no subspecialization in a specific specialization".
Edit
To find doctors who have a specific Specialization and then no subspecializations at all:
Doctor.objects.filter(specialization__name="My Specialization",
subspecialization=None)

Add m2m-through relation from two different classes to the same model

I have an existing applicaton where I have to change a regular m2m relationship to a through relation.
I need the ability to add extra parameters to the relation.
My problem is that I need to do it in two places.
This is the standard through example.
class Person(models.Model):
name = models.CharField(max_length=255)
class Group(models.Model):
name = models.CharField(max_length=255)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
I have another model that needs a m2m relation to Membership. My suggestion is the following.
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group, null=True, blank=True)
another_group = models.ForeignKey(AnotherGroup, null=True, blank=True)
class AnotherGroup(models.Model):
name = models.CharField(max_length=255)
members = models.ManyToManyField(Person, through='Membership')
It bugs me that I have to have one field that is null for every instance, or is this my only option?
Another option is using content type in through table.
https://docs.djangoproject.com/en/1.9/ref/contrib/contenttypes/#generic-relations
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Membership(models.Model):
person = models.ForeignKey(Person)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')

Can't get a handle on a Django ManyToMany Model object

I have a simple example of a Member, and a MemberGroup class in a Django app. I would like the following data representation, but I'm not sure if I'm complicating things by using a ManyToMany relationship:
Member:
1 - Member1
2 - Member2
3 - Member3
Group:
1 - Group1
2 - Group2
3 - Group3
MemberGroup:
1 - Member1/Group1
2 - Member1/Group2
3 - Member2/Group3, etc.
I have the following classes:
class Member(models.Model):
nickname = models.CharField(max_length=30, blank=False)
paid = models.BooleanField(default=False)
class MemberGroup(models.Model):
member = models.ManyToManyField(Member)
group_name = models.CharField(max_length=50, db_index=True)
description = models.CharField(max_length=255)
I'd like to be able to use the combo of id/member/group in other model classes (say maybe BlogPost(MemberGroupId, post), but I'm not sure how to get a handle on that particular object. The current model generates the correct database tables, I'm just not sure how to get a handle on an object that represents the link table. In the Django shell, when I get MemberGroup(id=1) and try printing the member associated with that MemberGroup, I get
<django.db.models.fields.related.ManyRelatedManager object at 0x2ae6c10>
Do I need to create another class, Group, and then have Member and Group be foreign keys in the MemberGroup class to accomplish what I need or can I use my current setup? Thanks for any help!
First, I would do the models like this:
from django.db import models
class Group(models.Model):
group_name = models.CharField(max_length=50, db_index=True)
description = models.CharField(max_length=255)
class Member(models.Model):
nickname = models.CharField(max_length=30, blank=False)
paid = models.BooleanField(default=False)
groups = models.ManyToManyField(Group)
Then you could do this:
>>> test_group = Group(group_name='Admin', description='Admin group')
>>> test_group.save()
>>> m1 = Member(nickname='Robert')
>>> m1.save()
>>> m1.groups.add(test_group)
>>> m1.save()
>>> m1.groups.all()
[<Group: Group object>]
>>> test_group.objects.get(group_name='Admin').member_set.all()
[<Member: Member object>]
That code creates a group "Admin", then a member "Robert". It associates the "Robert" user to the "Admin" group. I can then return all members of the "Admin" group using member_set.
EDIT: There's nothing exactly wrong with putting the ManyToMany field in the Group model to refer to the members, but it seems (to me, anyway) more logical to associate a member to a group rather than a group to a member.
So reading the docs again related to the many_to_many relationship yielded the correct solution for me, which was using this "through" designation on a model. My models now look like this:
class Member(models.Model):
nickname = models.CharField(max_length=30, blank=False)
paid = models.BooleanField(default=False)
class Group(models.Model):
group_name = models.CharField(max_length=50, db_index=True)
description = models.CharField(max_length=255)
created_by = models.ForeignKey('Member', related_name='created_by')
created_date = models.DateTimeField(auto_now=True)
members = models.ManyToManyField(Member, through="Membership")
def __unicode__(self):
return self.group_name
class Membership(models.Model):
member = models.ForeignKey(Member)
group = models.ForeignKey(Group)
active = models.BooleanField(default=True, db_index=True)
join_date = models.DateTimeField(auto_now=True)
league_manager = models.BooleanField(default=False, db_index=True)
def __unicode__(self):
return "%s (%s)" % (self.member, self.group)
and now I can get a handle on the Membership class where as before, the relationship was created, but there was no explicit object for me to use. Thanks everyone.

Categories