Django Q() for filtering returns the same objects multiple times

34 Views Asked by At

I am making a tutoring dashboard in Django. The tools used are htmx,jquery, bootstrap.

In the tutors.html page, there is a searchbar. When an user types something, it will send a request to the server and it would call the view search_tutor. The following is the code:

<form method="POST">
 {% csrf_token %}
   <div class="form-floating mb-3">

      <input class="form-control" id="search-input" hx-get="{% url 'search_tutor' %}" hx-target="#tutors" hx-trigger="keyup changed" name="q"  placeholder="Search Tutor" data-sb-validations="required">
      <label class="text-muted" for="q"> Search Tutor</label>
   </div>
</form>
            
<div id="tutors" style="transition:1s">
       {% include "templates/partials/tutors.html" %}
</div>

The Tutor Model is the following:

class Account(models.Model):
    token = models.CharField(max_length=50, unique=True)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    profile_picture = models.ImageField(upload_to=upload_prof_pic, blank=True, null=True)
    
    
    school = models.ForeignKey(School, on_delete=models.CASCADE , null=True)
    country = models.ForeignKey(Country, on_delete=models.DO_NOTHING, related_name='account_country', null=True)
    phone = models.CharField(max_length=20, blank=False)
    added = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    stripe_account = models.CharField(max_length=300, blank=True)
    stripe_client = models.CharField(max_length=300, blank=True)
    
    
    
    is_tutor = models.BooleanField(default=False)
    description = models.TextField(max_length=600, blank=False)
    tutor_subject = models.ManyToManyField(Subject, blank=True, null=True )

    
    def __str__(self):
        return str(self.user)
        
    def get_phone(self, *args, **kwargs):
        if self.country != None:
            return f"+{self.country.phone_extension} ({self.phone1})-{self.phone2}-{self.phone3}"
        else:
            return "NONE"



    def rating_score (self, *args, **kwargs):
        total = 0
        amount = 0
        r = Rating.objects.filter(user=self.user)
        if r.count() == 0 :
            return 0
        else:
            for i in r.all():
                total += i.rating
                amount +=1
        
        return total/amount

    def rating_count(self, *args, **kwargs):
        r = Rating.objects.filter(user=self.user)
        return r.count()
    
    def full_name(self, *args, **kwargs):
        return f'{self.user.first_name.title()} {self.user.last_name.title()}'
        

I think my problem could be in the filter on the view. I do not know what is happening, but when I type something in the searchbar, multiple copies of the same object appear. Here is my search_tutor view:


def search_tutor(request):
    x = Account.objects.get(user=request.user)
    if request.method == "GET":
        q = request.GET['q']
        a= Account.objects.filter(
            Q(is_tutor=True)
            &(
                Q(user__first_name__icontains=q)|
                Q(user__last_name__icontains=q)|
                Q(user__email__icontains=q)|
                Q(tutor_subject__name__icontains=q)|
                Q(tutor_subject__abbr__icontains=q)|
                Q(user__username__icontains=q)|
                Q(description__icontains=q)

            )
        )
            context= {'a': a}
    return render(request, f"{mp}/tutors.html", context)

Here are images: When the page opens, it shows the right amount of users. When I start typing, more than on of the same item is returned

Any help would be greatly appreciated.

I tried doing some searches but it seems that I am the only one.

1

There are 1 best solutions below

0
On

That is the result of filtering on one-to-many and many-to-many relations where there are multiple matches in the related models.

You can use .distinct() to return each item at most once:

def search_tutor(request):
    x = Account.objects.get(user=request.user)
    if request.method == 'GET':
        q = request.GET['q']
        a = Account.objects.filter(
            Q(is_tutor=True)
            & (
                Q(user__first_name__icontains=q)
                | Q(user__last_name__icontains=q)
                | Q(user__email__icontains=q)
                | Q(tutor_subject__name__icontains=q)
                | Q(tutor_subject__abbr__icontains=q)
                | Q(user__username__icontains=q)
                | Q(description__icontains=q)
            )
        ).distinct()
    return render(request, f'{mp}/tutors.html', {'a': a})