AttributeError at /addtowatchlist/5: 'QuerySet' object has no attribute 'listings'

Question:

Within my app, I’m allowing any given user to add an auction listing to their watch list, but no matter what I have tried, it does not seem to work and I keep getting the error in the title. I am simply trying to access the user thats making the request and adding the listing to their watchlist and redirecting them to the main page.

views.py

from .models import *

def add_to_watchlist(request, listing_id):
    
    listing = Listing.objects.get(id=listing_id)
    # Retrieving the user watchlist (where it always fails)
    watchlist = PersonalWatchList.objects.filter(user=request.user)
    

    # Fails here too
    if (watchlist.listings.all() == None) or (listing not in watchlist.listings.all()):
        watchlist.listings.add(listing)
        watchlist.save()
    else:
        messages.error(request, "Listing is already in your Watchlist.")
        return redirect(reverse('index'))

    messages.success(request, "Listing added to your Watchlist.")
    return redirect(reverse('index'))

models.py

class PersonalWatchList(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    listings = models.ManyToManyField(Listing, blank=True)

    def __str__(self):
        return f"Watchlist for {self.user}"

urls.py

from django.urls import path

from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("login", views.login_view, name="login"),
    path("logout", views.logout_view, name="logout"),
    path("register", views.register, name="register"),
    path("create", views.createListing, name="create"),
    path("view/<str:listing_title>", views.view, name="view"),
    path("addtowatchlist/<int:listing_id>", views.add_to_watchlist, name="add_to_watchlist")
]

Section of template used to add listing to watchlist

<div class="listing-actions">
<a href= {% url 'view' listing.title %} class="btn btn-primary view-button">View</a>
<!--TODO: Make watchlist-->
<a href={% url 'add_to_watchlist' listing.id %} class="btn btn-primary add-to-watchlist-button">Watchlist</a>
</div> 

I have tried changing my logic around using try and except, but it still results in a very similar error. In my urls.py I have tried changing the path names to avoid them from overlapping, as the "view" and "add_*to_*watchlist" were similar before, but that change still has not worked. Before, in

watchlist = PersonalWatchList.objects.filter(user=request.user)

I used get() instead, and that wasn’t working either. Any tips would be greatly appreciated!

Edit:
When I add listings to a given users watchlist through django admin, that works just fine, which I really don’t understand how, but through the server itself it fails

Asked By: Ronak Patel

||

Answers:

To retrieve an individual user’s watchlist, use get instead of filter:

watchlist = PersonalWatchList.objects.get(user=request.user)

Using the filter() method is returning a QuerySet (Multiple objects) as opposed to the single unique one you want.

The other issue is in your model definition. To use the default user class as a foreign key, you need to use

turning this:

user = models.ForeignKey(User, on_delete=models.CASCADE)

into this:

user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Try this and comment your results.

I essentially had to change up my logic, as I came to understand that the reason this error was being thrown was because the User did not exist yet in the PersonalWatchList Model. Here is the updated workaround.

views.py updated

def add_to_watchlist(request, listing_id):
    user = request.user

    # Getting the listing that was clicked
    listing = Listing.objects.get(id=listing_id)

    # Retrieving the user watchlist (if it exists)
    try:
        watchlist_exists = PersonalWatchList.objects.get(user=request.user)
    except:
        watchlist_exists = None

    # If there isn't one for this user, make one
    if watchlist_exists == None:
        PersonalWatchList.objects.create(user=user)

    # Access that watchlist
    watchlist = PersonalWatchList.objects.get(user=user)

    # Comparing the listings already present in their watchlist to the one that was hit
    if (listing not in watchlist.listings.all()):
        watchlist.listings.add(listing)
        watchlist.save()
    else:
        messages.error(request, "Listing is already in your Watchlist.")
        return redirect(reverse('index'))

    messages.success(request, f"'{listing}' added to your Watchlist.")
    return redirect(reverse('index'))

Answered By: Ronak Patel