Problem with mixing Detail View and Form Mixin django - python

I am trying to create a comment system for the blog portion of my app with Django. I have attempted to mix my detail view with the form mixin and I'm struggling a bit. When the form is submitted, it doesn't save and no error is present. If any of you can help I would greatly appreciate it.
Here is my View
class DetailPostView(FormMixin, DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "posts"
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"] = CommentForm
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self):
return reverse("post-detail", kwargs={"pk": self.object.pk})
The model
class Comment(models.Model):
comment = models.ForeignKey(Post, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
content = models.TextField()
author = models.CharField(max_length=50)
created_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_on"]
def __str__(self):
return self.title

The reason that this happens is because you construct a new form that you pass to the context data, as a result, it will not render any errors, since you construct a form without validating the request data and render that form, you thus do not display the form that rejected the data in the first place.
But you do not need to do that. Django's FormMixin [Django-doc] already takes care of that. You thus should not override the .get_context_data(…) method [Django-doc].
Another problem is that you did not save your form, you can override a the form_valid method, or you can inherit from ModelFormMixin [Django-doc].
Finally you better first create the form, and then assign self.object, otherwise it will pass this as an instance to the form:
from django.views.generic.edit import ModelFormMixin
class DetailPostView(ModelFormMixin, DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'posts'
form_class = CommentForm
# no get_context_data override
def post(self, request, *args, **kwargs):
# first construct the form to avoid using it as instance
form = self.get_form()
self.object = self.get_object()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.object.pk})

Related

how can i post form object, correctly?

i am practicing CBV , so i thought to check if i can override methodes, well one of biggest problems is that idk how to use data(like data just submitted ), i wrote this code for a DetailView so i could see post and comments under it:
class ArtDetailView(FormView, DetailView):
model = Art
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super(ArtDetailView, self).get_context_data(**kwargs)
context['time'] = timezone.now()
context['form'] = self.get_form()
return context
def form_valid(self, form):
form.instance.writer = self.request.user
form.instance.text = self.post
#form.instance.art = Art.objects.get(id=self.pk)
form.save()
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse('pages:art_detail', args=(self.kwargs['pk'],))
forms.py:
from django import forms
from .models import Art, Comment
class CommentForm(forms.ModelForm):
class Meta():
model = Comment
fields = ['text','art']
but when i post something it is in this shape:screen_shot(2nd comment)
,i think problem is withform.instance.text = self.post but i don't know how to fix it
can you please also explain a little because all i want is to learn.
and i tried to also add art as autofill(i added as comment) but wasn't successful, can you pls check it it too.
You can overide post method to save form as well. Since you only adding form which is always a text and no possible errors in forms unless blank. You code snippet will look like this
# In forms.py
def CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('text', )
# In views.py
class ArtDetailView(FormView, DetailView):
# same as previous but remove form_valid method
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
form_instance = form.save(commit=False)
# this will give form instance and then add related field and save again (commit=True by default)
form_instance.writer = request.user
# since it is art detail view, self.get_object() will give art object
form_instance.art = self.get_object()
form_instance.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super().form_invalid(from)
If you go to FormView declaration you will find like this

What causes "no attribute 'object_list' when I purposely csuse HTML error

