Django Custom Manager (get Boss' subordinates) - python

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.

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.

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.

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.

Get models ordered by an attribute that belongs to its OneToOne model

Let's say there is one model named User and the other named Pet which has a OneToOne relationship with User, the Pet model has an attribute age, how to get the ten User that owns the top ten oldest dog?
class User(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
class Pet(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
owner = models.OneToOneField(User, on_delete=models.CASCADE)
age = models.IntegerField(null=False)
In User, there is an attribute friends that has a ManyToMany relationship with User, how to get the ten friends of User Tom that owns the top ten oldest dog?
class User(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
friends = models.ManyToManyField(self, ...)
class Pet(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
owner = models.OneToOneField(User, on_delete=models.CASCADE)
age = models.IntegerField(null=False)
Use the double-underscore syntax.
User.objects.order_by('-pet__age')[:10]
Edit
To get the ten friends of Tom, you can get the instance and filter:
User.objects.get(name='Tom').friends.order_by('-pet__age')[:10]
or if you already have Tom:
tom.friends.order_by('-pet__age')[:10]
Another solution (alternative to order_by) is using nlargest function of heapq module, this might be better if you already have friends list (tom's friends in this case) with a large number of items (I mean from performance perspective).
import heapq
heapq.nlargest(
10,
User.objects.get(name='Tom').friends.all(),
key=lambda f: f.pet.age
)
Note: You have also nsmallest function that you can use to get the youngest pets.
Try this :
First define unicode in model User like this:
By this,User model objects will always return name field of the user records.
class User(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
friends = models.ManyToManyField(self, ...)
def __unicode__(self):
return self.name
Then use this query:
User.objects.filter(friends='Tom').order_by('-pet__age')[:10]

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