Search code examples
pythondjangoattributeerror

Python Django - AttributeError: 'IdeaView' object has no attribute 'user'


My website allows users to post their ideas and other users comment on the ideas and rate the idea from 1 to 10. The Idea page loads with the users idea and the rating form and comment form, but when a logged in user tries to submit the comment form or rating form they encounter the AttributeError. Because the page loads I believe the get() method is working, but I don't believe the post() method is working. How do I access the user attribute in my view?

Here is my class based view that handles the idea page:

class IdeaView(TemplateView):
    template_name = 'idea.html'

    def get_average_rating(self, idea):
        """get average rating for an idea"""
        ratings = Rating.objects.filter(idea=idea)
        # aggregate function used to perform calcs on database records. Takes one or more arguments.
        # returns dictionary containing results of calcs.
        # rating__sum is the key used to access the sum of the 'rating' values from the dictionary.
        total_ratings = ratings.aggregate(Sum('rating'))['rating__sum'] or 0
        number_of_ratings = ratings.count() or 0
        return total_ratings / number_of_ratings
        
    def get_context_data(self, **kwargs):
        # call corresponding method of parent class and updating context dict. with data defined in get_context_data() method
        context = super().get_context_data(**kwargs)
        # add additional data to context dictionary that is available to all when accessing this method
        context['user'] = self.request.user
        context['rating_form'] = RatingForm()
        context['comment_form'] = CommentForm()
        return context
    
    def get(self, request, slug):
        """ get idea, user comments and forms for displaying to user"""
        idea = Idea.objects.get(slug=slug)
        rating = self.get_average_rating(idea)

        # double underscore __ is used to perform lookups that span relationships
        # idea__slug means that we are transversing the 'idea' relationship and filtering the slug field of the 'Idea' model
        comments = Comment.objects.filter(idea__slug=slug).order_by('date_commented')
        context = {'idea': idea, 'rating': rating, 'comments': comments}
        context.update(self.get_context_data(**context))
        return render(request, self.template_name, context)
    

    @login_required
    def post(self, request, slug):
        """handle Post request for user comment or rating of idea.
        Redirect to login screen  if not logged in."""
        comment_form = CommentForm(request.POST)
        rating_form = RatingForm(request.POST)

        # allow for updating user rating if they've already rated. if rating exits then update.
        # I've added __init__ function in RatingForm.
        # I used filter() because it returns an empty query_set whereas get() raises 
        # a DoesNotExist exception that needs to be handled.
        idea = Idea.objects.get(slug=slug)
        rating_exists = Rating.objects.filter(idea=idea, author=self.request.user).first()

        if rating_exists and rating_form.is_valid():
            # update existing rating
            rating_exists.rating = rating_form.cleaned_data['rating']
            rating_exists.idea = idea
            rating_exists.author = self.request.user
            rating_exists.save()
        elif comment_form.is_valid() and not rating_form.is_valid():
            # only save comment form
            comment = comment_form.save(commit=False)
            comment.idea = idea
            comment.author = self.request.user
            comment.save()
        elif rating_form.is_valid() and not comment_form.is_valid():
            # only save users rating form
            rating = rating_form.save(commit=False)
            rating.idea = idea
            rating.author = self.request.user
            rating.save()
        
        rating = self.get_average_rating(idea)
        comments = Comment.objects.filter(idea__slug=slug).order_by('date_commented')
        context = {'idea': idea, 'rating': rating, 'comments': comments}
        context.update(self.get_context_data(**context))
        return render(request, self.template_name, context)

Here are my models:

from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.template.defaultfilters import slugify

class Idea(models.Model):
    title = models.CharField(max_length=255, unique=True)
    idea = models.TextField()
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='idea_authored')
    date_posted = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    slug = models.SlugField(unique=True, blank=True, null=True)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title + ' ' + self.author.username)
            super().save(*args, **kwargs)


    def __str__(self):
        return f'Idea: {self.title} by {self.author.username}'
    

class Rating(models.Model):
    idea = models.ForeignKey(Idea, on_delete=models.CASCADE, related_name='idea_rating')
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='authors_rating')
    rating = models.DecimalField(max_digits=3, decimal_places=1)


class Comment(models.Model):
    idea = models.ForeignKey(Idea, on_delete=models.CASCADE, related_name='idea_comments')
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='authors_comment')
    comment = models.TextField()
    date_commented = models.DateTimeField(auto_now_add=True)

Here is my html template:

{% block content %}

<div class="container">
<div class="row d-flex justify-content-between">
    <div>
        <h1>{{ idea.author.username }}</h1>
        <h5 style="text-indent: 25px;">Date: {{ idea.date_posted }}</h5>
    </div>
    <div class="border border-dark rounded">
        <h5>{{ rating }}/10</h5>
        <form method="post">
            {% csrf_token %}
            {{ rating_form.as_p }}

            {% if user.is_authenticated %}
            <button type="submit" class="btn btn-primary">Submit</button>
            {% else %}
            <a href="{% url 'users:login' %}?next={{ request.path }}" class="btn btn-primary">Login</a>
            {% endif %}
        </form>
    </div>
</div>
</div>
<h5>{{ idea.title }}</h5>
<p class="border border-dark rounded">{{ idea.idea }}</p>
<h4>Comments:</h4>

{% if comments %}

    {% for comment in comments %}
        <h5 style="text-indent: 25px;">{{ comment.author.username }} ({{ comment.date_commented }})</h5>
        <p class="border border-dark">{{ comment.comment }}</p>
    {% endfor %}

{% else %}

    <h4>Be the first to comment.</h4>

{% endif %}

<form method="post">
    {% csrf_token %}
    {{ comment_form.as_p }}
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

{% endblock %}

I've tried changing request.user to self.request.user and I've tried putting self.request.user in the get_context_data() method, but no luck.


Solution

  • Have you tried like this?

    from django.contrib.auth.decorators import login_required
    from django.utils.decorators import method_decorator
    
    
    @method_decorator(login_required, name="post")
    class IdeaView(TemplateView):
        ...
    

    learn more here: ref

    Hope this helps!