DJANGO: NOT NULL constraint failed: courses_comment.lesson_id - django

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()

Related

2 Forms on same model not saving as same user - Django

I'm creating a questionnaire / survey, and have two forms (Model Form) built on the same model. These forms are called on separate views, but when saved they appear as separate users in the database. I'm not sure how to get them so save as the same user, I am already using the ' post = form.save(commit=False), post.user = request.user, post.save()' method to save the forms.
EDIT: Added in an attempt to save to the same instance
Model:
class QuizTakers(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
industry_choices = (
(1, 'Service'),
(2, 'Hospitality'),
(3, 'Wholesale/Retail'),
(4, 'Manufacturing'),
(5, 'Agriculture')
)
industry = MultiSelectField(choices=industry_choices, max_length=1, max_choices=1)
company_name = models.CharField( max_length=100)
email = models.EmailField(blank=True)
score = models.FloatField(default=0)
completed = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.company_name
Forms:
# Form for getting company name
class QuizTakerForm(forms.ModelForm):
class Meta:
model = QuizTakers
fields = ['company_name']
# Form for getting company industry
class QTIndustryForm(forms.ModelForm):
class Meta:
model = QuizTakers
fields = ['industry']
Views:
# view for getting company name
def start(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = QuizTakerForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
request.session['company_name'] = form.cleaned_data['company_name']
post = form.save(commit=False)
post.user = request.user
post.save()
# redirect to a new URL:
return HttpResponseRedirect('industry/')
# if a GET (or any other method) we'll create a blank form
else:
form = QuizTakerForm()
return render(request, 'ImpactCheck/start.html', {'form': form})
# view for getting industry
class IndustryView(FormView):
template_name = 'ImpactCheck/industry.html'
form_class = QTIndustryForm
success_url = '1/'
def get(self, request):
company_name = request.session['company_name']
this_user=QuizTakers.objects.filter(company_name=company_name).order_by('-timestamp').first()
form=self.form_class(instance=this_user)
company_name = request.session['company_name']
return render(request, 'ImpactCheck/industry.html', {'form': form, 'company_name': company_name})
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
post = form.save(commit=False)
post.user = self.request.user
post.save()
return HttpResponseRedirect('/1')
Firstly, in your def start(request) function, you should consider adding the ID to request.session instead of the company name. Something along the lines of
def start(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = QuizTakerForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
form.instance.user=request.user
form.save()
request.session['obj_id'] = post.id
# redirect to a new URL:
return HttpResponseRedirect('industry/')
Now you can use that id to get both the name of your company, as well as the object.
In your IndustryView(FormView), if you're having trouble with the form instances, it's better to use UpdateView instead of the FormView (Be sure to import UpdateView first)
class IndustryView(UpdateView):
template_name = 'ImpactCheck/industry.html'
model = QuizTakers
fields = ['industry']
success_url = '/1'
def get_object(self):
return QuizTakers.objects.get(pk=self.request.session.get('obj_id'))
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['company_name'] = QuizTakers.objects.get(pk=self.request.session.get('obj_id'))
return ctx
We use the get_context_data method since you need the company_name in your template. The get_object method in this view, tells django which object is to be updated. By default, it grabs the pk from the url (as a url parameter). But since we store our id in the session, we need to explicitly define this function.
Also, since we switched to UpdateView, you no longer need the QTIndustryForm either.

AttributeError After processing the view

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.

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

"Select a valid choice. <choice> is not one of the available choices" error when submitting ManyToMany ModelForm

I want to limit the choices of a ManyToManyField to those matching a ForeignKey. The form displays properly, but upon saving results in an error Select a valid choice. <choice> is not one of the available choices.
Before I was trying to limit the queryset by passing a parameter in the view to the form, and then using that parameter to filter the queryset.
Models:
class VenueEventTimeslot(models.Model):
venue = models.ForeignKey(Venue)
name = models.CharField(max_length=255)
class VenueEvent(models.Model):
venue = models.ForeignKey(Venue)
event_timeslots = models.ManyToManyField(VenueEventTimeslot)
class VenueEventForm(ModelForm):
event_timeslots = ModelMultipleChoiceField(queryset=None, widget=CheckboxSelectMultiple())
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj',None)
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue_obj)
class Meta:
model = VenueEvent
fields = ['event_timeslots']
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm(venue_obj = venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)
However, if I pass the queryset from the view, it works perfectly.
Models:
class VenueEventTimeslot(models.Model):
# same as above
class VenueEvent(models.Model):
# same as above
class VenueEventForm(ModelForm):
class Meta:
model = VenueEvent
fields = ['date','client_name','event_timeslots']
widgets = {
'date': SelectDateWidget(),
'event_timeslots': CheckboxSelectMultiple(),
}
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm()
form.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue)
context = {'venue':venue, 'form':form}
return render(request, ..., context)
Would anyone be able to shed some light on this?
I just solved a problem similar to this yesterday which is right here, How To Exclude A Value In A ModelMultipleChoiceField?, but I think the issue with your init function is the way it is formatted. Instead of venue=venue_obj, you need to change it to pk=venue_obj because it appear you are getting the pk of venue in the view instead of the venue attribute of VenueEvent , and I reformatted your form a bit to make it look cleaner.
forms.py
class VenueEventForm(ModelForm):
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj')
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(), queryset=VenueEventTimeslot.objects.filter(pk=venue_obj))
class Meta:
model = VenueEvent
fields = ['event_timeslots']
views.py
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST, venue_obj=venue)
if form.is_valid():
# form stuff
else:
print VenueEventForm.errors
else:
form = VenueEventForm(venue_obj=venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)

