ValidationError loses context - django

In my forms.py I raise an validation error when the user is already a member of the project. If i try to add a user who is already a member the validation error gets perfectly raised, but then I get redirected to the template and I have no context any more.
Any Best Practices in raising a form validation error? What am I doing wrong?
class AddUserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project')
self.user = kwargs.pop('user')
super(AddUserForm, self).__init__(*args, **kwargs)
self._user_cache = None
def clean_user(self):
"""
Check if the user is already a member of the project.
"""
user = self.cleaned_data['user']
if ProjectMember.objects.filter(project=self.project, user=user).exists():
raise forms.ValidationError(_("User is already a member of this project."))
# store user instance we queried for here to prevent additional lookups.
self._user_cache = user
return user
views.py without the ProjectUpdate view because it does not matter in this case. The views are a little bit complicated, because I have 2 forms in one template. If you know any better way to accomplish this, let me know.
class ProjectDetailView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
view = ProjectDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if 'update_form' in request.POST:
view = ProjectUpdate.as_view()
elif 'add_user_form' in request.POST:
view = ProjectAddUser.as_view()
return view(request, *args, **kwargs)
class ProjectDisplay(DetailView):
model = Project
def get_context_data(self, **kwargs):
context = super(ProjectDisplay, self).get_context_data(**kwargs)
context['update_form'] = ProjectUpdateForm(initial={
'name': self.object.name,
'description': self.object.description
})
context['add_user_form'] = AddUserForm(project=self.object, user=self.request.user)
context['project'] = self.object
context['is_member'] = self.object.user_is_member(self.request.user)
return context
class ProjectAddUser(CreateView):
model = ProjectMember
form_class = AddUserForm
template_name = 'projects/project_detail.html'
def get_success_url(self):
return reverse('project_detail', kwargs={'slug': self.get_object().slug})
def get_object(self, queryset=None):
return Project.objects.get(slug=self.kwargs['slug'])
def get_form_kwargs(self):
kwargs = super(ProjectAddUser, self).get_form_kwargs()
kwargs.update({'project': self.get_object()})
kwargs.update({'user': self.request.user})
return kwargs

Related

Django forms CheckboxSelectMultiple

Im trying to change a form from RadioSelect to MultipleChoice
with this form, I can see and the form in my tempalte as RadioButtons and can fill and save.
class TestForm(forms.Form):
def __init__(self, question, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
choice_list = [x for x in question.get_answers_list()]
self.fields["answers"] = forms.ChoiceField(choices=choice_list,
widget=RadioSelect)
But, when I change to
widget=CheckboxSelectMultiple
Then I can see and select all Choices, but after saving the page is reloading without saving.
class TestTake(FormView):
form_class = TestForm
template_name = 'question.html'
result_template_name = 'result.html'
single_complete_template_name = 'single_complete.html'
def dispatch(self, request, *args, **kwargs):
self.quiz = get_object_or_404(Quiz, url=self.kwargs['quiz_name'])
if self.quiz.draft and not request.user.has_perm('quiz.change_quiz'):
raise PermissionDenied
if self.logged_in_user:
self.sitting = Sitting.objects.user_sitting(request.user,
self.quiz)
else:
self.sitting = self.anon_load_sitting()
if self.sitting is False:
return render(request, self.single_complete_template_name)
return super(TestTake, self).dispatch(request, *args, **kwargs)
How can I insert a Multiple Select checkbox here?

How to use two different models in CreateView in django

I have following models:
class BankAccount(models.Model):
owner = models.ForeignKey(User)
class MoneyTransfer(models.Model):
sender = models.ForeignKey(BankAccount)
and url:
url(r'^accounts/(?P<pk>\w+)/send_transfer$', SendTransfer.as_view(), name='SendTransfer')
that means "I want to send money from Account with id=pk"
This is my view:
class SendTransfer(View):
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def get(self, request, *args, **kwargs):
instance = BankAccount.objects.get(id=self.kwargs['pk'])
if instance.is_legal():
if instance.organization.owners.all().filter(user__id=self.request.user.id).count() == 0:
return None
else:
if instance.citizen.user.id != self.request.user.id:
return None
return render(self.request, self.template_name, self.get_context_data())
def post(self, request, *args, **kwargs):
sender = BankAccount.objects.get(id=kwargs['pk'])
form = self.form_class(sender, self.request.user, request.POST)
if form.is_valid():
MoneyTransfer.objects.create(sender=sender,
receiver=BankAccount.objects.get(id=self.request.POST['receiver']),
total=float(self.request.POST['total']),
when=timezone.localtime(timezone.now()),
comment=self.request.POST['comment'])
return redirect('AccountDetail', kwargs['pk'])
data = self.get_context_data()
data['form'] = form
return render(request, self.template_name, data)
def get_context_data(self):
account = BankAccount.objects.get(id=self.kwargs['pk'])
return {'form': SendTransferForm(account, self.request.user),
'user': self.request.user,
'account': account}
I think there's a lot of redudant code for CBV. What can I do for shorting it?
UPD
my current code:
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer, self).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
last line of dispatch() method raises TypeError: init() takes at least 3 arguments (1 given)
CBVs are designed for code reuse. If you don't yet have another class that could benefit of code you posted, the actual amount of code is almost identical, be that a CBVs or a plain function.
But the more pythonic and Django-ish (from my biased POV) way would be to:
Inherit your class from the FormView instead of the View. That eases the form management a bit.
Add a SingleObjectMixin to get the object from url kwargs for free.
Move your object validation to the get_object() method. It's a good practive to raise 404 if your object doesn't validate.
Refactor out the get_context_data() as you already have all that data in your context (request, form and object)
Instead of relying on the self.request.POST, clean your data through the form.
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
Some of your code has gone thanks to the CBV magic, some just has moved to another methods. Take a look, I'd welcome your comments.

