How can I pass the form with an actual filled profile informations (like ProfileUpdateForm(instance=x) in function views).
I did it this way and have no idea how to pass an actual profile instance to the form. Something like UpdateView, but in my DetailView class
class ProfileDetailView(FormMixin, DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'profiles/myprofile.html'
form_class = ProfileUpdateForm
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)
def form_valid(self, form):
form.save()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('profiles:profile', kwargs={'pk': self.get_object().pk})
If you want to update an instance of a model you should use UpdateView [Django docs]:
from django.urls import reverse
from django.views.generic.edit import UpdateView
class ProfileDetailView(UpdateView):
model = Profile
context_object_name = 'profile'
template_name = 'profiles/myprofile.html'
form_class = ProfileUpdateForm
def get_success_url(self):
# Use `reverse` instead of `reverse_lazy`. You will get an error otherwise!
return reverse('profiles:profile', kwargs={'pk': self.get_object().pk})
Also if there is any need to pass a keyword argument to the form (here instance, although I would suggest to stick with UpdateView) you should override get_form_kwargs:
class MyView(SomeGenericView):
...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['some_kwarg'] = 'Some value'
# Your use case implies below line
# kwargs['instance'] = self.get_object()
return kwargs
Related
I have a detailed user view that has a button for user updates. The user update form is inside a modal, for that, I am using a FormView ModelForm and a TbUser. I don't get how the form validation works but the fields are correct. When I update something for a user, I get an error, TbUser with username already exists, which means the code does not update the user but tries to add a new one. Also, I want to redirect to user-detail page after submit.
views.py
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, FormView):
form_class = UserUpdateForm
template_name = 'users/modals/user_update_modal.html'
success_message = "User updated successfully."
def get_form_kwargs(self):
kw = super().get_form_kwargs()
kw['request'] = self.request
return kw
def form_valid(self, form):
obj = form.save(commit=False)
print(obj.username)
print('valid')
TbUser.objects.filter(id=self.request.user.id).update(username=obj.username, real_name=obj.real_name,
email=obj.email, cellphone=obj.cellphone,
department=obj.department, role=obj.role)
def form_invalid(self, form):
messages.error(self.request, form.errors)
# Where to redirect here? I want to
def get_success_url(self):
return reverse('user-detail', kwargs={'pk': self.formclass})
forms.py
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
if request.user.customer:
self.fields['department'].queryset = TbDepartment.objects.filter(
customer=request.user.customer)
self.fields['role'].queryset = TbRole.objects.filter(
customer=request.user.customer)
class Meta:
model = TbUser
fields = ['username', 'real_name', 'email',
'cellphone', 'department', 'role']
urls.py
urlpatterns = [
path('users-list/', views.UsersListView.as_view(), name='users-list'),
path('user-detail/<str:pk>/',
views.UserDetailView.as_view(), name='user-detail'),
path('tb-user-update-form/<str:pk>/update/',
views.UserUpdateView.as_view(), name='tb-user-update-form'),
]
You need to override the get_object method to let it point to the object you want to update.
A FormView will construct a form *without looking for an object, you can use an UpdateView [Django-doc] to fetch the object with get_object, and then inject this in the form to update that object:
from django.views.generic import UpdateView
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
form_class = UserUpdateForm
template_name = 'users/modals/user_update_modal.html'
success_message = "User updated successfully."
def get_form_kwargs(self):
kw = super().get_form_kwargs()
kw['request'] = self.request
return kw
def get_object(self, *args, **kwargs):
return self.request.user
def form_invalid(self, form):
messages.error(self.request, form.errors)
return super().form_invalid(form)
def get_success_url(self):
return reverse('user-detail', kwargs={'pk': self.object.pk })
I have got a CreateView for emergency lighting tests done on sites. It gets the site from the url so the user does not need to enter it into the form. The CreateView Also has a emergency lighting device formset to attach multiple devices to the main test. When I hit submit on the form i get
form_valid() takes exactly 2 arguments (3 given)
models.py
class Site(models.Model):
....
class EmergencyLighting(models.Model):
site = models.ForeignKey(Site)
class EmergencyLightingDevice(models.Model):
emergency_lighting = models.ForeignKey(EmergencyLighting)
views.py
class FormsetMixin(object):
object = None
def get(self, request, *args, **kwargs):
if getattr(self, 'is_update_view', False):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset_class = self.get_formset_class()
formset = self.get_formset(formset_class)
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
if getattr(self, 'is_update_view', False):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset_class = self.get_formset_class()
formset = self.get_formset(formset_class)
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
def get_formset_class(self):
return self.formset_class
def get_formset(self, formset_class):
return formset_class(**self.get_formset_kwargs())
def get_formset_kwargs(self):
kwargs = {
'instance': self.object
}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def form_valid(self, form, formset):
self.object = form.save()
formset.instance = self.object
formset.save()
return redirect('/sites/list')
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
class EmergencyLightingCreate(FormsetMixin, CreateView):
template_name = 'emergency_lighting/emergencylighting_form.html'
model = EmergencyLighting
form_class = EmergencyLightingForm
formset_class = EmergencyLightingFormSet
def form_valid(self, form):
emergency_lighting = form.save(commit=False)
emergency_lighting.site_id = self.kwargs['site']
return super(EmergencyLightingCreate, self).form_valid(form)
forms.py
class EmergencyLightingForm(forms.ModelForm):
class Meta:
model = EmergencyLighting
exclude = ('creation', 'last_modified')
class EmergencyLightingDeviceForm(forms.ModelForm):
class Meta:
model = EmergencyLightingDevice
exclude = ('creation', 'last_modified')
EmergencyLightingFormSet = inlineformset_factory(EmergencyLighting, EmergencyLightingDevice,
extra=0, min_num=1, exclude=('creation', 'last_modified'))
urls.py
url(r'^(?P<site>[0-9]+)/create/$', EmergencyLightingCreate.as_view(), name='emergency-lighting-create'),
Like the error says, you're passing three arguments to form_valid; that's because you explicitly overrode post to send those three, and modified the signature of form_valid in FormsetMixin to accept them. However in EmergencyLightingCreate you've reverted to only accepting two arguments. You need to be consistent about how many arguments your methods accept when you're subclassing.
I have a DetailView which displays a Post. I now want to add the ability to create a Comment for a Post. For that I need a CommentForm, within the DetailView, so that I can create comments while being on the same page with a Post.
Is this possible, or should I be looking for another approach, like doing the form handling 'manually'?
class Comment(models.Model):
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
author_name = models.CharField(max_length=255)
parent_post = models.ForeignKey('Post',related_name='comments')
class PostDetailView(BlogMixin,DetailView):
""" A view for displaying a single post """
template_name = 'post.html'
model = Post
#Add some code for the CommentForm here?
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
exclude = ("parent_post","created_at")
def create_view(request, **kwargs):
if request.method == "POST":
parent_fk = request.args['parent_fk'] #Im hoping to find how this will work soon
form = CommentForm(request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.parent_post = parent_fk
new_comment.save()
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
** Alternative **
I have been trying to apply the solution - A better alternative - but I get
Exception Value: __init__() takes exactly 1 argument (3 given)
Exception Location: .../sitepackages/django/core/handlers/base.py in get_response, line 112
and have not been able to trace it yet.
class PostView(BlogMixin,DetailView):
""" A view for displaying a single post """
template_name = 'post.html'
model = Post
def get_context_data(self, **kwargs):
context = super(PostView, self).get_context_data(**kwargs)
context['form'] = CommentForm()
return context
class PostDetailView(View):
def get(self, request, *args, **kwargs):
view = PostView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = PostComment.as_view()
return view(request, *args, **kwargs)
class PostComment( SingleObjectMixin , FormView):
template_name = 'post.html'
form_class = CommentForm
model = Post
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(PostComment, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.object.pk})
class BlogMixin(object):
"""
Basic mixin for all the views. Update the context with additional
information that is required across the whole site, typically
to render base.html properly
"""
def get_context_data(self, *args, **kwargs):
context = super(BlogMixin, self).get_context_data(*args, **kwargs)
blog = Blog.get_unique()
context.update({
'blog': blog,
'active_user': users.get_current_user(),
'is_admin': users.is_current_user_admin()
})
return context
urls.py:
url(r'^post/(?P[\d]+)/$', views.PostDetailView., name="post-detail"),
If you want to use your first method, you can make the FK a hidden field. In your view, you can save the FK before committing the comment to the database. Like this:
if form.is_valid():
comment = form.save(commit=False)
comment.parent_post = parent_post
comment.save()
Edit: If you want to fetch the comments, then you can use filter by post to get a QuerySet of the comments.
Why dont you send your form in the context in detail view:
class YourDetailView(DetailView):
#Your stuff here
def get_context_date(self, **kwargs):
context = super(YOurDetailView, self).get_context_data(**kwargs)
context['form'] = YourForm
return context
PS. Look for the parameters in get_context_date..
In the end I could not make it work with the redirect, but the following is working:
class PostDetailView(BlogMixin,CreateView):
""" A view for displaying a single post """
template_name = 'post.html'
model = Comment
fields = ['body','author_name']
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['post'] = Post.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
# self.object = form.save()
obj = form.save(commit=False)
obj.parent_post = Post.objects.get(pk=self.kwargs['pk'])
obj.save()
return redirect('post-detail', self.kwargs['pk'])
in urls.py:
url(r'^add/from?(?P<link>[a-zA-Z0-9/]+)$', PostAdd.as_view(), name='post_add'),
in views.py:
class PostAdd(CreateView):
model = Post
form_class = PostForm
template_name = 'post_add.html'
success_url = ????
def get_context_data(self, **kwargs):
context = super(PostAdd, self).get_context_data(**kwargs)
context['from'] = self.kwargs['link']
return context
I can pass link to the template, but I need to set success_url=link
You should override the get_success_url method on the view, rather than using the success_url attribute.
class PostAdd(CreateView):
def get_success_url(self):
# Grab 'link' here and return it:
return self.kwargs['link']
I need to set the value of a BooleanField from my Django model via the CreateView for my ModelForm. But for some reason, setting it in form_valid() isn't working.
Here's my model:
class Feedback(models.Model):
was_satisifed = models.BooleanField(
help_text='Returns true if the user exits the process early.',
default=False)
Here's my view:
class FeedbackActionMixin(object):
model = Feedback
form_class = FeedbackForm
def form_valid(self, form):
instance = form.save(commit=False)
instance.was_satisfied = True
return super(FeedbackActionMixin, self).form_valid(form)
The form submits, but the "was_satisfied" value is left at the default False. What am I missing?
If memory serves, form_valid is called after the data has already been posted. You might try overriding the post method on your mixin:
class FeedbackActionMixin(object):
model = Feedback
form_class = FeedbackForm
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
form.was_satisfied = True
return self.form_valid(form)
else:
return self.form_invalid(form)
Here's what I ended up doing, though in forms.py not views.py:
class SuccessfulFeedbackForm(FeedbackFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SuccessfulFeedbackForm, self).__init__(*args, **kwargs)
self.fields['was_satisifed'].initial = True
class UnsuccessfulFeedbackForm(FeedbackFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UnsuccessfulFeedbackForm, self).__init__(*args, **kwargs)
self.fields['was_satisifed'].initial = False