How to create many to many relationships using three tables Django - django

I have three tables Client, Company, Account and want to create a relation between then so that they can satisfy following conditions.
One client can have many accounts. One Account can be associated
with many clients.
One company can have many accounts. One account can be in many
companies.
One client can have many companies. One Company can be associated
with many clients.
Tables are as follows:
class Company(models.Model):
name = models.CharField(max_length=255)
website_url = models.CharField(max_length=255, null=True, blank=True, default='')
class Account(models.Model):
customer_id = models.CharField(max_length=20, null=True, blank=True)
name = models.CharField(max_length=255)
class Client(models.Model):
name = models.CharField(max_length=255)
age = models.CharField(max_length=10, null=True, blank=True)
I don't know how to define many to many relationships for these tables.

Checkout this so answer: Understanding ManyToMany fields in Django with a through model
You can also look for django documentation about many to many fields :
https://docs.djangoproject.com/en/4.1/topics/db/examples/many_to_many/

Related

What would be best if i want to relate one table to few others? (Django models example)

Existed models:
class OrderDocument(models.Model):
name = models.CharField(max_length=100)
file = models.FileField(upload_to=get_document_name)
order_group = models.ForeignKey(OrderGroup, on_delete=models.CASCADE, null=True, related_name='documents')
class Order(models.Model):
name = models.CharField(max_length=100)
order_group = models.ForeignKey(OrderGroup, on_delete=models.CASCADE, null=True, related_name='orders')
class OrderGroup(models.Model):
name = models.CharField(max_length=100)
I want to rewrite it for add ability to add OrderDocuments and choose with what table (Order or OrderGroup) it will related. Which way will the best in this case to implement it?
It can be more than 2 relations in future.
I don't want using m2m in Order and OrderGroup
Order can be without OrderGroup

Django access manytomany field from related_name in a view

I have what i think is a simple question but I am struggling to find out how it works. I get how related name works for foreign keys but with many to many fields it seems to break my brain.
I have two 3 models at play here. A User, TeamMember and Team Model as seen below.
User model is the built in django model.
#TeamMember Model
class TeamMember(models.Model):
member = models.ForeignKey(User, on_delete=models.SET(get_default_team_member), verbose_name='Member Name', related_name="team_members")
...
#Team Model
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="managers", null=True, blank=True)
team_lead = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tls", null=True, blank=True)
tps = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tps", null=True, blank=True)
members = models.ManyToManyField(TeamMember, blank=True, related_name="members")
...
Now in a view i want to access a specific users team. I thought i could do this by doing something like this:
member = TeamMember.objects.get(pk=1)
member_team = member.members.name
However if I print member_name than it prints nothing. If I try to access any of the other fields on that model like member.members.team_lead.first_name it fails to find the team_lead field. I understand that this has a .all() attribute but i thought it was tied to the team object through the members field. So if that member matches the team it would give me the team. So I thought it might be an issue if the same member was linked to more than one team (which is possible) so i tired something like this member.members.all().first().name and i get an error that states it cannot get name from NoneType.
Is there an easy way to get the team name from a relationship like this or am i better off just doing a team query with the user?
Thanks,
jAC
First of all, I would like to point out that you are not using the related_name (and related_query_name parameters in a proper way). I think this SO post will help you to understand the concept in a better way.
So, I would change the related_name (and related_query_name) values in the Team model as below,
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
team_lead = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
tps = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
members = models.ManyToManyField(
TeamMember, blank=True, related_name="teams", related_query_name="team"
)
...
Now in a view i want to access a specific user's team.
Since the Team and TeamMember models are connected via ManyToManyField, you may have "zero or more" Teams associated with a single TeamMember
So, the following query will get you all the teams associated with a particular TeamMemeber
team_member = TeamMember.objects.get(pk=1)
all_teams = team_member.teams.all()
You can also iterate over the QuerySet as,
team_member = TeamMember.objects.get(pk=1)
for team in team_member.teams.all():
print(team.name)
For anyone wondering what I did based on JPG's advice was the for loop option
team_member = TeamMember.objects.get(pk=1)
teams = [t.name for t in team_member.members.all()]
I personally do not care which team i get as my need in this case is just to pass a team through even if it is none. So i just use team = team[0] if teams.count() > 0 else "No team"

