My question is when I use an UpdateView, it doesn't update the record. Besides this it also doesn't give any error.
When I debug code, the form_valid() function is not called after the HTTP POST request. But my form class (AdvertisementDetailForm) is correctly working. I couldn't find what I'm doing wrong.
Console outputs like that:
GET
/advertisement/publish/adv1_3ad5f56a-d99d-4ed4-b878-1327b9fa1bf8/1/
HTTP/1.1
POST
/advertisement/publish/adv1_3ad5f56a-d99d-4ed4-b878-1327b9fa1bf8/1/
HTTP/1.1
1st App views.py:
class PublishAdvertisement(LoginRequiredMixin, generic.UpdateView):
login_url = '/'
redirect_field_name = '/'
model = AdvSummary
form_class = AdvertisementDetailForm
template_name = 'advertisement/publish_advertisement.html'
success_url = reverse_lazy('brand:brand_home')
def form_valid(self, form):
pk = self.kwargs.get('pk')
obj = get_object_or_404(AdvSummary, pk=pk)
obj.advertisement_image = form.cleaned_data['advertisement_image']
obj.adv_max_follower = self.request.POST['adv_max_follower']
obj.adv_min_follower = self.request.POST['adv_min_follower']
obj.adv_desc = self.request.POST['adv_desc']
selected_categories = Category.objects.filter(pk__in=self.request.POST.getlist('categories'))
obj.categories.add(*[cat for cat in selected_categories])
obj.publish_date = timezone.now()
obj.save()
return super().form_valid(form)
urls.py:
urlpatterns = [
path('publish/<slug:slug_name>/<int:pk>/',
views.PublishAdvertisement.as_view(), name='publish'),
path('delete/<slug:slug_name>/<int:pk>/',
views.DeleteAdvertisement.as_view(), name='delete'),
]
2nd app views.py:
class BrandHomePage(LoginRequiredMixin, generic.View):
login_url = '/'
redirect_field_name = '/'
def post(self, request, *args, **kwargs):
view = CreateAdvertisement.as_view()
return view(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
view = PublishedAdvertisementList.as_view()
return view(request, *args, **kwargs)
urls.py:
urlpatterns = [
path('', views.BrandHomePage.as_view(), name='brand_home'),
]
Solution:
I've figured it out problem's reason when I added form_invalid() function just like that:
def form_invalid(self, form):
print("form is invalid")
return HttpResponse("form is invalid.. this is just an HttpResponse object")
And I've find out error in forms.py
Thank you
def form_valid(self, form):
pk = self.kwargs.get('pk')
obj = get_object_or_404(AdvSummary, pk=pk)
Check here that you are getting object or not.
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 })
In my detailView I have 2 methods get_context_data and post. In get_context_data I can get the detailView pk with self.object.pk but how can I get it in the post method?
[ updated ]
here is the view
class Class_detailView(LoginRequiredMixin, DetailView):
login_url = '/'
model = Class
template_name = "attendance/content/teacher/class_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['attendance_form'] = AttendanceForm(current_class_pk=self.object.pk) # pass data to form via kwargs
return context
def post(self, request, *args, **kwargs):
if request.method == "POST":
attendance_form = AttendanceForm(request.POST)
if attendance_form.is_valid():
attendance_form.instance.teacher = self.request.user
attendance_form.save()
return redirect('class-detail', pk=self.kwargs.get('pk'))
form
class AttendanceForm(forms.ModelForm):
class Meta:
model = Attendance
fields = ['student',]
def __init__(self, *args, **kwargs):
current_class_pk = kwargs.pop('current_class_pk')
super(AttendanceForm, self).__init__(*args, **kwargs)
current_student = Class.objects.get(id=current_class_pk)
self.fields['student'].queryset = current_student.student
I want to get the pk and pass it to the form when the post request is called.
How can I do it?
did you try this:
def post(self, request, *args, **kwargs):
if request.method == "POST":
attendance_form = AttendanceForm(request.POST, current_class_pk=self.kwargs.get('pk'))
if attendance_form.is_valid():
attendance_form.instance.teacher = self.request.user
attendance_form.save()
return redirect('class-detail', pk=self.kwargs.get('pk'))
I would redirect the user to "admin" page if he is superuser, else to normal "profile" but it didn't work
urls.py
url(r'^user/login/', LoginUserView.as_view(), name='login'),
url(r'^user/profile/', UpdateUserView.as_view(), name='profile'),
url(r'^user/admin/$', UpdateAdminView.as_view(), name='admin'),
views.py
class LoginUserView(auth_views.LoginView):
template_name = "Login/login.html"
#TODO
def dispatch(self, request, *args, **kwargs):
if self.request.user.is_superuser:
self.redirect_field_name = reverse_lazy("admin")
else:
self.redirect_field_name = reverse_lazy("profile")
return super(LoginUserView, self).dispatch(request, *args, **kwargs)
Setting redirect_field_name isn't going to work - it is the name of a parameter, by default 'next', not the url to redirect to.
If you want to the success url, you should override get_success_url.
class LoginUserView(auth_views.LoginView):
template_name = "Login/login.html"
def get_success_url(self):
url = self.get_redirect_url()
if url:
return url
elif self.request.user.is_superuser:
return reverse("admin")
else:
return reverse("profile")
The get_redirect_url() call at the beginning of the method means that the default behaviour that tries to use the redirect field will still work.
"e" is missing in:
self.redirect_field_name = reverse_lazy("profile")
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'])
I have a problem with my login redirect to FormView. It always redirects me to accounts/profile/ when next this the url/accounts/login/?next=/add/posts/ ought to take me to the url add/post/.
class LoginForm(FormView):
form_class = AuthenticationForm
redirect_field_name = REDIRECT_FIELD_NAME
template_name = 'accounts/login.html'
success_url = ''
#method_decorator(csrf_protect)
#method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
return super(LoginForm, self).dispatch(*args, **kwargs)
def form_valid(self, form):
self.check_and_delete_test_cookie()
login(self.request, form.get_user())
return super(LoginForm, self).form_valid(form)
def form_invalid(self, form):
self.set_test_cookie()
return super(LoginForm, self).form_invalid(form)
def get_success_url(self):
if self.success_url:
redirect_to = self.success_url
else:
redirect_to = self.request.REQUEST.get(self.redirect_field_name, '')
netloc = urlparse.urlparse(redirect_to)[1]
if not redirect_to:
redirect_to = settings.LOGIN_REDIRECT_URL
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
elif netloc and netloc != self.request.get_host():
redirect_to = settings.LOGIN_REDIRECT_URL
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
return redirect_to
def set_test_cookie(self):
self.request.session.set_test_cookie()
def check_and_delete_test_cookie(self):
if self.request.session.test_cookie_worked():
self.request.session.delete_test_cookie()
return True
return False
def get(self, request, *args, **kwargs):
self.set_test_cookie()
return super(LoginForm, self).get(request, *args, **kwargs)
I use to have a custom class to take care of this little feature, something like:
from django.urls import reverse_lazy
class NextUrlMixin(object):
""" Allows to redirect a view to its correct success url. """
def get_success_url(self):
if 'next' in self.request.GET:
return self.request.GET.get('next')
return reverse_lazy('index')
And use it in your CBV:
class LoginForm(NextUrlMixin, FormView):
# rest of your code
(NB: For Django<1.10, use from django.core.urlresolvers import reverse_lazy)