AttributeError After processing the view - django

After Submitting the like button data is successfuly updating with the database but after this step it won't redirecting to the successful url. instead of that it is throwing attribute error. If I use HttpResponseRedirect('/album/') instaed of successful url this error is not comming. Please refer this link for the traceback
models.py
Codes in models.py
class VoteManager(models.Manager):
def get_vote_or_unsaved_blank_vote(self,song,user):
try:
return Vote.objects.get(song=song,user=user)
except ObjectDoesNotExist:
return Vote.objects.create(song=song,user=user)
class Vote(models.Model):
UP = 1
DOWN = -1
VALUE_CHOICE = ((UP, "👍️"),(DOWN, "👎️"),)
like = models.SmallIntegerField(null=True, blank=True, choices=VALUE_CHOICE)
user = models.ForeignKey(User,on_delete=models.CASCADE)
song = models.ForeignKey(Song, on_delete=models.CASCADE)
voted_on = models.DateTimeField(auto_now=True)
objects = VoteManager()
class Meta:
unique_together = ('user', 'song')
views.py
codes in views.py
class SongDetailView(DetailView):
model = Song
template_name = 'song/song_detail.html'
def get_context_data(self,**kwargs):
ctx = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
vote = Vote.objects.get_vote_or_unsaved_blank_vote(song=self.object, user = self.request.user)
vote_url = reverse('music:song_vote_create', kwargs={'song_id':vote.song.id})
vote_form = SongVoteForm(instance=vote)
ctx['vote_form'] = vote_form
ctx['vote_url'] = vote_url
return ctx
class SongVoteCreateView(View):
form_class = SongVoteForm
context = {}
def get_success_url(self,**kwargs):
song_id = self.kwargs.get('song_id')
return reverse('music:song_detail', kwargs={'pk':song_id})
def post(self,request,pk=None,song_id=None):
user = self.request.user
song_obj = Song.objects.get(pk=song_id)
vote_obj,created = Vote.objects.get_or_create(song = song_obj,user = user)
vote_form = SongVoteForm(request.POST, instance=vote_obj)
if vote_form.is_valid():
new_vote = vote_form.save(commit=False)
new_vote.user = self.request.user
new_vote.save()
return new_vote
song_detail.html
codes in html page.
<form action="{{vote_url}}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ vote_form.as_p }}
<button class="btn btn-primary" type="submit" >Vote</button>
</form>
Error code
Please refer this link for the traceback
'Vote' object has no attribute 'get'

The post method needs to return an HttpResponse, not a Vote object.
But you shouldn't be defining post in the first place. All that code should go in form_valid.
def form_valid(self, form):
user = self.request.user
song_obj = Song.objects.get(pk=self.kwargs['song_id'])
vote_obj, _ = Vote.objects.get_or_create(song = song_obj, user = user)
form.instance = vote_obj
return super().form_valid(form)
Note, you don't need to check is_valid, and you also don't need to set the user as you've already done that in vote_obj.

You are returning Vote object from post method in your SongVoteCreateView view. You should return Response instead. Django doesn't know what to do with model object returned from a view.

Related

DJANGO: NOT NULL constraint failed: courses_comment.lesson_id

