I am trying to model a two-way relationship between group members and the group they belong to.
More specifically, I'd like to model something like this:
class Member(models.Model):
name = models.CharField(max_length=50)
my_group = models.ForeignKey('Group')
#leader = models.BooleanField()
class Group(models.Model):
name = models.CharField(max_length=50)
leader = models.ForeignKey('Member')
So I want every member to be linked to a group. But each group should have a leader. I'd normally add a "leader" attribute to assign a leader for a group or add an extra model / table. However, I would like to be able to choose the leader from the members list for a specific group in the Django Admin interface and the above attempt results in an error of course, because the two models reference each other.
Here's the error:
ERRORS: app1.Group.leader: (fields.E303) Reverse query name for 'Group.leader' clashes with field name 'Member.my_group'. HINT: Rename field 'Member.my_group', or add/change a related_name argument to the definition for field 'Group.leader'
What's the best way to achieve this?
You can have intermediate model called Membership to define the level of membership
models:
class Member(models.Model):
name = models.CharField(max_length=50)
# may be a relations to User here
class Group(models.Model):
name = models.CharField(max_length=50)
members = models.ManyToManyField(Member, through=Membership)
class Membership(models.Model):
group = models.ForeignKey(Group)
members = models.ForeignKey(Member)
# member level
is_leader = models.BooleanField(default=False)
you can check the documention on ManyToMany fields for some more examples.
Related
I'm trying to access data in a join table using django. I have a list of users and a list of groups in a ManyToMany relationship.
class Member(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
groups = models.ManyToManyField(Group, related_name="members")
class Team(models.Model):
name = models.CharField(max_length=25)
creator = models.ForeignKey("footballteamapi.Member", on_delete=models.CASCADE)
The join table looks like this:
footballteamapi_member_teams
id+member_id+team_id
1+1+1
2+1+2
3+2+1
How do I go about accessing that table since there's no coinciding model to reference (i.e. Team.objects.all())?
You can use access the Team table in the reverse direction like so:
member = Member.objects.get(id="<member-id>")
teams = member.team_set.all()
But I like to set the related_name option on the ForiegnKey field like so:
class Team(models.Model):
creator = models.ForeignKey(..., related_name='teams')
...
Then the above changes to:
member = Member.objects.get(id="<member-id>")
teams = member.teams.all()
I have the following objects:
class Customer(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
customer = models.ForeignKey(Customer)
class GroupMember(models.Model):
group = models.ForeignKey(Group)
member = models.ForeignKey(Member)
class Member(models.Model):
customer = models.ForeignKey(Customer)
I can get all the members assigned to a group with this:
group_members = group.groupmember_set.all()
All of the members available for a certain customer with this:
member_list = customer.members.all()
I want to create a list of all of members available for a customer that are not assigned to a group. Something like:
not_group_members = Member.objects.filter(?)
How can I create this query?
With your current setup, that's not possible since Member has a non-nullable foreign key to group.
However, if you change to this:
class Member(models.Model):
customer = models.ForeignKey(Customer, null=True)
Then you can find Members that are not in any group.
not_group_members = Member.objects.filter(group=None)
customer.members.filter(groupmember_set__isnull=True)
(I used members here because you did, even though the default related name would be member_set.)
I was able to accomplish this with:
customer.members.filter(groupmember__isnull=True)
I don't understand why we have to use ManyToManyField to declare a many to many association. To do so, I would create another table with two foreign keys, period!
Here is an example from the doc https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
extraField = models.DateField()
I would just write:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
extraField = models.DateField()
Is it correct? What is the difference?
There is nothing wrong with defining an intermediate model for the relationship. That way you can store extra information on the intermediate model (Membership), like when the person joined the group, or if the membership is still valid. However, most of the time you don't need the extra information and only want to store which person is member of which group. In those cases, you could just use a simple ManyToManyField. Django makes it very easy to work with those fields, so you can do group.members.add(user) and group.members.delete(user), compare that to:
Membership.objects.create(user=user, group=group)
Membership.objects.get(user=user, group=group).delete()
Disclaimer: pseudo-code, might not actually work
You can still use ManyToManyField with an intermediate model, this allows for most of the Django ManyToMany conveniences, but with some restrictions:
Unlike normal many-to-many fields, you can’t use add, create, or assignment (i.e., beatles.members = [...]) to create relationships.
Why? You can’t just create a relationship between a Person and a Group - you need to specify all the detail for the relationship required by the Membership model. The simple add, create and assignment calls don’t provide a way to specify this extra detail. As a result, they are disabled for many-to-many relationships that use an intermediate model. The only way to create this type of relationship is to create instances of the intermediate model.
The remove() method is disabled for similar reasons. However, the clear() method can be used to remove all many-to-many relationships for an instance.
Once you have established the many-to-many relationships by creating instances of your intermediate model, you can issue queries. Just as with normal many-to-many relationships, you can query using the attributes of the many-to-many-related model.
Source: docs.djangoproject.com
I needed to assign one or more categories to a list of submissions, I initially used a table with two foreign keys to accomplish this until I realized Django has a many-to-many field, however following the documentation I haven't been able to duplicate what I did with original table.
My question is : Is there a benefit to using many-to-many field instead of manually creating a relationship table? If better, are there any example on submitting and retrieving many-to-many fields with Django?
From the Django docs on Many-to-Many relationships:
When you're only dealing with simple many-to-many relationships such
as mixing and matching pizzas and toppings, a standard ManyToManyField
is all you need. However, sometimes you may need to associate data
with the relationship between two models.
In short: If you have a simple relationship a Many-To_Many field is better (creates and manages the extra table for you). If you need multiple extra details then create your own model with foreign keys. So it really depends on the situation.
Update :- Examples as requested:
From the docs:
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
You can see through this example that membership details (date_joined and invite_reason) are kept in addition to the many-to-many relationship.
However on a simplified example from the docs:
class Topping(models.Model):
ingredient = models.CharField(max_length=128)
class Pizza(models.Model):
name = models.CharField(max_length=128)
toppings = models.ManyToManyField(Topping)
There seems no need for any extra data and hence no extra model.
Update 2 :-
An example of how to remove the relationship.
In the first example i gave you have this extra model Membership you just delete the relationship and its details like a normal model.
for membership in Membership.objects.filter(person__pk=1)
membership.delete()
Viola! easy as pie.
For the second example you need to use .remove() (or .clear() to remove all):
apple = Toppings.objects.get(pk=4)
super_pizza = Pizza.objects.get(pk=12)
super_pizza.toppings.remove(apple)
super_pizza.save()
And that one is done too!
According to an example, I have three models:
class User(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(User, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(User)
group = models.ForeignKey(Group)
date_joined = models.DateField()
Adding members works. But how do I delete a single Membership instance
(a User quits a group), without deleting neither the User, nor the Group?
When I try deleting it like this:
u = User(request.user)
g = Group.objects.get(id=group_id, membership__user=u)
m = Membership(user=request.user, group=g)
m.delete()
I get an error:
AssertionError at /groups/quit/1/
Membership object can't be deleted because its id attribute is set to
None.
In the line
m = Membership(user=request.user, group=g)
You created a new Membership you didn't fetch one from the database. That is why its id attribute is set to None.
Perhaps you meant
m = Membership.objects.get(user=request.user, group=g)
This particular error is triggered by the fact, that your m instance of class Membership is unsaved, so its primary key is None. Apparently, it is impossible to delete such an unsaved instance (which makes sense, because there is nothing to "delete").