Search code examples
pythondjangomodeldjango-templates

How to call OneToOneField reverse relationship in template (django)?


I have these models in my models.py
User Model

class User(AbstractBaseUser, PermissionsMixin):
    """Custom user model"""
    email = models.EmailField(max_length=255, unique=True)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    def __str__(self):
        return '{}'.format(self.email)

UserInfo Model

class UserInfo(models.Model):
    """User's Info"""
    user = models.OneToOneField(User, related_name="parent_user", on_delete=models.CASCADE)
    image = models.ImageField(upload_to=user_image_file_path)
    age = models.IntegerField()
    bio = models.CharField(max_length=400)
    def __str__(self):
        return '{}'.format(self.user)

My-template
In my template i am passing some filtered User Profiles(let's say filtering by age, if age > 25 ) now i need to show the name of the user(first_name) as well but i don't know how to call a OneToOnefield reversely. Help is really appreciated :).

  {% for p in profiles %}
      <div class="profile-container" style="position: relative">
          <div class ="left-div"> 
              <div class="mySlides fade">
                  <img src="media/{{p.image}}" style="width:100%" id="user-media-img">

                  <div class="text">
                      User First Name is : {{`What to type here?`}} 
                  </div>
              </div>
          </div>
          <div class="right-div">
              <div class="details">
                  <h1>BIO</h1>
                  <p>{{p.bio}}</p>
                  <h1>Age:</h1>
                  <p>{{p.age}}</p>
              </div>
          </div>
      </div>
  {% endfor %}

EDIT : My views.py:
I am passing a list named profiles which have filtered profiles based on 2-3 different type of filters so the code will be too much to paste here with all the references, I am pasting short version here:

start =18
end=25
 matching = UserInfo.objects.filter(
        age__gte=start, age__lte=end
    ).values()

    for z in matching:
        if z['user_id'] not in rec_ids:
            profiles.append(z)
    print('profiles====', profiles)

 context = {
        'profiles' : profiles,
    }

    return render(request, 'home/news.html', context)

Pasting the output here

profiles==== [{'id': 18, 'user_id': 8, 'image': 'uploads/user/image/9d096190-73a0-4885-b4cd-ef6c7229b9eb.png', 'age': 23, 'bio': 'cool'}..etc]

Thanks :)


Solution

  • As @khadim hussen wrote in the comment you should use the following syntax:

    p.user.first_name
    

    Where p is your UserInfo model and p.user is the reference to the OneToOne realationship.

    Note that when you don't have a related user with p -> user the value you will return is None.

    If you want to show different info than None in your template just can do the following:

    {{p.user or 'Without User'}}
    

    But in your views yo will get a RelatedObjectDoesNotExists to ensure in your views that the relationship exists you can make the following validation:

    p = UserInfo.objects.first()
    if hasattr(p, 'user'):
       user = p.user
    

    Other thing that is important is when you have a related_name property as in your example:

    user = models.OneToOneField(User, related_name="parent_user", on_delete=models.CASCADE)
    

    So in this case you need to refer to the user property with the related_name.

    In a more concrete way you have to do as following:

    {{ p.parent_user.first_name }}
    

    One important thing is that if you ar using queryset.filter(...).values() so if you want to load values from other model you can use the following:

    queryset.filter(...).values('image','age','bio','user','parent_user__first_name',...)
    ``
    And in your template:
    ```python
    {% p.parent_user__first_name %}
    

    That's it.