Order a ManyToMany relationship, Django - django

With these models:
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
order_index = models.IntegerField(unique=True)
class Meta:
ordering = ['order_index']
I've been trying to get the following code to work:
group = Group.objects.get(id=1)
group.members.all() # I want the resulting membership objects to ordered by order_index as default.
Is this possible? The answers to similar questions require you to write group.members.all().order_by(...). I am trying to have it so the results are ordered by order_index by default.

This is how you get the user, and then the members associated with that user ordering them by date_joined. Not sure how you would do it automatically because the ordering is usually by the pk value which I'm assuming would correspond with the date joined because the higher the pk the later joined.
user = Person.objects.get(pk=request.user.id)
get_members = user.group.members.all().order_by('date_joined')

Related

Many to Many in two directions

I have a friend that requested something that I was thinking would be simple and quick. It never turns out that way. Quick disclaimer, model design is a krux of mine. I often spend too long perfecting it only to have to rework it several times. Anyway, here is the current state of my model. For everything, it works, except when creating 'raids'.
from django.db import models
# Create your models here.
class PlayerRole(models.Model):
"""
PlayerRole Model
"""
role = models.CharField(max_length=20)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.role
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['role']
class PlayerClass(models.Model):
"""
PlayerClass Model
"""
name = models.CharField(max_length=100)
color = models.CharField(max_length=6)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class Player(models.Model):
"""
Player Model
"""
name = models.CharField(max_length=100)
playerclass = models.ForeignKey(PlayerClass, on_delete=models.CASCADE, blank=True, null=True)
playerrole = models.ForeignKey(PlayerRole, on_delete=models.CASCADE, blank=True, null=True)
value = models.IntegerField(default=0)
reliability = models.IntegerField(default=0)
last_drop = models.DateField(auto_now=False, blank=True, null=True)
last_raid_attended = models.DateField(auto_now=False, blank=True, null=True)
last_boss_attended = models.DateField(auto_now=False, blank=True, null=True)
drop_received = models.BooleanField(default=False)
note = models.TextField(null=True, blank=True)
core_raider = models.BooleanField(default=False)
enabled = models.BooleanField(default=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class WoWInstance(models.Model):
"""
Instance Model
"""
name = models.CharField(max_length=100)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class Boss(models.Model):
"""
Boss Model
"""
name = models.CharField(max_length=100)
instance = models.ForeignKey(WoWInstance, on_delete=models.CASCADE)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class Raid(models.Model):
"""
Raid Model
"""
date = models.DateTimeField(auto_now_add=True)
boss = models.ForeignKey(Boss, on_delete=models.CASCADE, null=True, blank=True)
success = models.BooleanField()
attendees = models.ManyToManyField(Player)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return str(self.date) + ' + ' + self.boss.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['boss']
The idea here is simply to track attendance on each attempt at fighting something in a game. In the most general and simple sense - Many Raids can contain many of the same Bosses. Many Raids can contain many of the same Players.
Now, I thought that because Django automatically creates ID fields, this method wouldn't be an issue. But, it seems not to be the case. If I create a raid with the same Boss more than once, I get...
Raid with this Boss already exists.
What am I missing here? Since all Raid and Boss are unique, shouldn't the two never overlap?
If I create a raid with the same Boss more than once, I get...
Raid with this Boss already exists.
Well that is due to the unique_together constraint:
class Raid(models.Model):
# ...
class Meta:
unique_together = ['boss']
If you write unique_together, it means you enforce a uniqness constraint on a combination of fields. But since you here have mentioned only one field ('boss'), you thus add a unique=True constraint on that specific field.
It thus means that no two Raids can exist with the same boss field. You probably want to remove that.
Since all Raid and Boss are unique, shouldn't the two never overlap?
The Raids and Bosses are already unique. An object does not belong to two models at once (unless one is the subclass of another, but let us ignore that case). You even made the name unique as well (although you better do that by adding a unique=True parameter to the name fields). So there is no need at all to make the 'boss' unique here, since you clearly do not want that.

Correct way of getting count on FK

What is the correct way of getting the 'contact' count from within my 'Group'?
I was thinking of just creating a new method within 'group' and filter(), but this means hitting the db again which seems bad, right?
class GroupManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(user=user,)
class Group(models.Model):
name = models.CharField(max_length=60)
modified = models.DateTimeField(null=True, auto_now=True,)
#FK
user = models.ForeignKey(User, related_name="user")
objects = GroupManager()
def get_absolute_url(self):
return reverse('contacts.views.group', args=[str(self.id)])
class Contact(models.Model):
first_name = models.CharField(max_length=60)
last_name = models.CharField(max_length=60)
#FK
group = models.ForeignKey(Group)
group_object.contact_set.count() should do it. Django creates the relation by adding _set to the end of the foreign key's model name.
Have a look at the docs on related objects for more info.

Filtering Many-to-Many relationship by Relationship field in Django

I'm trying to filter many-to-many relationship by some through Class field.
Quoting the Django documentation, i will explain my goal
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)
In this example my goal sould be filter many to many relationship and obtain only the Person who has joined some Group starting from certain date (date_joined field).
Is it possible?
You can query across relationships with the django ORM (or in this case the reverse relationship):
person = Person.objects.filter(
membership__group=example_group,
membership__date_joined__gte=example_date
)
You can also do this:
person = example_group.members.filter(
membership__date_joined__gte=example_date
)

Getting the last member of a group on an intermediary M2M

If we look at the existing docs, what is the best way to get the last member added? This is similar to this but what I want to do is to be able to do this.
group = Group.objects.get(id=1)
group.get_last_member_added() #This is by ('-date_added')
<Person: FOO>
I think the best way is through a manager but how do you do this on an intermediary model?
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)
No need for a manager! Unless you'd like to add some kind of try/except in case there is no latest member.
group = Group.objects.get(id=1)
latest_person = group.members.latest('membership__date_joined')

Default ordering for m2m items by intermediate model field in Django

I have an unusual problem. Let's consider such models (taken from django 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)
Now, let's say we've got 2 Beatles members in out Beatles band (following the example in django docs for intermediate models):
>>> beatles.members.all()
[<Person: Ringo Starr>, <Person: Paul McCartney>]
The above code will return members sorted by default ordering for Person model. If I specify:
>>> beatles.members.all().order_by('membership__date_joined')
the members, are sorted via the date joined. Can I somehow set this as default behavior for this ManyToMany field? That is to set default order of related items by field in the intermediate model? The ManyRelatedManager seems to have an init argument core_filters, but I have no vague idea how to access it withous subclassing the whole m2m field in django. Any creative ideas? :)
Thanks in advance :)
I've opened a ticket in django trac.
Here is a dirty-hack method to achieve this (look at Group model):
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')
#property
def members(self):
return self._members.order_by('membership__date_joined')
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)
Didn't bother to create a set property decorator, but it should be quite easy to mimic the setting of original field. Ugly workaround, but seems to do the trick.
I think this should work:
class Membership(models.Model):
...
class Meta:
ordering = ['date_joined']