Django DetailView with form for comments - django

I have this url mapping:
url(r'^article/(?P<slug>[-\w]+)/$', views.ArticleView.as_view(), name='article-detail'),
and I have this view:
class ArticleView(DetailView):
model = Article
template_name = 'template/article.html'
context_object_name = 'article'
def get_context_data(self, **kwargs):
context = super(ArticleView, self).get_context_data(**kwargs)
context['comments'] = self.object.comment_set.filter(approved=True)
return context
I've already displayed all the approved comments (as you see), but I don't know how to create a comment form inside that ArticleView.
I have this ModelForm:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = '__all__'
and... Comment model:
class Comment(models.Model):
author = models.CharField(max_length=100)
article = models.ForeignKey(Article, on_delete=models.CASCADE)
email = models.EmailField()
message = models.TextField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
The problem with CommentForm is that I don't know how to 'hide' article and approved fields and how to fill article field with the article got in the ArticleView.
I've tried to combine FormMixin with DetailView but.. when I submit the
comment form, console displays: Method not Allowed (POST).
How can I create a form view into ArticleView?
If you didn't get something, please ask me, I know my grammar is bad. I will try to be clear as much as possible.
Thanks in advance for answers.

Setting a temporary variable like this and you won't have to set an initial value in ArticleView
Simply as this
context['form'] = self.get_form()
and this
def form_valid(self, form):
new_comment = form.save(commit=False)
new_comment.post = self.get_object()
return super(ArticleView, self).form_valid(form)

I solved it, kind of..
class ArticleView(FormMixin, DetailView):
model = Article
template_name = 'template/article.html'
form_class = CommentForm
def get_success_url(self):
return reverse('article-detail', kwargs={'slug': self.object.slug})
def get_context_data(self, **kwargs):
context = super(ArticleView, self).get_context_data(**kwargs)
context['form'] = CommentForm(initial={
'article': self.object
})
context['comments'] = self.object.comment_set.filter(approved=True)
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 form_valid(self, form):
form.save()
return super(ArticleView, self).form_valid(form)
in forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
exclude = ('admitted',)
widgets = {
'article': forms.HiddenInput()
}
The only way I could... set a value for that article field (which is a foreign key for the article) was to set initial value in ArticleView.
If someone have a better alternative, I'm glad too see it.

