In a Django, given a Person model:
class Person(models.Model):
person_id = models.AutoField(primary_key=True, blank=False, null=False)
name = models.CharField(max_length=256, blank=False, default="")
and a Member model which has a person as a Foreign Key:
class Member(models.Model):
member_id = models.AutoField(primary_key=True, blank=False, null=False)
person = models.ForeignKey(Person)
I'm looking to be able to add a field to the Member model which contains the name of the person.
I've tried:
name = person.name and
name = self.person.name
with no luck.
I could define a function:
def name(self):
return self.person.name
...but then I would need to reference it with parens: member.name()
And, of course, I could always just reference it via member.person.name, but that's so verbose.
Is there a way to define this attribute?
You can use the #property as a decorator.
#property
def name(self):
return self.person.name
Now, you can just reference it as
member.name
instead of member.name()
Now, name is treated as a property on Member class, instead of a class method.
Related
Error in the line as below:
'ForwardManyToOneDescriptor' object has no attribute 'pk'
friend_user = User.objects.get(pk=Friend.to_user.id)
Thanking you in advance,
models.py
class Friend(models.Model):
status = models.CharField(max_length=10)
from_user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, related_name = 'from_user')
to_user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="to_user")
date_modified = models.DateTimeField(auto_now=True, blank=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.to_user.email
Use 'id' instead of 'pk', If Friend is your another model, then need to get an instance of Friend class. So that you can access the attributes
friend_user = User.objects.get(id=Friend.to_user.id)
The related_name specifies the name of the relation at the target model, so your
to_user = models.ForeignKey(
AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="to_user" # <-- confusing relation name
)
adds a relation User.to_user. This is a confusing name because this to_user actually relates to a Friend. And because a foreign key technically allows multiple friends refer to the same user, it should be a plural.
So a better name would be to_friends, which adds Friend.to_user and User.to_friends:
to_user = models.ForeignKey(
AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="to_friends" # <-- better relation name
)
Provided you changed the name of the relation, you can find the friend for a given user using:
friend = User.to_friends.get(id=...)
assuming the user actually has this friend. If not, you get a Friend.DoesNotExist exception. If you want to avoid that and get a None friend instead, use:
friend = User.to_friends.filter(id=...).first()
if friend is not None:
... # Do something with friend.
I am trying to make an app where users will login to their profile and can add songs to their favorite list. I am defining a M2M relationship for this.
My question is how to say combination of (song, singer) is unique?
I searched and found that it may be possible through unique_together. Is this the correct way of setting this?
models.py:
from django.contrib.auth.models import User
class Singer(models.Model):
name = models.CharField(max_length=500, unique=True)
def __str__(self):
return self.name
class Song(models.Model):
id = models.AutoField(primary_key=False)
singer = models.ForeignKey(Singer, on_delete=models.CASCADE, related_name='song')
name = models.CharField(max_length=500)
Class Meta:
unique_together = (singer, id)
def __str__(self):
return self.name
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
songs = models.ManyToManyField(Song, related_name='profile')
def __str__(self):
return self.user.username
Please feel free to correct my models.py, if you think the relationship is not correct.
Thanks,
I would use a default primary key (auto field), and use the meta class property, unique_together
class Song(models.Model):
singer = models.ForeignKey(Singer, on_delete=models.CASCADE, related_name='song')
name = models.CharField(max_length=500)
class Meta:
unique_together = (("singer", "name"),)
It would act as a "surrogate" primary key column.
You don't specify id in your model song. I would also recommend to use slug field of django and specify unique on the same. Just in case you have two singers with the same name. Then the second or more you put like abc, abc-1, abc-2, in which case you don't have to change the name and unique_together clause works just fine.
class Song(models.Model):
singer = models.ForeignKey(Singer, on_delete=models.CASCADE, related_name='song')
name = models.CharField(max_length=500)
class Meta:
unique_together = (("singer", "name"),)
def __str__(self):
return self.name
I tried to create a model with a many to many self relation, i put this in my code:
class Person(models.Model):
name = models.CharField(max_length=50)
shape_clm = models.ManyToManyField("self", through='Friend', symmetrical=False)
def __str__(self):
return self.name
class Friend(models.Model):
pers_one = models.ForeignKey(Person)
pers_two = models.ForeignKey(Person)
type = models.CharField(max_length=150)
But when i tried to migrate the model to the DB the following error is raised:
Friend.pers_one: reverse accessor for Friend.pers_one clashes with reverse accessor for Friend.pers_two
I'm using Postgres as DB server, how i can make this m2m relation?
You need to add the related_name keyword argument, otherwise the ORM can't tell how will you refer to either of the fields.
class Friend(models.Model):
pers_one = models.ForeignKey(Person, related_name='pers_ones')
pers_two = models.ForeignKey(Person, related_name='pers_twos')
Add related_name arguments to your ForeignKeys in the Friend ModelClass:
class Friend(models.Model):
pers_one = models.ForeignKey(Person, related_name="friends_one")
pers_two = models.ForeignKey(Person, related_name="friends_two")
type = models.CharField(max_length=150)
For more information about related_name take a look at the docs.
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.
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']