Django OneToOneField allow online one reference - python

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.

Related

problem with multiple user dependent on each other in Django

I am working with some custom-made user models in Django. They are the following:
myCustomeUser responsible for the primary identity of a user
Industry is a user that will link with OneToOneField to the myCustomeUser
Employee is another user account, which will FK to the myCustomeUser and FK to Industry
my models.py:
class myCustomeUser(AbstractUser):
id = models.AutoField(primary_key=True)
username = models.CharField(max_length=20, unique="True", blank=False)
password = models.CharField(max_length=20, blank=False)
is_Employee = models.BooleanField(default=False)
is_Industry = models.BooleanField(default=False)
class Industry(models.Model):
user = models.OneToOneField(myCustomeUser, on_delete=models.CASCADE, primary_key=True, related_name='industry_releted_user')
name = models.CharField(max_length=200, blank=True)
owner = models.CharField(max_length=200, blank=True)
license = models.IntegerField(null=True, unique=True)
industry_extrafield = models.TextField(blank=True)
Now I need to write the model of Employee. There are some conditions also:
It should contain name, National ID, gmail, rank, employee_varified, named fields
This will inherit the myCustomeUser and Industry both
The Industry account user will primarily entry all the data of Employee in the database, except username and password(which are inherited from myCustomeUser)
Later on, the Employee will search his National ID given by the Industry and finish the registration process by creating his username and password.
I have tried the Employee model like this:
class Employee(models.Model):
user = models.ForeignKey(myCustomeUser,primary_key=True, null=True, on_delete=models.CASCADE)
industry = models.ForeignKey(Industry, on_delete=models.CASCADE)
National_ID = models.IntegerField(null=True, blank=False, unique=True)
name = models.CharField(max_length=200, blank=False, null=True)
gmail = models.EmailField(null=True, blank=False, unique=True)
rank = models.CharField(max_length=20, blank=False, null=True)
employee_varified = models.BooleanField(default=False, blank=True, null=True)
But the problem with this model is I cannot create any Employee object without giving user (that means username and password), But the Industry user needs to entry their Employee's data, before complete the Employee's registration.
how can I write my Employee model to solve this problem?
If you can't guarantee that a related object will exist when you create an object, you can make the relationship(s) optional.
So in your case, I'd create your model more like;
class Employee(models.Model):
user = models.ForeignKey(
myCustomeUser,
blank=True,
null=True,
on_delete=models.CASCADE
)
industry = models.ForeignKey(
Industry,
blank=True,
on_delete=models.CASCADE
)
national_id = models.IntegerField(
null=True,
blank=False,
unique=True
)
name = models.CharField(
max_length=200,
blank=False,
null=True
)
# ... etc
You may also benefit from having a look through the following site which might help you learn a thing or two about django
https://www.django-antipatterns.com/

Django: How to conditionally define fields in a model mixin?

In my back-end (API) site, there are certain fields that are common to most models, but not all. I have been using mixins for those fields so that I can include them for the models they apply to, and omit them from the ones they don't. For example:
class AddressPhoneModelMixin(models.Model):
address = models.TextField(
verbose_name=_('Address'),
blank=True,
null=True,
)
country = models.ForeignKey(
Country,
on_delete=models.SET_NULL,
verbose_name=_('Country'),
blank=True,
null=True,
)
phone_number = PhoneNumberField(
verbose_name=_('Phone'),
blank=True,
null=True,
)
mobile_number = PhoneNumberField(
verbose_name=_('Mobile Phone'),
blank=True,
null=True,
)
fax_number = PhoneNumberField(
verbose_name=_('Fax'),
blank=True,
null=True,
)
class Meta:
abstract = True
But I have other such mixins, and when a model needs to include all fields, the model definition gets to be quite long:
class Client(AddressPhoneModelMixin, DateFieldsModelMixin, models.Model):
And I now have other "common" fields I want to add, so it's only going to get worse. I want to keep all these common fields in one place for DRY, but also in case anything changes about a field, I only have one place to make the changes.
My idea is to have one mixin called CommonFieldsModelMixin, so that I will have only one mixin to include in the model definition. But for those models that don't need certain fields, the field definitions would all be wrapped in conditionals. Taking the above mixin as an example, and adding a conditional "Email Address" field, this is what I want to do:
class CommonFieldsModelMixin(models.Model):
address = models.TextField(
verbose_name=_('Address'),
blank=True,
null=True,
)
country = models.ForeignKey(
Country,
on_delete=models.SET_NULL,
verbose_name=_('Country'),
blank=True,
null=True,
)
if include_email: # <--this is what I want to add
email = models.EmailField(
verbose_name=_('Email Address'),
blank=True,
)
phone_number = PhoneNumberField(
verbose_name=_('Phone'),
blank=True,
null=True,
)
mobile_number = PhoneNumberField(
verbose_name=_('Mobile Phone'),
blank=True,
null=True,
)
fax_number = PhoneNumberField(
verbose_name=_('Fax'),
blank=True,
null=True,
)
class Meta:
abstract = True
Then when using the mixin on a model, it would be something like this:
class Client(CommonFieldsModelMixin, models.Model):
include_email = True
name = models.CharField(
verbose_name=_('Client'),
max_length=100,
)
status = models.CharField(
verbose_name=_('Status'),
max_length=25,
)
Notice the include_email = True property. In reality, all fields would be wrapped in conditionals, but this is intended as a simple example.
My question is, how can I access the include_email property of the parent from within the mixin? There isn't a self to use. I've also tried using super(), but that didn't work either. Is there any way to accomplish this?
And I'll also need to do the same (or similar) thing for the serializers. So if that would work differently, any suggestions there would be appreciated.

Django Custom Manager (get Boss' subordinates)

I have two models.
Boss:
class Boss(models.Model):
name = models.CharField(max_length=200, blank=False)
company = models.CharField(max_length=200, blank=False)
and Employee:
class Employee(models.Model):
boss = models.ForeignKey(Boss, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=False)
company = models.CharField(max_length=200, blank=False)
I want to create a custom manager that will get Boss' employees, it might be working like this:
b1 = Boss.objects.first()
b1.subordinates.all()
I do not know how to implement this actually. I understand I can do something like this:
class SubordinatesManager(Manager):
def get_queryset:
return super().get_queryset().filter(boss=b1)
but this will work only for Employee class.
You basically already have that, you can query like:
my_boss.employee_set.all()
You might want to change the name employee_set by changing the related_name attribute of the ForeignKey:
class Employee(models.Model):
boss = models.ForeignKey(Boss, on_delete=models.CASCADE, related_name='subordinates')
name = models.CharField(max_length=200, blank=False)
company = models.CharField(max_length=200, blank=False)
in which case the query is thus:
my_boss.subordinates.all()
You can also perform extra filtering, annotations, etc., like for example:
my_boss.subordinates.filter(name__contains='John')
to get all Employees with my_boss as boss, that have John in their name.

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.

Django ORM: Models with 2 table referencing each other

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

Categories