I am writing a django ListView with FormMixin, but it can't handle form errors. The model limits input to 140 characters, when I inspect and change limit to 200 and submit I get
'PostListView' object has no attribute 'object_list'"
Here's the code
class PostListView(FormMixin, generic.ListView):
model = Post
form_class = PostForm
paginate_by = 10
template_name = 'index.html'
def get_success_url(self):
return reverse('index')
def post(self, request, *args, **kwargs):
form = PostForm(request.POST)
if form.is_valid():
form.save()
return super().form_valid(form)
else:
return self.form_invalid(form)
With everything working normally, it saves the data and displays the list. On error, no error, it fails.
EDIT
As #crimsonpython24 has said, ListView is for displaying data. I opted to use a basic view
def index(request):
'''deal with post method first'''
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse('index'))
else:
form = PostForm
posts = Post.objects.all()
return render(request, 'index.html', {'form':form, 'posts':posts})
This allows for invalid form data to be returned for correction, and also allows viewing of posts
The point is that ListView is only supposed to view objects. If you have a form in your view, try to go for one of the edit views, which lets you create, update, and delete (now I'm assuming that you also handle a form in this view).
I can't exactly describe how ListView causes your problem other than it does not fit your purpose and there are better alternatives.
EDIT
Now you're concatenating a FormView and ListView. However, I will still recommend going for a FormView as a ListView doesn't have a form_class attribute. It's easy, though. Let's say you have this FormView class:
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm # Now you can simply do this to your form
success_url = '/thanks/'
def form_valid(self, form):
form.send_email()
return super().form_valid(form)
and then simply pass in context data to make it behave partially like a ListView:
class ContactView(FormView):
# whatever you have here already
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
return context
So now your new view will render both your form and context data. Seeing as both these views don't require a primary key to operate, I don't see any problem implmenting this.

Django: DeleteView pass argument from ForeignKey's Model to get_success_url

After the user deleted a PieceInstance I would like to redirect him to the Piece to which the PieceInstance belonged to. I use the default DeleteView and would like to pass the Piece id (Piece via PieceInstance referenced by ForeignKey) to the get_success_url method. self.piece.pk seems not to get the id of the corresponding Piece.
models.py
class PieceInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular Piece across whole system')
piece = models.ForeignKey('Piece', on_delete=models.SET_NULL, null=True)
...
class Piece(models.Model):
title = models.CharField(max_length=200)
...
views.py
class PieceInstanceDelete(LoginRequiredMixin, DeleteView):
model = PieceInstance
def get_success_url(self):
return reverse_lazy('piece-detail', kwargs={'pk': self.piece.pk})
urls.py
path('piece/<int:pk>', views.PieceDetailView.as_view(), name='piece-detail')
Based on the source code, the object is stored in self.object before it gets deleted. So you can redirect to the primary key of the related piece with:
class PieceInstanceDelete(LoginRequiredMixin, DeleteView):
model = PieceInstance
def get_success_url(self):
return reverse_lazy('piece-detail', kwargs={'pk': self.object.piece_id})
The problem however is that this is undocumented, and therefore it is possible that the Django view is later rewritten without such behavior.
In case this behavior no longer happens, it might be worth to patch the delete function:
# probably not necessary, but there is no "contract" that the above will
# always work
class PieceInstanceDelete(LoginRequiredMixin, DeleteView):
model = PieceInstance
def delete(self, *args, **kwargs):
self.object = self.get_object()
super().delete(*args, **kwargs)
def get_success_url(self):
return reverse_lazy('piece-detail', kwargs={'pk': self.object.piece_id})
got it using this answer:
DeleteView with a dynamic success_url dependent on id
here the DeleteView class:
class PieceInstanceDelete(LoginRequiredMixin, DeleteView):
model = PieceInstance
def get_success_url(self):
piece = self.object.piece
return reverse_lazy('piece-detail', kwargs={'pk': piece.pk})

Django CBV Detailview

Hello Everybody excuse my english....
I am facing a problem with django.
I need to restrict object so only their owners can print it.
Model.py
class Post(models.Model):
title = models.CharField(max_length=50, blank=False)
prenom = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, null=False)
View.py
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
url.py
url(r'detail-post/(?P<pk>[-\d]+)$', views.detailpost.as_view(), name='detailpost'),
This works properly but the problem is that every users can access to the post of another user (http://localhost:8000/detail-post/1). So my question is how can i do some stuff befor rendering the page and see if the post belongs to the current user if yes we print it else we redirect the user to another page.
You can use the LoginRequiredMixin (new in Django 1.9) to make sure that only logged in users can access the view.
Then override the get_queryset method, and filter the queryset so that it only includes posts by the logged-in user.
from django.contrib.auth.mixins import LoginRequiredMixin
class DetailPost(LoginRequiredMixin, DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get_queryset(self):
queryset = super(DetailPost, self).get_queryset()
return queryset.filter(owner=self.request.user)
If the user views a post that does not belong to them, they will see a 404 page. If you must redirect the user instead of showing a 404, then you'll have to take a different approach.
Note that I have renamed your class DetailPost (CamelCase is recommended for classes in Django. You'll have to update your urls.py as well.
You can override get() or post() method in your view class.
from django.shortcuts import redirect
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get(self, request, *args, **kwargs):
self.post = Post.objects.get(pk=self.kwargs['pk'])
if self.post.user != request.user or not request.user.is_superuser:
return redirect('login')
else:
return super(detailpost, self).get(request, *args, **kwargs)
You should override 'get()' method in your 'detailpost' class, so that it would be something like below:
def get(self, request, *args, **kwargs):
queryset = self.model._default_manager.filter(user=request.user)
self.object = self.get_object(queryset)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
It seems like it is the only way to pass User from Request instance to filter queryset.
I did not find that DetailView uses self.request

Django CreateView is not saving object

I'm practicing django Class-Based-View with a basic blog application.
For some reason, however, the CreateView for my Post model is not saving the post inside the database.
models.py
class Post(models.Model):
user = models.ForeignKey(User)
post_title = models.CharField(max_length=200)
post_content = models.CharField(max_length=500)
post_date = models.DateTimeField('date posted')
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
exclude = ('user', 'post_date')
views.py
class PostCreate(CreateView):
template_name = 'app_blog/post_save_form.html'
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_date = datetime.now()
return super(PostCreate, self).form_valid(form)
It displays content without generating any error, but when I check the admin page,
the post created by the CreateView is not saved in the database..
Any idea..??
Thanks
One tip: don't use exclude when defining forms, use fields, is more secure and the recommended way to do it.
The redirect is defined by get_success_url method. If you have in your model the method get_absolute_url CreateView will redirect to that URL, otherwise you can always override get_success_url in your view.
Using get_absolute_url:
class Post(models.Model):
user = models.ForeignKey(User)
post_title = models.CharField(max_length=200)
post_content = models.CharField(max_length=500)
post_date = models.DateTimeField('date posted')
#permalink
def get_absolute_url(self):
return ('myurlname', (), {'myparam': something_useful})
Using get_success_url:
class PostCreate(CreateView):
template_name = 'app_blog/post_save_form.html'
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_date = datetime.now()
form.save()
return super(PostCreate, self).form_valid(form)
def get_success_url(self):
return reverse('myurlname', args=(somethinguseful,))
I think you will find this page very useful when working with CBVs:
http://ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/CreateView/
the problem is that you are excluding fields that are mandatory, so it won't pass through your form validation.
You should pass this fields hidden with some default value, let the use fill them, set them to null=True or populate them before you access form_valid
I came across this question today after many years but those answer seems not correctly.
The main issue here is the form.instance is None for CreateView. So my approach is below as suggestion form django docs:
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.post_date = datetime.now()
instance.save()
return redirect(self.get_success_url())
I think this is a simple case of not calling form.save(). When the form is validated, all of the checks are done, but it doesn't actually save the object in the database. To do that, you explicitly need to tell it to, via the save() method.
So you want:
class PostCreate(CreateView):
template_name = 'app_blog/post_save_form.html'
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_date = datetime.now()
form.save()
return super(PostCreate, self).form_valid(form)

Categories