Google has lots of examples of doing reverse lookup in the interactive prompt but none of doing them inside a django model as a method.
I have the following models.py file:
class Client(models.Model):
...
def __unicode__(self):
return ???
class ClientDetails(models.Model):
client = models.ForeignKey(Client, null=True)
created = models.DateTimeField(default=datetime.now)
created_by = models.ForeignKey(User, null=True)
name_title = models.CharField(max_length=3, choices=NAME_TITLE_CHOICES)
first_name = models.CharField(max_length=40)
middle_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
...
How do I get the client method to return last_name from ClientDetails?
If a ClientDetails object should only ever be associated with a single Client object, then I would change your FK to a OneToOneField, which will provide you with a neat reverse accessor that can only ever link between a given Client and its associated ClientDetails. Then you can do:
try:
return self.clientdetails.last_name
except ClientDetails.DoesNotExist:
##handle it here, returning a graceful message
Alternatively, if you kept it as a FK, then you'd have to do something like:
try:
self.clientdetails_set.all()[0].last_name
except IndexError, e:
## handle exception here
but using a FK here is brittle and not great form (as the exception-handling implies: if there is none returned, then you'll get an IndexError. Also, there might be more than one ClientDetails object linked to that Client, and you'd only get the details of the first one here.)
So, I really would recommend using a OneToOneField instead of that FK. (All a OneToOneField is basically an FK with unique=True set on it and some neater accessors than standard FKs)
For that you can use in client the *clientdetails_set* to access all the ClientDetails objects that are linked to that client.
The set is an object set from Django, so calling the method all() will retrieve every of the objects. If you know there's only one you can do self.clientdetails_set.all()[0].last_name
Here's the link for the django documentation: Link
Related
I am implementing a friend system similar to facebook where one can send friendship requests and accept requests and then see their friends. On the page where one sees the friend requests I am trying to filter out the users who have accepted the friend request and are friend already now. In the code below, 'u' is the current logged in user. Friendship table hold two fields , both foreign keys, please see below:
try:
already_friends = Friendship.objects.get(Q(from_friend=u) | Q(to_friend=u))
for x in already_friends.iterator():
my_requests = FriendRequest.objects.filter(Q(receiver=request.user) & ~Q(sender=x))
except ObjectDoesNotExist:
class FriendRequest(models.Model):
sender = models.ForeignKey(User, related_name='the_sender',on_delete=models.CASCADE)
receiver = models.ForeignKey(User, related_name='the_receiver', on_delete=models.CASCADE)
def __str__(self):
return "request sent"
class Meta:
unique_together = (('sender', 'receiver'),)
class Friendship(models.Model):
from_friend = models.ForeignKey(User, related_name="from_friend", on_delete=models.CASCADE)
to_friend= models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return "Friend request accepted"
class Meta:
unique_together = (('from_friend', 'to_friend'),)
When I use the query I wrote at the top using Q (complex queries) and then use iterator, I get the following error:
'Friendship' object has no attribute 'iterator'
How can I achieve what I intend to do using django models/queries?
You should use .filter(…) [Django-doc] not .get(…) [Django-doc]: .get(…) retrieves a single Friendship object, and will raise an error if there is no such object, or if there are multiple ones. .filter(…) on the other hand will return a (possibly empty) queryset of all Friendship records that satisfy the given predicate:
already_friends = Friendship.objects.filter(Q(from_friend=u) | Q(to_friend=u))
for x in already_friends.iterator():
my_requests = FriendRequest.objects.filter(Q(receiver=request.user) & ~Q(sender=x))
Furthermore it makes no sense to use Q(sender=x): x is a Friendship object, not a user object. You probably want to check if the from_friend_id or to_friend_id is the u, and thus use the other one to filter. Something like:
already_friends = Friendship.objects.filter(Q(from_friend=u) | Q(to_friend=u))
for x in already_friends.iterator():
if x.from_user_id = u.id:
my_requests = FriendRequest.objects.filter(receiver=request.user, sender_id=x.to_user_id)
else:
my_requests = FriendRequest.objects.filter(receiver=request.user, sender_id=x.from_user_id)
I have the following two models:
class URLResource(models.Model):
class ResourceType(models.TextChoices):
VID = 'VID', _('Video')
DOC = 'DOC', _('Document')
resource_type = models.CharField(
choices=ResourceType.choices,
default=ResourceType.UNK,
max_length=3)
title = models.CharField(max_length=280, null=True)
url = models.URLField()
created = models.DateTimeField(auto_now_add=True)
def __repr__(self):
return f'URLResource {self.resource_type}: {self.title} URL: {self.url}'
class Record(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
...)
date = models.DateTimeField(default=now, blank=True)
# <resource>: I want to point to a resource somehow
def __repr__(self):
return f'Record {{ User: {self.user}, Resource: }}'
My use case is such that a lot of users can create Records and list them, but since the underlying URLResource is going to be same, I thought I could use many-to-one relationship from Records-to-URLResource so the underlying resources would remain the same. However, even though I have only one type of resource now i.e. URLResource, I also want to roll out other resources like VideoResource, PhysicalResource etc. Clearly, I can't simply use the many-to-one relationship. So, I figured I could use Django's contenttype framework, but I still can't get things to work.
I want to be able to do atleast these two things:
When creating a Record, I can easily assign it a URLResource (or any other Resource) (but only one resource).
I can easily access the URLResource fields. For example like Record.objects.get(user=x).resource.resource_type.
Recently I've faced an issue with Django, model inheritance and how the creation of model instances works.
Suppose I have the following (basic) setup:
class InviteBaseManager(models.Manager):
def create(self):
new_code = # create some kind of unique code, not really relevant here.
return super().create(code=new_code)
class InviteBase(models.Model):
code = models.CharField(max_length=10, blank=False, null=False, unique=True)
creationDate = models.DateTimeField(default=timezone.now())
objects = InviteBaseManager()
class PartyInviteManager(models.Manager):
def create(self, name):
# method 1
newInvite = InviteBase.objects.create()
print(newInvite.code) # is definitly set, lets assume "ABCD"
# as expexted, the "InviteBase" table has one row with code "ABCD" and
# default creationDate
newPartyInvite = super().create(partyName=name, invite=newInvite)
print(newPartyInvite.invite.code) # is EMPTY, prints nothing
# In fact, when looking at the db, there is still only *one* row in the table "InviteBase",
# with an *empty* code field and a default creationDate field.
return newPartyInvite
#method 2
newPartyInvite = super().create(partyName=name)
# creates the InviteBase instance implicitly, again, newPartyInvite.invite.code is empty.
# fill newPartyInvite.invity.code manually.
return newPartyInvite
class PartyInvite(InviteBase):
#Isn't blank=False and null=False unnecessary? A child should probably not exist with no parent?
invite = models.OneToOneField(InviteBase, parent_link=True, on_delete=models.CASCADE, null=False, blank=False)
partyName = models.CharField(...)
objects = PartyInviteManager()
So the question is: How can I pass an already existing instance of the base class inside the create method of my PartyInviteManager? Even when using method 1, the existing instance that I pass along seems to be overwritten. A new one is created with the default value. Interestingly enough, this violates the constraints that code cannot be blank or NULL.
This behaviour seems a bit odd to me? Can someone point out what I am missing here?
To clarify: I know that I should usually use **kwargs in the create methods and that inheritance might not be the ideal use case here, but I'm just very curious about this behaviour.
I know that this kind of model inheritance wont even create a pk for the child model (because holding a OneToOneField to the parent class effectively acts as a pk anyway), but why would it be impossible to pass a manually created instance as parent? Am I not allowed to use inheritance for my use-case?
I think I've found the correct way of doing this:
class InviteBaseManager(models.Manager):
def create(self, **kwargs):
kwargs['code'] = #createCode
return super().create(**kwargs)
class InviteBase(models.Model):
code = models.CharField(max_length=10, blank=False, null=False, unique=True)
creationDate = models.DateTimeField(default=timezone.now())
objects = InviteBaseManager()
class PartyInviteManager(InviteBaseManager):
def create(self, name):
return = super().create(partyName=name)
class PartyInvite(InviteBase):
invite = models.OneToOneField(InviteBase, parent_link=True, on_delete=models.CASCADE, null=False, blank=False)
partyName = models.CharField(...)
objects = PartyInviteManager()
The PartyInviteManager now inherits from the InviteManager as well. When calling super.create() from the child manager, the base manager gets called, appends the code field and everything works as expected.
I also found out, that if the PartyInviteManager does not inherit from the InviteBaseManager, the create method of InviteBaseManager does not get called when creating a new PartyInvite. This seems very odd to me.
Of course, the easier way would have been to create the code as default value via a function, like that:
def createCode():
return "ABCD" # add fancy code creation magic here.
class InviteBase:
code = models.CharField(max_length=128, blank=False, null=False, default=createCode)
But if, depending on the child class, the code needs additional information (such as invited members or whatever), this approach would not work anymore.
On a side note, interestingly enough the following code snippet:
invite = PartyInviteManager.objects.create(partyName='Birthday')
print(invite.invite.code)
print(invite.code)
produces the following output:
ABCD
ABCD
In the create method of the PartyInviteManager, one can directly use code=XXXX to pass along a string for the InviteBase model, invite_code on the other hand does not work.
Here are stripped down versions of the models I'm dealing with:
class Contact(models.Model):
contact_no = models.IntegerField(primary_key=True)
email_address = models.CharField(max_length=60, null=True)
class ContactRole(models.Model):
contact_no = models.ForeignKey(Contact, primary_key=True, db_column='contact_no')
role_code = models.CharField(primary_key=True, max_length=16)
role_scope_code = models.CharField(primary_key=True, max_length=16)
Contacts can and almost always do have many ContactRoles.
I want a list of Contacts where the role_scope_code of the related ContactRole is 'foo'. I know I can get this with:
Contact.objects.filter(contactrole__role_scope_code='foo')
What I also want, is for each Contact in the queryset to have a single .contactrole property. It would be the ContactRole with the role_scope_code of 'foo'. Instead I'm getting a set of all ContactRoles that match on contact_no, so that to get to properties of the ContactRole I have to do something like this:
contacts = Contact.objects.filter(contactrole__role_scope_code='foo')
for contact in contacts:
print contact.contactrole_set.filter(role_scope_code='foo')[0].role_code
I have to filter on role_scope_code twice! That doesn't seem DRY at all. What I'm looking for is a query that will allow me to have a set that works like this:
contacts = Contact.objects.filter(contactrole__role_scope_code='foo')
for contact in contacts:
print contact.contactrole.role_code
For the life of me I can't figure out how to tell Django to only return the related objects that match the filter I applied to the parent object.
A OneToOneField will solve this provided that a contact only have one contactrole. A OneToOneField gives you the api you are looking for. So instead of using a ForeignKey use a OneToOneField
I'd like to create a many-to-many relationship from and to a user class object.
I have something like this:
class MyUser(models.Model):
...
blocked_users = models.ManyToManyField(MyUser, blank=True, null=True)
The question is if I can use the class reference inside itself. Or do I have to use "self" insead of "MyUser" in the ManyToManyField? Or is there another (and better) way to do it?
Technically, I'm pretty sure "MyUser" or "self" will work, as long as it's a string in either case. You just can't pass MyUser, the actual class.
However, the docs always use "self". Using "self" is not only more explicit about what's actually happening, but it's impervious to class name changes. For example, if you later changed MyUser to SomethingElse, you would then need to update any reference to "MyUser" as well. The problem is that since it's a string, your IDE will not alert you to the error, so there's a greater chance of your missing it. Using "self" will work no matter what the class' name is now or in the future.
class MyUser(models.Model):
...
blocked_users = models.ManyToManyField("self", blank=True)
Don't forget use symmetrical=False, if you use .clear() or .add() method for related objects and don't wanna object on other side of relation update own data in relation field.
some_field = models.ManyToManyField('self', symmetrical=False)
I think it should be class name instead of self. because with using self like this
parent = models.ManyToManyField('self', null=True, blank=True)
when i add parent:
user1.parent.add(user2)
i have 2 record in database like this:
and with using class name liken this:
parent = models.ManyToManyField('User', null=True, blank=True)
i have one record in database like this:
note that i use uuid for pk and i use django 3.1
EDIT:
as #shinra-tensei explained as comment in this answer we have to set symmetrical to False if we use self. documented in Django Documents: ManyToManyField.symmetrical
If you use self or MyUser you will get a NameError in both cases. You should write "self" as string. See the example below:
class MyUser(models.Model):
...
blocked_users = models.ManyToManyField("self", blank=True, null=True)
And do not forget to set the symmetrical attribute to False if the relationship is not symmetrical.
For further details check: https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.ManyToManyField
don't use 'self' in ManyToManyField, it will cause you object link each other, when use django form to submit it
class Tag(models.Model):
...
subTag = models.ManyToManyField("self", blank=True)
...
aTagForm.save()
and result:
a.subTag == b
b.subTag == a