I try create comments form add and take error. But I'm not shure that I correctly use lesson = at view.py at def post function.
Can You help me?
models.py:
class Comment(models.Model):
text = models.TextField('Comment text')
user = models.ForeignKey(User, on_delete=models.CASCADE)
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE)
view.py:
class LessonDetailPage(DetailView):
....
def post(self, request, *args, **kwargs):
lesson = Lesson.objects.filter(slug=self.kwargs['lesson_slug']).first()
post = request.POST.copy()
post['user'] = request.user
post['lesson'] = lesson
request.POST = post
form = CommentForms(request.POST)
if form.is_valid():
form.save()
part of urls.py
path('course/<slug>/<lesson_slug>', views.LessonDetailPage.as_view(), name='lesson-detail'),
forms.py:
class CommentForms(forms.ModelForm):
text = forms.CharField(
label='Text',
required=True,
widget=forms.Textarea(attrs={'class': 'form-control'})
)
user = forms.CharField(
widget=forms.HiddenInput()
)
lesson = forms.CharField(
widget=forms.HiddenInput()
)
class Meta:
model = Comment
fields = ['text']
comment.html
<div class="form-section">
<form method="post">
{% csrf_token %}
{{ form }}
<button type="submit">ОК</button>
</div>
And my Error
IntegrityError at /course/linux/set-on-linux
NOT NULL constraint failed: courses_comment.lesson_id
Request Method: POST
Request URL: http://127.0.0.1:8000/course/linux/set-on-linux
Django Version: 4.0.6
Exception Type: IntegrityError
Exception Value:
NOT NULL constraint failed: courses_comment.lesson_id
My suspicion is that this is causing your issue:
lesson = Lesson.objects.filter(slug=self.kwargs['lesson_slug']).first()
What this is doing is returning the first Lesson object in a queryset filtered by your lesson slug. However, filter will return an empty queryset if there are no results. Running first() on that empty queryset will return nothing, which would explain why an ID is not being passed to your form.
To solve this, you just need to catch whether the lesson object is empty:
if lesson is None:
# do something else
As an aside, combining .filter() and .first() is generally not recommended as you are potentially being vague with your object selection. Using .get() will get you a single object and return an error if two or more are returned. The downside with .get() is that it will also raise an exception if nothing is returned, so you need to handle both outcomes in your view.
But better at forms.py write:
class CommentForms(forms.ModelForm):
class Meta:
model = Comment
fields = ['text', 'user', 'lesson'] #here is the answer
widgets = {'user': forms.HiddenInput(), 'lesson': forms.HiddenInput()}
then at views.py:
def post(self, request, *args, **kwargs):
course = Course.objects.filter(slug=self.kwargs['slug']).first()
lesson = Lesson.objects.filter(slug=self.kwargs['lesson_slug']).first()
post = request.POST.copy()
post['user'] = request.user
post['lesson'] = lesson
request.POST = post
form = CommentForms(request.POST)
if form.is_valid():
form.save()
May be not best solution but It's work:
def post(self, request, *args, **kwargs):
lesson = Lesson.objects.filter(slug=self.kwargs['lesson_slug']).first()
post = request.POST.copy()
post['user'] = request.user
post['lesson'] = lesson
request.POST = post
form = CommentForms(request.POST)
if form.is_valid():
comment = Comment(text=request.POST['text'], user=request.POST['user'], lesson=request.POST['lesson'])
comment.save()

How to delete / edit posts as staff or superuser?