Django - Extending models with multi role users and multiple requirement types

I am new to Django only 2 weeks and I am stuck with scenario I need help with.
This is kind of core concept and I apologize in advance for my lack of knowledge.
I've extended the base user model like below with multiple roles. Now each role has distinct requirements for their profiles.
I need help to understand that how to extend Students or Staff further. There are two scenario I need to extend them.
Both can have similar extended models like addresses below.
Both can have different extended models like Students have CurrentCourse and Staff does not. Staff has Salary model and Students does not.
class User(AbstractUser):
is_student = models.BooleanField(default=True)
is_staff = models.BooleanField(default=True)
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
dob = models.CharField(max_length=30, blank=True)
location = models.CharField(max_length=30, blank=True)
class CurrentCourse(models.Model):
student = models.OneToOneField(Student,
on_delete=model.CASCADE)
....
class Staff(models.Model):
ROLES = (('Teacher', 'Teacher'), ('Professor', 'Professor'), ('Administration', 'Administration'))
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
role = models.CharField(max_length=100, choices=ROLES)
website = models.CharField(max_length=100, blank=True)
Both types of Users can have multiple addresses and both Students and Staff needs addressess, not sure how to extend this with ForeignKey.
class Address(models.Model):
user = ?
street = models.CharField(max_length=200)
city = models.CharField(max_length=50)
country = models.CharField(max_length=50)
class Salary(models.Model):
staff = models.OneToOneField(Staff, on_delete=models.CASCADE)
current_salary = models.DecimalField(max_digits=10, decimal_places=2)
Finally please let me know how can I apply validators on each model for instance I did sub-classed 'User' to all models instead of Student or Staff. How to apply a validator on OneToOneField like below? I thought that if I apply a validator like:
staff_validator(value):
I can't call user in validator function and not sure if it will be global or within model its applied on with indent.
class Salary(models.Model):
staff = models.OneToOneField(User, on_delete=models.CASCADE, validators=[staff_validator])
Thank you in advance for your kind help for this.
Imagine the real world scenario where your Users are People and People can have different roles with the time. Like if I am a Student today I belong to "People" category and if I will be Professor tomorrow still I will belong to People category.
So People can wear different hats or adapt different roles but they still will be People. Similarly your User model will always be part of all the roles you need to define in database. So you need to extend your User model for not only all the roles in fact for models you believe User can be part of and it should be like below:
class User(AbstractUser):
is_student = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
.....
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='foo')
.....
class Courses(models.Model):
user = models.ManyToMany(User)
You need to consult official docs with three types of models. Abstract models, Proxy models and Inherited models for more clarity. Also for creating view for your application you can create checks for your users with decorators. Here is a good example as per your question.

How to create a shared model in Django?