I know it's been years, but maybe my answer will be useful for someone
Yierstem's answer worked for me, but I made a few changes
First, changed get_success_url in order to use parent model's get_absolute_url:
def get_success_url(self):
return self.object.get_absolute_url()
Add get_absolute_url method to parent model first:
def get_absolute_url(self):
return reverse('record_detail', args=[str(self.pk)]
Second, I add my target article and author (ForeignKey(s) of my Comment model) inside form_valid method:
def form_valid(self, form):
new_comment = form.save(commit=False)
new_comment.target = self.object
new_comment.author = self.request.user
new_comment.save()
return super(RecordDetailView, self).form_valid(form)

Related

Add custom logic in CreateView to not show the add form if a condition is met

How do I add custom logic in a class based view ?
urls.py:
path('books/add', views.AddBook.as_view(), name='add_book'),
models.py
class Book(models.Model):
title = models.CharField(max_length=255)
summary = models.TextField(max_length=2048)
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
MAX_BOOKS = 5
class AddBook(LoginRequiredMixin, generic.CreateView):
model = Book
"""
if user has reached MAX_BOOKS
books = Book.objects.filter(user=request.user)
if len(books) == MAX_BOOKS
template_name = 'books/restrict_book.html'
and send in some data like { 'error_msg': 'You cannot add further books' }
"""
form_class = BookForm
# fields = ['title']
template_name = 'books/add_book.html'
success_url = reverse_lazy('dashboard')
def form_valid(self, form):
form.instance.user = self.request.user
super(AddBook, self).form_valid(form)
return redirect('dashboard')
You can use count() method to count queryset and compare with the max books value.
def form_valid(self, form):
if Book.objects.filter(user=self.request.user).count() >= MAX_BOOKS:
return render(self.request, 'books/restrict_book.html')
form.instance.user = self.request.user
return super(AddBook, self).form_valid(form)
EDIT: If you want to render another template if Book count limit reached before submitting the form you can override get method also.
def get(self, request, *args, **kwargs):
if Book.objects.filter(user=request.user).count() >= MAX_BOOKS:
return render(request, 'books/restrict_book.html')
return render(request, self.template, {'form':self.form_class()})

update a Model field in DetailView

As a newbie in Django, I'm sure there is something obvious I'm not seeing. I have a user model with a one to one relationship to a userprofile model, where I'm storing the profile photo. I mixed DetailView and Formview because I want the user to go to his details page and update just the photo, but somehow its not working for me. I know I could do the job with UpdateView, but for didactic purposes, can anyone tell me why this is not working? I'm trying to updated the model fields in the form_valid method but this is not working, they just remain with the old values. I thought at the beginning it was the photo that could not be updated because of some errors on my side, but I've tried also updating other string fields and it doesnt work. Here the code: (the commented out fields are the places where I tried updating several model fields using get_object_or_404 and other functions)
class UserDetail(FormMixin, DetailView):
template_name = "users/user_detail.html"
model = User
form_class = forms.UserPhotoForm
def get_success_url(self):
return reverse('users:user_detail', args=[str(self.get_object().pk)])
def get_context_data(self, **kwargs):
user = self.get_object()
form = forms.UserPhotoForm(instance=user)
context = super().get_context_data(**kwargs)
context['user_rating'] = CotizacionReview.objects.filter(cotizacion__user=self.get_object()).aggregate(Avg('nota'))
context['form'] = form
return context
def form_valid(self, form):
form.save()
return super(UserDetail, self).form_valid(form)
def post(self, request, *args, **kwargs):
a = get_object_or_404(User, pk=self.get_object().id).userprofile
form = forms.UserPhotoForm(request.FILES['avatar'], instance=a)
# get_object_or_404(User, pk=self.get_object().id).apellido = '1234'
if form.is_valid():
# print(get_object_or_404(User, pk=self.get_object().id).userprofile.avatar)
# I tried updating several model fields here, but didnt work
# print(request.FILES['avatar'])
return self.form_valid(form)
else:
return self.form_invalid(form)
Here the model:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='profile_pics', default='profile_pics/default-user-icon-4.jpg', blank=True)
telefono = models.CharField(max_length=12, blank=True)
nombre = models.CharField(max_length=64, blank=True)
apellido = models.CharField(max_length=64, blank=True)
link = models.CharField(max_length=256, blank=True)
educacion = models.CharField(max_length=256, blank=True)
experiencia = models.TextField(max_length=512, blank=True)
birthdate = models.DateField(blank=True, null=True)
#receiver(post_save, sender=User)
def update_profile_signal(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
So I achieved it by using commit=False in my form_valid method:
class UserDetail(FormMixin, DetailView):
template_name = "users/user_detail.html"
model = User
form_class = forms.UserPhotoForm
def get_success_url(self):
return reverse('users:user_detail', args=[str(self.get_object().pk)])
def get_context_data(self, **kwargs):
user = self.get_object()
form = forms.UserPhotoForm(instance=user)
context = super().get_context_data(**kwargs)
context['user_rating'] = CotizacionReview.objects.filter(cotizacion__user=self.get_object()).aggregate(Avg('nota'))
context['form'] = form
return context
def form_valid(self, form):
user_instance = form.save(commit=False)
user_instance.avatar = form.cleaned_data['avatar']
user_instance.id = self.get_object().userprofile.id
user_instance.save(update_fields=['avatar'])
return super(UserDetail, self).form_valid(form)
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
But now I have another problem. Every time I update the photo, a new photo is saved to the database. Is there a way of doing this and deleting the old photo? or replacing it?

Django - DetailView with FormMixin and initial

I have DetaiView for my post and I want to use a form in this view so I decided to use DetailView with FormMixin. I need to set some initial to this form and I don't know how to do it. Here is my code:
models.py
class Comment(models.Model):
post = models.ForeignKey(Post, related_name="comments", on_delete=models.CASCADE)
name = models.CharField("Nick", max_length=80)
email = models.EmailField()
body = models.TextField("Body")
created = models.DateTimeField("created", auto_now_add=True)
updated = models.DateTimeField("Updated", auto_now=True)
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = (
"name",
"email",
"body"
)
views.py
class PostDetailView(FormMixin, DetailView):
model = Post
form_class = CommentForm
template_name = "newspaper/post-detail.html"
def get_success_url(self):
return reverse("post-detail", kwargs={"slug": self.object.slug})
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context["form"] = self.get_form()
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 form_valid(self, form):
return super().form_valid(form)
So I want to post in CommentForm to post of this DetailView. I hope you understand :D.
Thanks in advance for the help!
With FormMixin you can specify form's initial using initial attribute:
class PostDetailView(FormMixin, DetailView):
model = Post
form_class = CommentForm
template_name = "newspaper/post-detail.html"
initial={'name': 'John'}
Or get_initial method:
def get_initial(self):
return {"post": self.get_object() }

Django inline formset validation

I have the following models:
class CaseForm(ModelForm):
class Meta:
model = Case
fields = '__all__'
class ClientForm(ModelForm):
class Meta:
model = Client
fields = '_all__'
CaseClientFormset = inlineformset_factory(Case, Client, form=ClientForm,
extra=0, max_num=2, min_num=1,
validate_max=True,
validate_min=True)
When I fill in the top part of the form (caseform) it saves correctly. When I fill in the caseform and a clientform it saves correctly.
If I fill in the caseform but partially fill in the clientform no validation appears to take place, and a case is saved and the client information goes missing and is never saved.
class CaseCreateView(LoginRequiredMixin, AdviserExistenceMixin,
CreateView):
model = Case
form_class = CaseForm
def form_valid(self, form):
context = self.get_context_data()
clients = context['clients']
self.object = form.save()
if clients.is_valid():
clients.instance = self.object
clients.save()
return super(CaseCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['clients'] = CaseClientFormset(self.request.POST)
else:
context['clients'] = CaseClientFormset()
context['navbar'] = str(self.model.__name__).lower()
return context
The other issue I have is that despite specifying min_num=1 and validate_min=True, I appear to be able to save a case without a clientform being filled in.
Any help would be appreciated.
Fixed replacing def form_valid with:
def form_valid(self, form):
context = self.get_context_data()
clients = context['clients']
if clients.is_valid():
self.object = form.save()
clients.instance = self.object
clients.save()
return super(CaseUpdateView, self).form_valid(form)
else:
return super(CaseUpdateView, self).form_invalid(form)

django class-based view - UpdateView - How to access the request user while processing a form?

In a class-base UpdateView in Django, I exclude the user field as it is internal to the system and I won't ask for it. Now what is the proper Django way of passing the user into the form.
(How I do it now, is I pass the user into the init of the form and then override the form's save() method. But I bet that there is a proper way of doing this. Something like a hidden field or things of that nature.
# models.py
class Entry(models.Model):
user = models.ForeignKey(
User,
related_name="%(class)s",
null=False
)
name = models.CharField(
blank=False,
max_length=58,
)
is_active = models.BooleanField(default=False)
class Meta:
ordering = ['name',]
def __unicode__(self):
return u'%s' % self.name
# forms.py
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
exclude = ('user',)
# views.py
class UpdateEntry(UpdateView):
model = Entry
form_class = EntryForm
template_name = "entry/entry_update.html"
success_url = reverse_lazy('entry_update')
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(UpdateEntry, self).dispatch(*args, **kwargs)
# urls.py
url(r'^entry/edit/(?P<pk>\d+)/$',
UpdateEntry.as_view(),
name='entry_update'
),
Hacking around like passing a hidden field doesn't make sense as this truly has nothing to do with the client - this classic "associate with logged in user" problem should definitely be handled on the server side.
I'd put this behavior in the form_valid method.
class MyUpdateView(UpdateView):
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
super(MyUpdateView, self).save(form)
# the default implementation of form_valid is...
# def form_valid(self, form):
# self.object = form.save()
# return HttpResponseRedirect(self.get_success_url())
Must return an HttpResponse object. The code below works:
class MyUpdateView(UpdateView):
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
return super(MyUpdateView, self).form_valid(form)
We can also do like
class MyUpdateView(UpdateView):
form_class = SomeModelForm
def form_valid(self, form):
form.instance.user = self.request.user
return super(MyUpdateView, self).form_valid(form)