I have practically finished the microblogging application and I want to change to class base views now. I read about generic views, but it looks like each of them has specific properties. In my index view I display two models and two forms, so I created IndexView (View) and put the get and post functions there and it works, but it doesn't look like there are any benefits of class views, so I'm probably doing something wrong. Currently, several views repeat a lot of code, and I thought that class views would solve this problem. I would like an example of how such a view should be written as class view with reusable get.
class IndexView(View):
def get(self, request, *args, **kwargs):
post_form = AddPostForm()
comment_form = AddCommentForm()
if request.user.is_authenticated:
logged_user = CustomUser.objects.get(id=request.user.id)
blocked_users = logged_user.blocked.all()
posts = Post.objects.exclude(author__in=blocked_users)
print(blocked_users)
posts = posts.order_by('-pub_date')
else:
posts = Post.objects.all()
posts = posts.order_by('-pub_date')
comments = Comment.objects.all()
comments = comments.order_by("-pub_date")
return render(request, 'mikroblog/index.html', {'posts': posts, 'comments': comments,
'post_form': post_form, 'comment_form': comment_form})
def post(self, request, *args, **kwargs):
if request.method == 'POST':
post_form = AddPostForm(request.POST)
comment_form = AddCommentForm(request.POST)
if post_form.is_valid():
new_post = post_form.save(commit=False)
new_post.author = request.user
new_post.tags = ''
content = ''
for word in new_post.content_post.split():
if '#' in word:
new_post.tags += f"{word} "
content += f"{word} "
else:
content += f"{word} "
new_post.content_post = content
new_post.save()
for word in content.split():
if '#' in word:
print(word)
user_to_notificate = CustomUser.objects.get(username=word[1:])
new_notification = TalkAbout()
new_notification.where = new_post
new_notification._from = new_post.author
new_notification.to = user_to_notificate
new_notification.sended = False
print(new_notification)
new_notification.save()
post_form = AddPostForm()
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.author = request.user
new_comment.save()
comment_form = AddCommentForm()
return HttpResponseRedirect(reverse('index'))
Use class-based views and override the 'post' and 'get' methods if you need to.
Follow this link:
Django CBVs documentation
Related
I found posts with similar problem, but the solutions dont seem to work with me. Or maybe I am missing something.
I created a form in Django, but as a default it seems that every mandatory fields have this "this field is required" text above.
Ideally, I would like to have this only when someone tries to submit a form and a field is missing.
form.py
class ElderFlowerChampFormAdmin(ModelForm):
fieldsets=[
("Look",{'fields':[
('look_colour','look_clarity','look_effervescence')]}),
}
widgets = {
'user': forms.Select(attrs={'class':'form-control','required':'True'}),
'look_colour':forms.Select(attrs={'class':'form-control','required':'True'}),
}
view.py
def elderflowerchamp(request, product_id):
global ChampageRating
product = Product.objects.get(pk=product_id)
url = request.META.get('HTTP_REFERER')
submitted = False
try:
if request.method == "POST":
reviews = ChampageRating.objects.get(pk=product_id)
if request.user.is_superuser:
form = ElderFlowerChampFormAdmin(request.POST, instance=reviews)
if form.is_valid():
form.save()
return redirect('home')
else:
form = ElderFlowerChampForm(request.POST, instance=reviews)
if form.is_valid():
ChampageRating = form.save(commit=False)
ChampageRating.user = request.user
ChampageRating.save()
return redirect('home')
else:
#This part goes to the page, but doesnt submit
reviews = ChampageRating.objects.get(pk=product_id)
if request.user.is_superuser:
form = ElderFlowerChampFormAdmin
else:
form = ElderFlowerChampForm
if 'submitted' in request.GET:
submitted = True
except:
reviews = None
if request.user.is_superuser:
form = ElderFlowerChampFormAdmin(request.POST)
if form.is_valid():
data = ChampageRating()
data.rating = form.cleaned_data['rating']
data.look_colour = form.cleaned_data['look_colour']
data.ip = request.META.get('REMOTE_ADDR')
data.product_id = product_id
data.user_id = request.user.id
data.save()
messages.success(request, 'Thank you! Your review has been submitted.')
return redirect('home')
else:
form = ElderFlowerChampForm(request.POST)
if form.is_valid():
data = ChampageRating()
data.rating = form.cleaned_data['rating']
data.look_colour = form.cleaned_data['look_colour']
data.ip = request.META.get('REMOTE_ADDR')
data.product_id = product_id
data.user_id = request.user.id
data.save()
messages.success(request, 'Thank you! Your review has been submitted.')
return redirect('home')
return render(request,"main/ElderFlowerReview.html", {'form':form, 'submitted':submitted,'reviews':reviews, 'product':product})
models.py
LOOKCOLOUR=(
(1,'Straw'),
(2,'Yellow'),
(3,'Gold'),
(4,'Amber'),
)
class ChampageRating(models.Model):
user = models.ForeignKey(User,blank=True, on_delete=models.CASCADE)
product=models.ForeignKey(Product,related_name="champcomments", on_delete=models.CASCADE)
look_colour=models.IntegerField(choices=LOOKCOLOUR, default=0)
def __str__(self):
return '%s - %s - %s'%(self.user, self.product, self.date_added)
Any ideas
something wrong on case request.GET here:
if request.user.is_superuser:
form = ElderFlowerChampFormAdmin
else:
form = ElderFlowerChampForm
You send the FormClass into the template context, and not the form instance.
Also see eror on the except case: If i have a GET, i should not render BoundedForm, here should be form.bounded = false (form is not bounded with any data). But you are always made the BoundedForm, with the request.POST data after error here:
reviews = ChampageRating.objects.get(pk=product_id)
My opinion is - you should try to read about Django-GCBV, it is better way to avoid complex errors, like in your question.
https://docs.djangoproject.com/en/4.0/topics/class-based-views/
I'm trying make a comment section for my Q&A project.I made the model for comment , the form part in question_detail.html an , also the QuestionCommentForm() in form.py .
model.py
class QuestionComment(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
question =
models.ForeignKey(Question,on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add= True)
body = models.CharField(max_length=200, null= True ,
blank = True, default ='')
def __str__(self):
return str(self.body)
forms.py
{%if c_form%}
<form method="POST" action= "{% usl 'blog:question-commet' question.id >{% csrf_token %}
{{c_form.media}}
{{ c_form.as_p}}
<button type = "submit" , name = "question_id", value = "{{question.pk}}", class ="btn btn-secondary btn-sm">submit comment</button>
views.py
#api_view(['POST','GET'])
def question_comment(request, *args, **kwargs):
form = QuestionCommentForm()
print('finction comment started'*20)
if request.method == 'POST':
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
new_comment = c_form.save(commit=False)
new_comment.refresh_from_db()
c_form.instance.user = request.user
question = Question.objects.get(id = request.POST.get('question_id')
new_comment.question = question
new_comment.bldy =c_form.cleaned_data.get('body')
new_comment.save()
context['c_form'] = c_form
return render(request, 'blog/question_detail.html',context)
class QuestionDetail(DetailView):
template_name = 'blog/question_detail.html'
model = Question
context_object_name = 'question'
count_hit = True
def get_queryset(self):
return Question.objects.filter(id = self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
c_form = QuestionCommentForm()
context = super(QuestionDetail,self).get_context_data(**kwargs)
self.obj= get_object_or_404(Question, id = self.kwargs['pk'])
self.object.save()
self.object.refresh_from_db()
answers = Answer.objects.filter (question_id = self.obj.id).order_by('-created_date')
liked =self.obj.like.filter(id =self.request.user.id).exists()
print('liked in class question not checked still' *10)
comments= QuestionComment.objects.filter(question = self.kwargs['pk'])
context['comments']= comments
context["answers"]=answers
context["liked "] = liked
context['c_form'] = c_form
return context
def post(request, *args, **kwargs):
print('post started'*100)
c_form = QuestionCommentForm()
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
new_comment = c_form.save(commit=False)
new_comment.refresh_from_db()
new_comment.user = request.user
new_comment.question = c_form.cleaned_data.get('question_id')
new_comment.bldy =c_form.cleaned_data.get('body')
new_comment.save()
context['c_form'] = c_form
else:
c_form= QuestionCommentForm()
return render(request, 'blog/question_detail.html',context)
url.py
...
path('question-comment/<int:pk>/', question_comment, name = 'question-comment'),
]
in my view, first I tried to use a another function to handle the comment, got no result and made a def post()
in the class QuestionDetial() , and still the form shows up but when I type something and hit the button , it refresh the page and nothing saves . I have already saved a comment using admin and it appears in the question-detail page. used print to find the bug, but it seems the post() in class and the question_comment() not being recall. searched a lot but no answer. (BTW I get no error except the NoReverseMatch that I fixed)
You never save the model object (not by the form, nor by the view). Furthermore the name of the method is 'POST', not 'Post':
#api_view(['POST','GET'])
def question_comment(request, *args, **kwargs):
form = QuestionCommentForm()
print('finction comment started'*20)
if request.method == 'POST':
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
c_form.instance.user = request.user
c_form.save()
context = {'c_form': c_form }
return render(request, 'blog/question_detail.html',context)
ok , the problem was that I had two form-tags in question_detail template and buy adding comment form in the end of the template had 3 , but the problem was when I pressed the submit comment button the terminal showed this message
"POST /create-like/blog/answer/18/ HTTP/1.1" 302 0
which was the form I had made for like function .(while my url was question-comment)
I accidentally forgot to close the like-form tag in template. so all I needed was putting a
</form>
after the form block .
thank's Willem Van Onsem. it solved my post method error.
and these were very helpful
Proper way to handle multiple forms on one page in Django
How can I build multiple submit buttons django form?
I'm pretty new to Django, I've been stuck on this view for a little while. My goal with this form is to be able to create a small note on a "Property" about maintenance or other information. The note would log the time, date, note and the user that recorded the note. Any help would be appreciated.
View:
#login_required(login_url="login")
def createNote(request, pk):
PropertyNoteFormSet = inlineformset_factory(
Property, PropertyNote, fields=('note', 'user',))
property_note = Property.objects.get(id=pk)
form = PropertyNoteFormSet(instance=property_note)
# form = OrderForm(initial={'customer': customer})
if request.method == "POST":
print(request.POST)
form = PropertyNoteFormSet(
request.POST, instance=property_note)
if form.is_valid():
form.save()
return redirect("/")
context = {"form": form}
return render(request, "dashboard/create_note.html", context)
Here is the ModelForm:
class PropertyNoteForm(ModelForm):
class Meta:
model = PropertyNote
fields = ['note']
exclude = ['user']
Here is the Model:
class PropertyNote(models.Model):
airbnb_name = models.ForeignKey(Property, blank=True,
null=True,on_delete=models.CASCADE)
note = models.TextField(blank=True, null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.note
The form comes out with around 4 boxes to fill in. Currently it works, but you have to actually select the user that is posting the note, I would like this part to be handled automatically and use the current logged in user. I think I still have a whole lot of holes in my knowledge around this stuff, I just can't seem to work it out.
Thanks in advance.
Edit:
I've tried this:
def createNote(request, pk):
PropertyNoteFormSet = inlineformset_factory(
Property, PropertyNote, fields=('note',), extra=1)
property_note = Property.objects.get(id=pk)
form = PropertyNoteFormSet(
queryset=PropertyNote.objects.none(), instance=property_note)
# form = OrderForm(initial={'customer': customer})
if request.method == "POST":
print(request.POST)
form = PropertyNoteFormSet(
request.POST, instance=property_note)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
print(instance.user)
instance.save()
return redirect("/")
context = {
"form": form,
'pk': pk,
}
return render(request, "dashboard/create_note.html", context)
But I get this:
AttributeError at /create_note/75/
'list' object has no attribute 'user'
Request Method: POST
Request URL: http://127.0.0.1:8000/create_note/75/
Django Version: 3.0.4
Exception Type: AttributeError
Exception Value:
'list' object has no attribute 'user'
you can use request.user.id to get the logged user id in your view.
See Documentation in Django
#login_required(login_url="login")
def createNote(request, pk, **kwargs):
note_form = PropertyNoteForm()
if request.method == "POST":
note_form = PropertyNoteForm(request.POST)
if note_form.is_valid():
add_note = note_form.save(commit=False)
add_note.user = request.user
add_note.airbnb_name =
Property.objects.get(id=pk)
add_note.save()
return redirect('/property/' + pk + '/')
context = {
"form": note_form,
'pk': pk,
}
return render(request, "dashboard/create_note.html", context)
I solved it with the above code. Using instance was the incorrect thing to do here. I didn't need to create an instance and I didn't need the inline form. I simply needed a new form:
note_form = PropertyNoteForm()
The user input information, I need to send that information to check if it's valid:
if request.method == "POST":
note_form = PropertyNoteForm(request.POST)
if note_form.is_valid():
Then I needed to populate the form with information that was not already in the form from the user:
add_note = note_form.save(commit=False)
add_note.user = request.user
add_note.airbnb_name = Property.objects.get(id=pk)
add_note.save()
return redirect('/property/' + pk + '/')
I used this tutorial to successfully set up a dynamic inline formset using Django. The CreateView works great, but I cannot get the UpdateView to actually update the related fields. There are no errors thrown, but the items will not update. I believe I have isolated the error to the form_valid function. The code is as follows. Thank you for any assistance.
class ApplicantCreate(CreateView):
model = Applicant
success_message = 'Your application was submitted successfully.'
form_class = forms.ApplicantForm
template_name = 'careers/add_applicant.html'
success_url = reverse_lazy('careers:thanks')
def get_context_data(self, **kwargs):
data = super(ApplicantCreate, self).get_context_data(**kwargs)
positions = super(ApplicantCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['employer'] = forms.ApplicantEmployerFormSet(
self.request.POST,
prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(
self.request.POST,
prefix='education')
else:
data['employer'] = forms.ApplicantEmployerFormSet(prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(prefix='education')
return data
context['unfilled_positions'] = Position.objects.filter(filled=False)
return positions
def form_valid(self, form):
context = self.get_context_data()
employer = context['employer']
education = context['education']
with transaction.atomic():
form.instance.created_by = self.request.user
self.object = form.save()
if employer.is_valid():
employer.instance = self.object
employer.save()
if education.is_valid():
education.instance = self.object
education.save()
return super(ApplicantCreate, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('careers:thanks')
class ApplicantUpdate(SuccessMessageMixin,LoginRequiredMixin,GroupRequiredMixin,UpdateView):
group_required = [u'careers-admin',u'careers']
model = Applicant
success_message = '%(first_name)s %(last_name)s was updated successfully.'
form_class = forms.ApplicantUpdateForm
template_name = 'careers/edit_applicant.html'
def get_context_data(self, **kwargs):
data = super(ApplicantUpdate, self).get_context_data(**kwargs)
positions = super(ApplicantUpdate, self).get_context_data(**kwargs)
if self.request.POST:
data['employer'] = forms.ApplicantEmployerFormSet(
self.request.POST,
instance=self.object,
prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(
self.request.POST,
instance=self.object,
prefix='education')
else:
data['employer'] = forms.ApplicantEmployerFormSet(
instance=self.object,
prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(
instance=self.object,
prefix='education')
return data
context['unfilled_positions'] = Position.objects.filter(filled=False)
return positions
def form_valid(self, form):
context = self.get_context_data()
employer = context['employer']
education = context['education']
with transaction.atomic():
form.instance.created_by = self.request.user
self.object = form.save()
if employer.is_valid():
employer.instance = self.object
employer.save()
if education.is_valid():
education.instance = self.object
education.save()
return super(ApplicantUpdate, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('careers:applicant_detail',kwargs={'pk': self.object.pk})
This is not a good blog post, because no errors are shown if any of the inline forms aren't valid. As a user, I wouldn't expect the view to just silently ignore errors in the inline forms, saving my main instance successfully and not reporting back those errors.
Note that I don't know what the errors are, maybe it's just an issue with the management form. But in any case, formset errors should be handled before the main object is actually saved.
In general, if you need to write a view with multiple forms (including a formset), it's better to use a function-based view or a View where you write the get and post than trying to force this into a generic class-based view, which is not meant for this.
def multiple_forms_view(request, object_id):
# if this is a POST request we need to process the form data
obj = get_object_or_404(MyModel, pk=object_id)
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = MyForm(request.POST, instance=obj)
formset = MyFormSet(request.POST, instance=obj, ...)
# check whether it's valid:
if form.is_valid() and formset.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = MyForm(instance=obj)
formset = MyFormSet(instance=obj, ...)
return render(request, 'name.html', {'form': form, 'formset': formset})
That way your template can render the errors of each form, including those in the formset. As mentioned before, you could also do this in a View (so your mixins will work), just write the get and post methods and return the same as in the function-based view above.
I am creating a forum where users can edit topics. When a user changes a topics title the slug for the topic is changed. When I post the changes to the topic the slug correcting middleware triggers and redirects me to back to the same editing page instead of letting me redirect to the topic itself. It seems django processes the view first and then calls post with the old url causing my changes to be saved but me not ending up in the right place. For example if I hit post without changing anything I am redirected where I should go. How do I fix this?
# middleware.py
class TopicSlugMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if (request.resolver_match and 'topic_slug' in request.resolver_match.kwargs):
try:
topic_slug = request.resolver_match.kwargs['topic_slug']
topic_pk = int(topic_slug.split('-')[0])
topic = Topic.objects.get(pk=topic_pk)
if (topic.slug != topic_slug):
return redirect(reverse(request.resolver_match.url_name, kwargs={'topic_slug': topic.slug}))
except:
raise Http404('The topic you are looking for does not exist')
return response
# views.py
class EditTopicView(View):
template_name = 'forums/edit_topic.html'
def get(self, request, topic_slug):
topic = Topic.objects.get(slug=topic_slug)
if (request.user.profile.role.can_modify_topics or request.user == topic.started_by):
if (request.user.profile.role.can_modify_topics):
form = EditMoveTopicForm(instance=topic)
else:
form = NewTopicForm(instance=topic)
return render(request, self.template_name, {'topic': topic, 'form': form})
else:
raise PermissionDenied('You do not have permission to modify this topic')
def post(self, request, topic_slug):
topic = Topic.objects.get(slug=topic_slug)
if (request.user.profile.role.can_modify_topics or request.user == topic.started_by):
if (request.user.profile.role.can_modify_topics):
form = EditMoveTopicForm(request.POST)
else:
form = NewTopicForm(request.POST)
if (form.is_valid()):
topic.title = form.cleaned_data['title']
topic.tags = form.cleaned_data['tags']
topic.save()
return redirect('/topics/' + topic.slug)
else:
return render(request, self.template_name, {'topic': topic, 'form': form})
else:
raise PermissionDenied('You do not have permission to modify this topic')