I have a couple of models that have need of a common set of fields. It is basically a set of various different types of contacts:
# models I would like to share
class Address(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
street1 = models.CharField(_('street1'), max_length=125, blank=True)
street2 = models.CharField(_('street2'), max_length=125, blank=True)
city = models.CharField(_('city'), max_length=50, blank=True)
state = models.CharField(_('state'), max_length=2, blank=True)
zip_code = models.CharField(_('zip_code'), max_length=10, blank=True)
class Phone(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
phone = models.CharField(_('phone'), max_length=50, blank=True)
# these are the models that I would like to have addresses and phone numbers
class Contact(models.Model):
name = models.CharField(_('name'), max_length=50, blank=False)
class Organization(models.Model):
name = models.CharField(_('name'), max_length=255, blank=False)
email = models.CharField(_('email'), max_length=100, blank=True)
website = models.CharField(_('website'), max_length=255, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
photo = models.CharField(_('photo'), max_length=255, blank=True)
I would like to share the Phone and Address models with the Contact, Organization and UserProfile models. My first attempt was to add ForeignKey on each of Contact, Organization and UserProfile but after more research I believe this is backwards, so I moved the ForeignKey to Address and Phone but then discovered that ForeignKey can belong to one and only one model. In addition to sharing this data structure between multiple different contact types, I would like the ability to add more than one address or phone number to a contact. A contact could have a home address, work address, mobile number, and work number for example. So I have basically 2 questions:
1) Is sharing a model in this way a reasonable thing to do?
2) How would I go about setting up the models?
If I've understood correctly; you want your Contact, Organization, and UserProfile models to all have fields for Address and Phone. Moreover, they can each have more than one address/phone.
Is this reasonable? Sounds so to me.
How could you go about setting up the models? Generic Relations spring to my mind.
Consider just Contact and Address for now. Your second attempt is correct: we have a Many-to-one relationship (one contact, many addresses), so we need to make Contact a ForeignKey field in the Address model, like so:
class Address(models.Model):
<other_fields>
contact = models.ForeignKey('Contact', on_delete=models.CASCADE)
This allows you to assign multiple addresses to each contact.
Moving on, you essentially want your Address model to have multiple foreign keys: Contact, Organization, and UserProfile. One way of achieving this is to use Generic Relations. This makes use of Django's built-in "contenttypes" framework, and allows you to create GenericForeignKey fields that point to more than one model. I encourage you to read the docs linked, since generic relations aren't so trivial. In the end, you'll have something like:
class Address(models.Model):
label = models.CharField(max_length=50, blank=True)
street_1 = models.CharField(max_length=125, blank=True)
street_2 = models.CharField(max_length=125, blank=True)
etc...
models_with_address = models.Q(app_label='app_name', model='contact') | \
models.Q(app_label='app_name', model='organization') | \
models.Q(app_label='app_name', model='userprofile')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=models_with_address)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
With this, you can create several addresses for each of the models specified in the models_with_address query. To be able to query the addresses for a given contact/organization/etc., you'll need a reverse generic relation. Setting this up involves adding the line address = GenericRelation(Address) to the respective models.
For further generalisation, you could create a ContactableModel (or whatever) class:
class ContactableModel(models.Model):
address = GenericRelation('Address')
phone = GenericRelation('Phone')
Any model with an address and phone number (Contact, Organization, etc.) could then inherit this so that you don't have to repeatedly include those two fields. You could also improve the models_with_address limit, so that we have something like limit_choices_to=<subclasses_of_ContactableModel>.
Hope this helps!
A simple solution would be to add ManyToManyField(Address|Phone) (i.e. two M2M fields)
to Contact, Organization and UserProfile.
But that would mean different contacts/organizations/users could share addresses.
While this looks tempting, the problem with such a solution is that if someone
edits an address of a contact, they also change the address for all the remaining objects in the system!
Another possible solution which avoids the above problem
and doesn't require M2M fields or generic relationships would be to use
multi-table inheritance:
class Address(models.Model):
entity = models.ForeignKey(Entity)
...
class Entity(models.Model):
pass
class Contact(Entity):
...
class Organization(Entity):
...
(Under the hood Django creates implicit OneToOneField-s
that point from Contact & Organization to Entity.)

Django model design issue with relationship

I have an problem where I can’t decide how to design the models for the following scenario
I want to create a companies table that will hold a list of companies. This table will have a comment field in it
I want that comment field to be able to hold multiple comments that are dated
A company can have multiple comments but a comment can only belong to only one company
Here the Comments table
class Comments(model.Models):
date = models.DateField(auto_now_add=True)
comment_text = models.TextField(required=True)
If I create the Companies table like this;
class Companies(models.Model):
name = models.CharField(max_length=30)
country = models.CharField(max_length=30)
comment = models.ForeignKey(Comments, on_delete=models.SET_NULL, null=True)
Then I can only attach one comment to one specific row of the Companies table
If I create the Companies table like this;
class Companies(models.Model):
name = models.CharField(max_length=30)
country = models.CharField(max_length=30)
comment = models.ManyToManyField(Comments)
Then a comment can belong to multiple companies which I don’t want.
In the Django documentation https://docs.djangoproject.com/en/2.0/topics/db/examples/ there is only one other options left which is the one-to-one mapping and this is clearly not what I want.
How can achieve what I want ?
You should do this.
class Company(models.Model):
name = models.CharField(max_length=30)
country = models.CharField(max_length=30)
class Comment(models.Model):
company = models.ForeignKey(Company, related_name='comments', on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True)
comment_text = models.TextField()
When accessing comments of a certain company, It will be;
comments = company.comments.all()
Just put the ForeignKey on Comment, pointing to Company. This does exactly what you want.