django SetPasswordForm and AdminPasswordChangeForm

hi im trying to use the above forms - but i get
__init__() takes at least 2 arguments (1 given)
i get to the form that it should show but it never save me the new password
i also needed to change the:
def __init__(self, user, *args, **kwargs):
self.user = user
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
to:
def __init__(self, *args, **kwargs):
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
since it doesnt get a user arg.
any ideas why?
thx
============================ edit =============================================
class set(FormView):
model = User
form_class = AdminPasswordChangeForm
template_name = 'set.html'
def dispatch(self, request, *args, **kwargs):
return super(set, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(set, self).get_form_kwargs()
kwargs['user_to_update'] = the user
return kwargs
the init:
def __init__(self, *args, **kwargs):
self.user = kwargs['user_to_update']
kwargs.pop('user_to_update')
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
Use the existing form. Overide the view's get_form_kwargs method to pass the expected arguments to the form, instead of changing the __init__ method, which will break other things.
In order to save the password, you need to override the form_valid method and call form.save().
For create and update views, you don't always need to override form_valid, because the default behaviour is to save the form and redirect. For FormView, the default behaviour is simply to redirect, so you do have to override it to get it to do anything useful.
class SetPasswordView(FormView):
form_class = AdminPasswordChangeForm
template_name = 'set.html'
success_url = '/thanks/'
def get_form_kwargs(self):
kwargs = super(set, self).get_form_kwargs()
kwargs['user_to_update'] = the user
return kwargs
def form_valid(self, form):
form.save()
return super(SetPasswordView, self).form_valid(form)

Curious about get_form_kwargs in FormView

I was having trouble with FormView recently and found that the way to go about doing it was to use get_form_kwargs.
Here is my code:
class InternalResetPasswordView(FormView):
template_name = 'reset_password.html'
form_class = forms.InternalPasswordResetForm
# success_message = "Password was reset successfully"
# To get request object
# http://notesondjango.wordpress.com/2012/12/18/modelform-formview-and-the-request-object/
# https://stackoverflow.com/questions/13383381/show-message-after-password-change
# http://pydanny.com/simple-django-email-form-using-cbv.html
# http://bubuzzz.wordpress.com/2012/05/01/class-based-generic-views-in-django-a-simple-sample/
def get_form_kwargs(self):
kwargs = super(InternalResetPasswordView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def get_success_url(self):
return reverse('user-detail', kwargs={'pk': self.request.user.id})
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(InternalResetPasswordView, self).dispatch(*args, **kwargs)
'''
def get_context_data(self, **kwargs):
context = super(InternalResetPasswordView, self).get_context_data(**kwargs)
context['InternalPasswordResetForm'] = context.get('form')
return context
def get_form_kwargs(self):
kwargs = super(InternalResetPasswordView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
'''
# self.request.user method obtained from
# https://docs.djangoproject.com/en/dev/topics/class-based-views/generic-editing/
def form_valid(self, form):
current_password = form.cleaned_data['old_password']
new_password = form.cleaned_data['new_password1']
confirm_new_password = form.cleaned_data['new_password2']
user = self.request.user
if user.check_password(current_password) and new_password == confirm_new_password:
user.set_password(new_password)
user.save()
# form.valid() redirects to get_success_url
return super(InternalResetPasswordView, self).form_valid(form)
After looking at this post, I still don't understand why get_form_kwargs has to be used and why using self.request instead of self.request.user in this case gives __init__() got an unexpected keyword argument 'request'.
Could someone explain this to me?
Thanks for all the help :)
The get_form_kwargs method will return a dictionary with the kwargs that will be passed to the __init__ of your form. Now, if you have a form that expects a kwarg named user and pass it a kwarg named request it will complain with the error you see. If you want to pass request instead of user (this is what I usually do since the request contains the user) then you should define your form class like this:
class RequestForm(forms.Form):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(RequestForm, self).__init__(*args, **kwargs)

Django - calling super in form __init__ starts form validation on "GET"

I have a ModelForm in my application in which I want to modify init function to add some customisation.
When init is commented out then the form works and validates properly. When I override init and go to url where the form is rendered it automatically says that "Field xyz is required"
Whats the cause of that problem?
class CreateListView(FormMixin, ListView):
def get_context_data(self, **kwargs):
self.object_list = self.get_queryset()
data = super(ListView, self).get_context_data()
data['object_list'] = self.get_queryset()
data['form'] = self.get_form(self.form_class)
return data
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form = form.save()
return HttpResponseRedirect(form.get_absolute_url())
return self.form_invalid(self.get_context_data())
class ActionGroupForm(forms.ModelForm):
class Meta:
model = ActionGroup
def __init__(self, *args, **kwargs):
super(ActionGroupForm, self).__init__(args, kwargs)
You are missing *, **:
super(ActionGroupForm, self).__init__(*args, **kwargs)