Why does my Django form keep saying "this field is required"

Does anyone know why my form (filepicker) is constantly returning "this field is required" when it worked in a simpler version?
My view is
def add_attempt(request, m_id, a_id):
template = loader.get_template('add_attempt.html')
if request.method == 'POST':
import pprint
pprint.pprint(request.POST)
pprint.pprint(request.FILES)
form = UploadAttemptForm(data=request.POST, files=request.FILES)
if form.is_valid():
form.instance.pub_date = datetime.datetime.now()
form.instance.user_id = request.user
form.instance.assignment = m.Assignment.objects.get(id=a_id)
form.save()
return HttpResponseRedirect(reverse('assignment', args=(m_id, a_id)))
else:
print form.errors
else:
form = UploadAttemptForm()
context = RequestContext(request,
{
'form':form,
})
return HttpResponse(template.render(context))
My Model is
class Attempt(models.Model):
user_id = models.ForeignKey(User)
pdf_filename = models.FileField(storage=settings.S3_STORAGE, upload_to='pdfs')
pub_date = models.DateTimeField('date uploaded')
assignment = models.ForeignKey(Assignment)
And my form is
class UploadAttemptForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UploadAttemptForm, self).__init__(*args, **kwargs)
class Meta():
model = Attempt
fields = ['pdf_filename',]
The error prints out as
`<QueryDict: {u'submit': [u'Upload Attempt'], u'pdf_filename': [u'something.pdf']}>`
<MultiValueDict: {}>
<ul class="errorlist"><li>pdf_filename<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
Adding my comment as a proper answer:
Please try adding enctype= multipart/form-data to your <form> element in your template file.
If you don't have this element your request.FILES will always be empty.
Copying from https://docs.djangoproject.com/en/1.7/topics/http/file-uploads/#basic-file-uploads:
Note that request.FILES will only contain data if the request method was POST and the <form> that posted the request has the attribute enctype="multipart/form-data". Otherwise, request.FILES will be empty.
If the field is required in your models.py (i.e. you have not stated blank=True or null=True), and you are using a ModelForm, then that will be a required field in your ModelForm