python-3.xdjangodjango-models

How to extend a User's profile to save data in Django


I'm new to Django and I'm attempting to build a website where a user can view a DB of books and related authors and add any book to their 'favourites'. I've been searching lots and I can't find a satisfactory way of doing this - or indeed of saving any user data other than customising fields when you create a user.

My current idea is to extend the UserModel to add an extra field which links their favourite book to the user. Tips on UserModel found here

I can now see the UserProfile with some favourites by logging in to the Admin account, but I cannot link the View class/function with my bookfavs.html for the User to see their favourited books. The bookfavs.html states the username but not favourites.

readable_project app_name = bookshelf

Models.py

class UserProfile(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    favorites = models.ManyToManyField(Book, related_name='favourites')

class Author(models.Model):
    first_name=models.CharField(max_length=30)
    last_name=models.CharField(max_length=30)
    dob = models.DateField()
    bio = models.CharField(max_length=300)
    
    def __str__(self):
        return self.first_name + " " + self.last_name

    def get_absolute_url(self):
            return reverse('authorlist')

class Book(models.Model):
    id = models.AutoField(primary_key=True)
    users = models.ManyToManyField(User)
    title = models.CharField(max_length=50)
    num_page = models.IntegerField()
    date_pub = models.DateField()
    tags = models.CharField(max_length=200)
    blurb = models.CharField(max_length=300)

    def get_absolute_url(self):
            return reverse('')`

views.py

#Add favourite button...?
def favorite(request, pk):
    if request.method == 'POST':
        favorite = Book.objects.get(pk=pk)
        user = request.user
        user.favorites.add(favorite)
        messages.add_message(request, messages.INFO, 'Book Favorited.')
        return redirect('home')


#List ALL books
class BookList(ListView):
    model = Book
    template_name='bookshelf/booklist.html'

#List favourite books
class BookFavs(TemplateView):
    model = UserProfile
    template_name = 'bookshelf/bookfavs.html'
    def get_context_data(self,*args, **kwargs):
        context = super(BookFavs, self).get_context_data(*args,**kwargs)
        context['users'] = UserProfile.objects.all()
        return context

bookfavs.html

{% if user.username %} 
<h4 class="header-greet">Welcome {{ user.username }} to your books!</h4> 
{% endif %}

{% if user.is_authenticated %}
    {% with userprofile.favorites.all as favourite_books %}
      {% for book in book_list %}
        {% if book in favorite_book %}
        <p>{{ book.title }}</p>
        {% endif %}
      {% endfor %}
    {% endwith %}
{% endif %}


Solution

  • Problems :

    1. you are trying to access userprofile.favorites.all in the template but you are passing users (which is a list of all user profiles) to the template context.
    2. in your template you are looping through book_list, but it should be favourite_books (the user's favorite books) to check if the book is in their favorites.

    Code :

    # List favorite books
    class BookFavs(TemplateView):
        template_name = 'bookshelf/bookfavs.html'
    
        def get_context_data(self, *args, **kwargs):
            context = super().get_context_data(*args, **kwargs)
    
            # access the current user's profile and their favorite books
    
            if self.request.user.is_authenticated:
                user_profile = self.request.user.userprofile
                context['favorite_books'] = user_profile.favorites.all()
            return context
    

    You can change your bookfavs.html according to new context with some extra messages:

    {% if user.is_authenticated %}
      <h4>Welcome {{ user.username }}! Here are your favorited books:</h4>
    
      <ul>
        {% for book in favorite_books %}
          <li>{{ book.title }}</li>
    
        <div>
        <form method="post" action="{% url 'favorite' book.id %}">
        {% csrf_token %}
        {% if book in user.userprofile.favorites.all %}
            <button type="submit" class="btn btn-danger">Unfavorite</button>
        {% else %}
            <button type="submit" class="btn btn-primary">Favorite</button>
        {% endif %}
        </form>
        </div>
    
        {% empty %}
          <p>You have no favorite books yet.</p>
        {% endfor %}
      </ul>
    {% else %}
      <p>Please log in to view your favorite books.</p>
    {% endif %}
    

    Lets Tweak Fav button function to work that way : 1. remember to cross check whether he/she is logged in or not

    from django.shortcuts import get_object_or_404, redirect
    from django.contrib.auth.decorators import login_required
    from django.contrib import messages
    
    @login_required
    def favorite(request, pk):
        book = get_object_or_404(Book, pk=pk)
        user_profile, created = UserProfile.objects.get_or_create(user=request.user)
    
        if book in user_profile.favorites.all():
            user_profile.favorites.remove(book)
            messages.info(request, f'"{book.title}" has been removed from your favorites.')
        else:
            user_profile.favorites.add(book)
            messages.success(request, f'"{book.title}" has been added to your favorites.')
    
        #  ensures the user is redirected to the same page they came from or you can go for your own implementation
        return redirect(request.META.get('HTTP_REFERER', 'home'))
    

    I have added form in html accordingly. Dont forget to add a url for that in your urls.py

    path('favorite/<int:pk>/', favorite, name='favorite')