Adding additional attributes to a Django field - python

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.

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.

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.

Create additional fields in models on foreign key (Django query)

models.py
from django.db import models
class person(models.Model):
name = models.CharField(max_length=100, blank=true)
characteristics = models.ManyToManyField(characteristics, default = '', on_delete=models.SET_DEFAULT)
def __str__(self):
return self.name
class characteristics(models.Model):
name = models.CharField(max_length=100, blank=true)
def __str__(self):
return self.name
I have some troubles with queryset.
I create 3 persons: Jim, John, Jack.
Then create 4 characteristics: strength, agility, wisdom, intelligence
Connect Jim with strength and agility( he is strong and dexterous)
Connect John with wisdom and intelligence( he is a smart guy)
Connect Jack with strength, agility, wisdom, intelligence( he is jack of all trades)
Output will be like:
Jim: strength, agility
John: wisdom, intelligence
Jack: strength, agility, wisdom, intelligence
Is there a posibility to add values to these characteristics?
For example:
Jim: strength=10 , agility=10
John: wisdom=10, intelligence=10
Jack: strength=9, agility=8, wisdom=8, intelligence=9
I think, this values need to be stored in "person" table. I can create additional fields in models.py, but my goal is to make additional field in "person" model when creating new charactaristics.
Thereis a way to make it using 0...1000 empty int fields in "person" model, then giving additional number(like id) to charecteristics and then connect that characteristics id with "person" 0...1000 empty fields
from django.db import models
class person(models.Model):
name = models.CharField(max_length=100, blank=true)
characteristics = models.ManyToManyField(characteristics, default = '', on_delete=models.SET_DEFAULT)
char0 = IntegerField(blank=True)
...
char1000 = IntegerField(blank=True)
def __str__(self):
return self.name
class characteristics(models.Model):
name = models.CharField(max_length=100, blank=true)
additional_id = models.BigAutoField
def __str__(self):
return self.name
Is there some elegant way to solve my problem?
I think you need to try something like this:
class characteristics(models.Model):
id = models.AutoField(primary_key=True, )
name = models.CharField(max_length=100)
parameter = models.SmallIntegerField(default=0)
Next you can do:
Jim = person.objects.get(name = Jim)
jims_str = characteristics.objects.create(name = "strengh", parameter = 5)
jims_wisdom = characteristics.objects.create(name = "wisdom", parameter = 2)
Jim.characteristics.add(jims_str)
Jim.characteristics.add(jims_wisdom)
Jim.save()
I can miss some details, but the main logic might be like that. Different Characteristics (the good practice is to name class names with Capital Latter in Python) might have same names, but unique PrimaryKey, and will be connected to Person model via M2M relation.
METHOD 2:
You can edit your Person like:
class Person(models.Model):
id = models.AutoField(primary_key=True, )
name = models.CharField(max_length=100)
strength = models.ForeignKey('Strength', blank=True, null=True, on_delete=models.SET_NULL)
agility= models.ForeignKey('Agility', blank=True, null=True, on_delete=models.SET_NULL)
intelligence= models.ForeignKey('Intelligence', blank=True, null=True, on_delete=models.SET_NULL)
wisdom= models.ForeignKey('Wisdom', blank=True, null=True, on_delete=models.SET_NULL)
Next create 4 models: Strength, Agility, Wisdom, Intelligence
class Strength(models.Model):
id = models.AutoField(primary_key=True, )
parameter = models.SmallIntegerField(default=0)
Found 4 solutions.
1st(i ought to use it coz haven't got enough time and haven't found other propriate way):
models.py
from django.db import models
class person(models.Model):
name = models.CharField(max_length=128, blank=False)
characteristics = models.ManyToManyField(characteristics,
null=True,
on_delete=models.SET_NULL)
characteristics1 = models.FloatField(blank=True, null=True)
....
characteristics20 = models.FloatField(blank=True, null=True)
class characteristics(models.Model):
name = models.CharField(max_length=100, blank=true)
additional_id = models.BigAutoField
After creating new object in characteristics, object will get additional_id(starts from 1).
Lateron connect characteristics with person.characteristicsX, where (X = characteristics.additional_id).
Then, we can call for object, and sort characteristics by characteristicsX(where X = characteristics.additional_id)
2nd:
models.py
from django.db import models
class person(models.Model):
name = models.CharField(max_length=128, blank=False)
characteristics = models.ManyToManyField(characteristics,
null=True,
on_delete=models.SET_NULL)
characteristics1_type = models.ForeignKey('characteristics', blank=True, null=True, on_delete=models.SET_NULL)
characteristics1_value = models.FloatField(blank=True, null=True)
....
characteristics20_type = models.ForeignKey('characteristics', blank=True, null=True, on_delete=models.SET_NULL)
characteristics20_value = models.FloatField(blank=True, null=True)
class characteristics(models.Model):
name = models.CharField(max_length=100, blank=True)
May cause troubles, because user can set 2 identical characteristics. Still there is a way to prohibit it using forms or etc.
3rd:
models.py
from django.db import models
class person(models.Model):
name = models.CharField(max_length=128, blank=False)
characteristics = models.models.TextField(blank=True)
class characteristics(models.Model):
name = models.CharField(max_length=100, blank=True)
Then save values into person.characteristics field using forms. This way can still work, but then it requiers to sort the information from TextField by charecteristics.objects. It'll take some time to setting it well, but will work awesome.
4th:
Use postgreSQL and store an array.

OneToThree django model

I have a classic model containing some contact data (mail, addr, names, ...), and an other which is supposed to link to three contacts (an admin, an owner, a tech). A contact can be registered as admin and as tech for example.
class Contact(django.db.models.Model):
user = models.OneToOneField(User)
city = models.CharField(max_length=255, blank=True)
country = models.CharField(max_length=2, blank=True)
email = models.EmailField(max_length=255, blank=True)
first_name = models.CharField(max_length=255, blank=True)
family_name = models.CharField(max_length=255, blank=True)
phone = models.CharField(max_length=255, blank=True)
class Product(... Some parents)
name = models.CharField(max_length=255)
contacts ?
I don't know how to link 3 contacts to my other Model ... and generate a form with a queryset already existing.
Some advices ?
Thanks
just use foreign keys
example:
class Product(... Some parents)
name = models.CharField(max_length=255)
admin = models.ForeignKey(Contact, related_name="admins")
owner = models.ForeignKey(Contact, related_name="owners")
tech = models.ForeignKey(Contact, related_name="techs")
if you want to generate the form use a modelform
class MyForm(forms.ModelForm):
class Meta:
model=Product
the select widgets will be filled automatically
EDIT: to display your contact in a human friendly way use
class Contact(django.db.models.Model):
<usual stuff>
def __unicode__(self):
return u"%s" % self.firstname # or any field you wish
see https://docs.djangoproject.com/en/1.10/ref/models/instances/#str
if you use python 3 replace unicode by str

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