I am fairly new to Django and right now not very familiar with the concept of Django. I created a user board (from simpleisbetterthancomplex.com) and would like to implement a Moderation with a "is_staff" user.
For now, only the user which created a post / comment is able to edit the post. As a is_staff user, I also want be able to edit ALL posts / comments.
This is the edit button:
{{ post.message }}
{% if post.created_by == user %}
<div class="mt-3">
<a href="{% url 'edit_post' post.topic.board.pk post.topic.pk post.pk %}"
class="btn btn-primary btn-sm"
role="button">Edit</a>
</div>
{% endif %}
I thought I could so something like:
{{ post.message }}
{% if user.is_staff %}
<div class="mt-3">
<a href="{% url 'edit_post' post.topic.board.pk post.topic.pk post.pk %}"
class="btn btn-primary btn-sm"
role="button">Edit</a>
</div>
{% endif %}
Although I reach the correct hyperlink to edit the post, I receive a "Page not found" error.
What could be an approach to implement some Moderation to my forum?
It's hard to tell what's wrong with your code. It seems both code samples should display the same button.
Please note that in this situation you should likely use a single button and tag, by changing the if to a slightly more complicated {% if user.is_staff or post.created_by == user %}. This should have the added effect of eliminating all possible discrepancies between the two buttons.
If you just want the ability to edit/delete posts, then the simplest way would likely be to use the built-in django admin panel. If you used django startproject, then your app already has one! Try going to localhost:8000/admin (by default) to check it out.
https://docs.djangoproject.com/pl/2.1/ref/contrib/admin/
EDIT: I think I can see the problem. You filter your queryset in PostUpdateView by (created_by=self.request.user). This filter works differently when dealing with a different user, such as the moderator. Try changing the view to reflect this.
The issue is the URL returned by edit_post. This view only allows access to owners of the post, so no one else can access this view.
You would need to add to the view to allow users with is_staff = True to also access this view.
The problem is with the queryset definition filtering out models not created by the user. But you want to keep this, so that other users don't have access.
You could remove the get_queryset call and adjust the dispatch method to only allow the owner or staff members to view. But I suggest keeping this intact and add a new moderator update view and use django braces to sort out the permissions. Something like;
from braces.views import StaffuserRequiredMixin
class ModeratorUpdateView(LoginRequiredMixin,
StaffuserRequiredMixin,
UpdateView):
## Update Code here ##
The view of my board:
class BoardListView(ListView):
model = Board
context_object_name = 'boards'
template_name = 'home.html'
class TopicListView(ListView):
model = Topic
context_object_name = 'topics'
template_name = 'topics.html'
paginate_by = 5
def get_context_data(self, **kwargs):
kwargs['board'] = self.board
return super().get_context_data(**kwargs)
def get_queryset(self):
self.board = get_object_or_404(Board, pk=self.kwargs.get('pk'))
queryset = self.board.topics.order_by('-last_updated').annotate(replies=Count('posts') - 1)
return queryset
#login_required
def new_topic(request, pk):
board = get_object_or_404(Board, pk=pk)
if request.method == 'POST':
form = NewTopicForm(request.POST)
if form.is_valid():
topic = form.save(commit=False)
topic.board = board
topic.starter = request.user # <- here
topic.save()
Post.objects.create(
message=form.cleaned_data.get('message'),
topic=topic,
created_by=request.user # <- and here
)
return redirect('topic_posts', pk=pk, topic_pk=topic.pk) # TODO: redirect to the created topic page
else:
form = NewTopicForm()
return render(request, 'new_topic.html', {'board': board, 'form': form})
def topic_posts(request, pk, topic_pk):
topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk)
topic.views += 1
topic.save()
return render(request, 'topic_posts.html', {'topic': topic})
#login_required
def reply_topic(request, pk, topic_pk):
topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk)
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.topic = topic
post.created_by = request.user
post.save()
topic.last_updated = timezone.now()
topic.save()
topic_url = reverse('topic_posts', kwargs={'pk': pk, 'topic_pk': topic_pk})
topic_post_url = '{url}?page={page}#{id}'.format(
url=topic_url,
id=post.pk,
page=topic.get_page_count()
)
return redirect(topic_post_url)
else:
form = PostForm()
return render(request, 'reply_topic.html', {'topic': topic, 'form': form})
#method_decorator(login_required, name='dispatch')
class PostUpdateView(UpdateView):
model = Post
fields = ('message', )
template_name = 'edit_post.html'
pk_url_kwarg = 'post_pk'
context_object_name = 'post'
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(created_by=self.request.user)
def form_valid(self, form):
post = form.save(commit=False)
post.updated_by = self.request.user
post.updated_at = timezone.now()
post.save()
return redirect('topic_posts', pk=post.topic.board.pk, topic_pk=post.topic.pk)
class PostListView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'topic_posts.html'
paginate_by = 20
def get_context_data(self, **kwargs):
session_key = 'viewed_topic_{}'.format(self.topic.pk)
if not self.request.session.get(session_key, False):
self.topic.views += 1
self.topic.save()
self.request.session[session_key] = True
kwargs['topic'] = self.topic
return super().get_context_data(**kwargs)
def get_queryset(self):
self.topic = get_object_or_404(Topic, board__pk=self.kwargs.get('pk'), pk=self.kwargs.get('topic_pk'))
queryset = self.topic.posts.order_by('created_at')
return queryset
And the models.py:
from django.db import models
from django.contrib.auth.models import User
from django.utils.text import Truncator
import math
class Board(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
def get_posts_count(self):
return Post.objects.filter(topic__board=self).count()
def get_last_post(self):
return Post.objects.filter(topic__board=self).order_by('-created_at').first()
class Topic(models.Model):
subject = models.CharField(max_length=255)
last_updated = models.DateTimeField(auto_now_add=True)
board = models.ForeignKey(Board, on_delete=models.CASCADE, related_name='topics')
starter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='topics')
views = models.PositiveIntegerField(default=0) # <- here
def __str__(self):
return self.subject
def get_page_count(self):
count = self.posts.count()
pages = count / 20
return math.ceil(pages)
def has_many_pages(self, count=None):
if count is None:
count = self.get_page_count()
return count > 6
def get_page_range(self):
count = self.get_page_count()
if self.has_many_pages(count):
return range(1, 5)
return range(1, count + 1)
def get_last_ten_posts(self):
return self.posts.order_by('-created_at')[:10]
class Post(models.Model):
message = models.TextField(max_length=4000)
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(null=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
updated_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='+')
def __str__(self):
truncated_message = Truncator(self.message)
return truncated_message.chars(30)

