How to filter related model rows of a django model?

Currently all related objects are being returned. I want to restrict related objects in such a way if I try to get PortfolioUser's education, I only get which has the value show=True

models.py

from django.db import models
from django.db.models import Q
from django.db.models.signals import pre_save, post_save
# Create your models here.


class PortfolioUser(models.Model):

    GENDER_CHOICES = [
        ("M", "Male"),
        ("F", "Female")
    ]

    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    email = models.EmailField(max_length=254)
    phone_number = models.CharField(max_length=50)
    gender = models.CharField(choices=GENDER_CHOICES,
                              max_length=1, null=True, default='M')
    about = models.TextField(null=True, default="Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad, blanditiis optio. Quia, corporis quidem. Nam aliquam officia rerum consequatur nisi ducimus nihil saepe aut excepturi, accusantium aspernatur possimus quod error!")
    is_active = models.BooleanField(default=False)

    class Meta:
        verbose_name = ("PortfolioUser")
        verbose_name_plural = ("PortfolioUsers")

    def __str__(self):
        return self.first_name+" "+self.last_name

    def get_absolute_url(self):
        return reverse("PortfolioUser_detail", kwargs={"pk": self.pk})


class Education(models.Model):
    education_name = models.CharField(max_length=254)
    institute_name = models.CharField(max_length=254)
    start_year = models.DateField(auto_now=False, auto_now_add=False)
    end_year = models.DateField(auto_now=False, auto_now_add=False)
    marks = models.CharField(max_length=50)
    show = models.BooleanField(default=True)

    portfolio_user = models.ForeignKey(
        PortfolioUser, on_delete=models.CASCADE, related_name="educations")

    class Meta:
        verbose_name = ("Education")
        verbose_name_plural = ("Educations")

    def __str__(self):
        return self.education_name+" -"+self.portfolio_user.first_name

    def get_absolute_url(self):
        return reverse("Education_detail", kwargs={"pk": self.pk})


class Project(models.Model):

    project_name = models.CharField(max_length=50)
    about = models.TextField(null=False)
    website = models.CharField(max_length=100, null=True, blank=True)
    github = models.CharField(max_length=100, null=True, blank=True)
    thumbnail = models.ImageField(upload_to="projects/thumbnails",
                                  height_field=None, width_field=None, max_length=None)
    show = models.BooleanField(default=True)

    portfolio_user = models.ForeignKey(
        PortfolioUser, on_delete=models.CASCADE, related_name="projects")

    class Meta:
        verbose_name = ("Project")
        verbose_name_plural = ("Projects")

    def __str__(self):
        return self.project_name+" -"+self.portfolio_user.first_name

    def get_absolute_url(self):
        return reverse("Project_detail", kwargs={"pk": self.pk})


def portfolio_user_active_changed_signal(sender, instance, **kwargs):
    thisObj = PortfolioUser.objects.filter(id=instance.id).first()
    if not thisObj.is_active == instance.is_active:
        # field is changed
        otherPortfolioUsers = PortfolioUser.objects.filter(~Q(id=instance.id))
        otherPortfolioUsers.update(is_active=False)


def make_default_portfolio_user_active(sender, instance, **kwargs):
    # gets at leat one active user if exists
    active_portfolio_user = PortfolioUser.objects.filter(
        is_active=True).first()
    if not active_portfolio_user:
        first_user = PortfolioUser.objects.filter(id=1)
        first_user.update(is_active=True)


pre_save.connect(portfolio_user_active_changed_signal, sender=PortfolioUser)
post_save.connect(make_default_portfolio_user_active, sender=PortfolioUser)

views.py

class PortfolioUserListView(ListView):
    model = PortfolioUser
    queryset = model.objects.filter(
        is_active=True, educations__show=True)
    template_name = "main/index2.html"
    context_object_name = "portfolio_user"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["title"] = context['portfolio_user']
        print(context)
        return context

I have two entries (rows) in Education. Only one of those rows has show = False.
But when I try to access portfolio_user.first.educations.all, it returns an object which still contains the value where show=False

How can I make it in such way that my final context only contains the related educations where show=true?

2 answers

  • answered 2021-06-19 18:14 acw

    There's no foreign key from PortfolioUser to Education. Something you can try is:

    # in your class view
    def get_queryset(self):
        return Education.objects.select_related(
            "portfolio_user"
        ).filter(
            show=True, portfoliouser__is_active=True
        ).only("portfolio_user")
    

    This way, even though a queryset of Education is returned to you, portfolio user is returned to you in this single query.

  • answered 2021-06-19 18:17 Yousef Alm

    portfolio_user.first().educations.filter(show=False)