I want to use ajax in comments and reply sections of my blog application. In function based view everything is working fine, but I want to do it class based view.
***My function based view***
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
comments = Comment.objects.filter(post=post, reply=None).order_by('-id')
if request.method == 'POST':
comment_form = CommentForm(request.POST or None)
if comment_form.is_valid():
content = request.POST.get('content')
reply_id = request.POST.get('comment_id')
comment_qs = None
if reply_id:
comment_qs = Comment.objects.get(id=reply_id)
comment = Comment.objects.create(post=post,
user=request.user,
content=content,
reply=comment_qs)
comment.save()
else:
comment_form = CommentForm()
context = {
'title': 'blog',
'post': post,
'comments': comments,
'comment_form': comment_form,
}
if request.is_ajax():
html = render_to_string('blog/comments.html', context, request=request)
return JsonResponse({'form': html})
return render(request, 'blog/post_detail.html', context)
***My class based view***
class PostDetailView(FormMixin, DetailView):
model = Post
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = get_object_or_404(Post, id=self.object.id)
comments = Comment.objects.filter(post=post, reply=None).order_by('-id')
context['title'] = 'Blog Detail'
context['comments'] = comments
context['comment_form'] = self.get_form()
return context
def post(self, request, *args, **kwargs):
#if request.user.is_authenticated():
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
content = request.POST.get('content')
reply_id = request.POST.get('comment_id')
comment_qs = None
if reply_id:
comment_qs = Comment.objects.get(id=reply_id)
comment = Comment.objects.create(post=self.object,
user=request.user,
content=content,
reply=comment_qs)
comment.save()
#return HttpResponseRedirect(self.object.get_absolute_url())
else:
return self.form_invalid(form)
if request.is_ajax():
html = render_to_string('blog/comments.html', context, request=request)
return JsonResponse({'form': html})
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.kwargs['pk'], })
In my class based view in if request.is_ajax():
html = render_to_string('blog/comments.html', context,request=request)
return JsonResponse({'form': html})
part it shows the error that context is not defined. So how to fix this problem or how to include it in function, since I have already context.
There is no problem in my function based view code it is working as expected.
It's because. You use context variable but it not defined yet.
if request.is_ajax():
html = render_to_string('blog/comments.html', context, request=request)
return JsonResponse({'form': html})
Try this this get the context:
context = self.get_context_data(object=self.object)
You can see this by tracing the source code of DetailView, you'll see that DetailView inheriting from BaseDetailView and you will found this script to get the context
Hope this helpful!
in this method:
def post(self, request, *args, **kwargs):
...
...
if request.is_ajax():
html = render_to_string('blog/comments.html', context, request=request)
return JsonResponse({'form': html})
simple, you use context, but its not defined. Either pass it as parameter or define it somewhere as a variable
Related
I use Slugify. After making a change in slug for all objects in Entry, slug still shows as old slug. If I refresh obj page, I get a page not found, unless I click "back" and then reopen the obj page, and that is when the obj page will load and new slug will update.
Any idea how to fix this?
I tried an empty migration and applied obj.save() and obj.refresh_from_db() but no luck.
Below is my model, this is what generates my slug field:
(My change was removed self.4 and replaced with self.5)
class Entry(models.Model)
1 = ...
2 = ...
3 = ...
4 = ...
5 = ...
slug = models.SlugField(null=True, unique=True, max_length=300)
def save(self, *args, **kwargs):
self.slug = slugify(f"{self.1}-{self.2}-{self.3}-{self.5}")
return super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("page", kwargs={"slug": self.slug})
View:
def post_page(request, slug):
form = PostForm()
entry = Entry.objects.get(slug=slug)
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.cleaned_data['post']
post = form.save(commit=False)
post.entry = entry
post.save()
return redirect('post_page', slug=entry.slug)
else:
msg_error = 'Please correct errors and resubmit'
return render(request, 'post_page.html', {'entry': entry, 'form':form, 'msg_error':msg_error})
return render(request, 'post_page.html', {'entry': entry, 'form':form})
Url:
path('post-page/<slug>/', views.post_page, name='post_page'),
try
path('post-page/<slug:slugname>/', views.post_page, name='post_page'),
def post_page(request, slugname):
form = PostForm()
entry = get_object_or_404(Entry, slug=slugname)
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.cleaned_data['post']
post = form.save(commit=False)
post.entry = entry
post.save()
return redirect('post_page', slug=entry.slug)
else:
msg_error = 'Please correct errors and resubmit'
return render(request, 'post_page.html', {'entry': entry, 'form':form, 'msg_error':msg_error})
return render(request, 'post_page.html', {'entry': entry, 'form':form})
You need to change URL pattern like this
path('post-page/<slug:slug>/', views.post_page, name='post_page'),
Found a solution
In shell:
for obj in Entry.objects.all():
obj.save()
I have this view:
#login_required
def newAnswer(request, id):
post = Post.objects.get(id=id)
form = AnswerForm(request.POST)
if request.method == 'POST':
if form.is_valid():
obj = form.save(commit=False)
obj.author = request.user
obj.post = post
obj.save()
form.save_m2m()
return redirect('main:post', id=post.id)
else:
return render(request, 'main/newAnswer.html', { 'form': form, 'formErrors': form.errors, 'userAvatar': getAvatar(request.user)})
else:
return render(request, 'main/newAnswer.html', {'form': form, 'post': post, 'userAvatar': getAvatar(request.user)})
When i try to post without loging in, it redirects me to "/accounts/login?next=/post/answer/new/81".
My question is how can i get the "next" param in my login view
thanks!
Everything arguments (params) you can see in url mean that request is done with GET method. Use request.GET.get('next', None).
here is my views.py
class DoctorDetailView(generic.DetailView):
model = Doctor
context_object_name = "doctor"
template_name = "adminplus/pages/doctor_profile.html"
def get_context_data(self, **kwargs):
context = super(DoctorDetailView, self).get_context_data(**kwargs)
data = {
"edit_form": forms.DoctorEditForm(instance=self.object),
"password_change_form": forms.PasswordChangeForm(self.object.user),
}
context.update(data)
return context
def post(self, request, *args, **kwargs):
if request.POST.get("change-password"):
form = forms.PasswordChangeForm(request.POST)
if form.is_valid():
print("valid form here!")
# ...
else:
print("invalid form : ", form.errors)
return render(request, self.template_name, ?? )
I've no idea how can i pass get_context_data back to my template. (or anything that works.. idk new to django:)
~Thanks in advance
You can pass contex to render directly:
def post(self, request, *args, **kwargs):
# ...
context = self.get_contex_data(**kwargs)
return render(request, self.template_name, context=context)
You can do this on the return:
return render(request, self.template_name, context=self.context )
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
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.