Difference between args=[topic.id] and args=[topic_id] when using the return HttpResponseRedirect(reverse) in Django

I'm following a tutorial from to build a simple blog app with Django.
I have noticed that in the new_entry() view, we need to pass topic_id in agrs when using the reverse function:
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST':
#No data submitted, create a blank form
form = EntryForm()
else:
#POST data submitted; process data
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
new_entry.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
context = {'topic': topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
However, when creating the edit_entry() view (that allows users to edit existing entries), we need to pass topic.id
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != 'POST':
#Initial request, render the form with current entry
form = EntryForm(instance=entry)
else:
#Submit changes, process data
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
context = {'topic':topic, 'entry':entry, 'form':form}
return render(request,'learning_logs/edit_entry.html', context)
Initially I thought this was a mistake so I used args=[topic_id] in both reverse functions and it worked fine
Later, I decided I wanted to add a title to each entry so I made some minor changes to models.py, migrated those changes to the database and then changed the templates to include {{entry.title}} in them.
Basically, all I did was add this code to models.py
title = models.CharField(max_length=200, default='Add a title')
models.py:
class Topic(models.Model):
"""A topic the user is learning about"""
text = models.CharField(max_length = 200)
date_added = models.DateTimeField(auto_now_add = True)
def __str__(self):
"""Return a string representation of the model"""
return self.text
class Entry(models.Model):
"""A blog post about a particular topic"""
topic = models.ForeignKey(Topic)
title = models.CharField(max_length=200, default='Add a title')
text = models.TextField()
date_added = models.DateTimeField(auto_now_add = True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Return a string representation of the model"""
char_numb = len(self.text)
if char_numb > 50:
return self.text[:50] + "..."
else:
return self.text
forms.py:
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text','title']
labels = {'text':'', 'title': ''}
widgets = {'text': forms.Textarea(attrs={'cols': 80})}
After adding these changes, I got the following error when I tried to edit an entry's default title:
NameError at /edit_entry/4/
global name 'topic_id' is not defined
I changed args=[topic_id] to args=[topic.id] in the views.py file edit_entry() view and now it works fine, any idea why this is the case? What difference is there between topic_id and topic.id in this context?
This is the edit_entry.html template in case it makes any difference:
{% extends "learning_logs/base.html" %}
{% block content %}
<h1>{{topic}}
</h1>
<p>Edit your entry</p>
<form action = "{% url 'learning_logs:edit_entry' entry.id %}" method
= 'post'>
{% csrf_token %}
{{ form.as_p }}
<button name = "submit">save changes</button>
</form>
{% endblock content %}
Thanks in advance for any advice
In your first view, you have topic_id from the url and you fetch topic from the database on the first line, so you can use either topic_id or topic in the view.
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
In the template context for they view, you set topic but not topic_id. Therefore you can only use topic.id in the template.
context = {'topic': topic, 'form': form}
In your second view, you get entry_id from the url and get topic via the entry. You don’t set topic_id anywhere so you must use topic.
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic

Django how to add taggit tags to model within template

I have a Django app that has a list of companies, and when the user clicks on a company they are presented with all of the details of that company. Now I'm trying to add a tag feature so any use can add tags to the company which they'll also be able to search on. I've created the form and the template stuff, but now I'm stuck now on how to wire it into the View, specifically updating the Company model with the new tag(s).
Forms.py
class TagForm(forms.Form):
tag = forms.CharField(label='Tag', max_length=100)
class Meta:
model = Company
field = 'tag'
Template
<div class="add-tag-form">
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default btn-sm">Add</button>
</form>
</div>
View
class CompanyDetails(generic.DetailView):
model = Company
template_name = 'company_details.html'
# This was my attempt to work it into the View...
def post(self, request, *args, **kwargs):
form = TagForm(request.POST)
if form.is_valid():
tag = form.save(commit=False)
tag.company = Company.objects.get(pk=pk)
tag.save()
def get_context_data(self, **kwargs):
pk = self.kwargs.get('pk')
context = super(CompanyDetails, self).get_context_data(**kwargs)
context['articles'] = Articles.objects.filter(company_id=pk).order_by('-date')
context['transcripts'] = Transcripts.objects.filter(company_id=pk).order_by('-date')
context['form'] = TagForm()
# Yahoo Finance API data
stock_symbol = self.object.stock_symbol
data = Share(stock_symbol)
stock_open = data.get_open()
year_range = data.get_year_range()
fifty_day_moving_average = data.get_50day_moving_avg()
market_cap = data.get_market_cap()
yahoo_finance = dict()
yahoo_finance['stock_open'] = stock_open
yahoo_finance['year_range'] = year_range
yahoo_finance['fifty_day_moving_average'] = fifty_day_moving_average
yahoo_finance['market_cap'] = market_cap
context['yahoo_finance'] = yahoo_finance
# Yahoo News RSS data
yahoo_news = feedparser.parse("http://finance.yahoo.com/rss/headline?s=" + stock_symbol)
yahoo_news = yahoo_news['entries']
for item in yahoo_news:
item['published'] = datetime.strptime(item['published'], '%a, %d %b %Y %H:%M:%S %z')
context['yahoo_news'] = yahoo_news
return context
UPDATE
The error I get "'TagForm' object has no attribute 'save'". When I change the form from forms.Form to forms.FormModel I get the following error "Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited; form TagForm needs updating.". I'm not trying to create a model, rather update it through the form.
Taggit is used in the Model
class Company(models.Model):
company_name = models.CharField(max_length=200)
tags = TaggableManager()
One thing that you can do is to make a simple form for the tag, just like what you did but remove the Meta information and update the post method to just add the tag in the company. Something like below:
# forms.py
class TagForm(forms.Form):
tag = forms.CharField(label='Tag', max_length=100)
# views.py
class CompanyDetails(generic.DetailView):
...
# This was my attempt to work it into the View...
def post(self, request, *args, **kwargs):
company = self.get_object()
form = TagForm(request.POST)
if form.is_valid():
company.tags.add(form.cleaned_data['tag'])
return self.render_to_response(self.get_context_data())

Django - ModelForm does not save because of missing user id

I get:
IntegrityError at /category/
NOT NULL constraint failed: transactions_category.user_id
here in my view:
elif "create" in request.POST:
createform = CategoryForm(data=request.POST)
createform.save(commit=False)
createform.user = request.user
createform.save() <--- specifically here, the .save with commit=False goes through and I can also set the user
I checked with the debugger and createform.user has a User, that User also has an id.
forms.py:
class CategoryForm(ModelForm):
class Meta:
model = Category
exclude = ["subcategory", "user"]
models.py:
class Category(models.Model):
title = models.CharField(max_length = 100)
subcategory = models.ManyToManyField("self", blank=True, symmetrical=False)
user = models.ForeignKey(User)
User import is:
from django.contrib.auth.models import User
Why will it not save? I set the user before calling .save(), the user is set (and has an id) as far as I can tell with the debugger?
I have run both manage.py migrate and manage.py makemigrations and everything is up to date.
Edit:
the template:
<p>Create new category form</p>
<form action="/category/" method="post">
{% csrf_token %}
{{ createform.as_p }}
<input type="submit" name="create" value="Create new category">
</form>
<hr>
The creation and saving of new Category objects worked before I added the "user" field to the model (and I ran migrate and makemigrations after I did)
Edit 2:
complete view:
def category_view(request):
createform = CategoryForm()
searchform = CategorySearchForm()
categories = Category.objects.all()
categories_left = []
categories_right = []
if request.method == "POST":
if "search" in request.POST:
categories_left = Category.objects.for_title(request.POST["left-search"], user=request.user)
categories_right = Category.objects.for_title(request.POST["right-search"], user=request.user)
elif "create" in request.POST:
createform = CategoryForm(request.POST)
if createform.is_valid():
createform.save(commit=False)
createform.user = request.user
createform.save()
elif "add-subcategory" in request.POST:
left = Category.objects.for_id(request.POST["left-category"], user=request.user)
right = Category.objects.for_id(request.POST["right-category"], user=request.user)
try:
left.subcategory.add(right)
old = left
old.pk = None
old.save()
except IntegrityError:
print("This combination already exists.") # TODO
context = {"createform":createform,
"searchform":searchform,
"categories":categories,
"categories_left":categories_left,
"categories_right":categories_right}
return render(request, "transactions/category_creation.html", context)
Try to save passing an instance of user.
createform.save(commit=False)
createform.instance.user = request.user
createform.save()
The form's save method returns an instance. You should update and save the instance, rather than trying to save the form again.
instance = createform.save(commit=False)
instance.user = request.user
instance.save()