I'm having a problem accessing a Django Form POST data.
I need to pass request.user to the form, so:
class TradeForForm(forms.Form):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
else:
request = kwargs.pop('request')
super(TradeForForm, self).__init__(*args, **kwargs)
#Obtain items for user
if user:
print user
items = Item.objects.filter(user=user)
choices = []
for i in range(len(items)):
choices.append([i,items[i].name])
self.fields['item_to_be_traded_for'].choices = choices
trade_type = forms.ChoiceField(
widget=RadioSelect(),
choices = [
['0','Item'],
['1','Money offer'],
]
)
item_to_be_traded_for = forms.ChoiceField()
and then call it using:
def trade_for(request, item_id):
item = Item.objects.get(id=item_id)
if request.method == 'POST':
form = TradeForForm(request.POST)
if form.is_valid():
pass
else:
form = TradeForForm(user=request.user)
variables = RequestContext(request, {
'form': form,
'item': item,
})
return render_to_response('trade_for.html', variables)
Now the problem is, when doing GET to access the empty form, it works just fine. But when I post it, I received an error:
KeyError at /trade_for/1/
'user'
Request Method: POST
Request URL: http://localhost:8000/trade_for/1/
Django Version: 1.3.1
Exception Type: KeyError
Exception Value:
'user'
Now how can this be fixed? I assume it's because the user variable is not passed to the form when creating it using the request.POST data, but I want to be able to create the form with the user parameter and without it, both working.
you should probably pass the user to the form creator even with POST data so the choices can be validated properly, so
TradeForForm(request.POST, user=request.user)
if you don't want this, you need to change user = kwargs.pop('user') to something like
user = kwargs.pop('user', None)
# if kwargs has no key 'user', user is assigned None
# make sure your code handles this case gracefully
pop will raise a KeyError unless it has a default value. So you just need to pass it a default value - probably None:
user = kwargs.pop('user', None)
If you want the form to work without the user, change the constructor to:
user = kwargs.pop('user', None)
But then you have to be able to deal with user being None.
The suggestion of user = kwargs.pop('user', None) is part of your missing code. A better approach would be to drop in your own arguments to avoid all the popping!
class TradeForForm(forms.Form):
def __init__(self, user=None, request=None, *args, **kwargs):
super(TradeForForm, self).__init__(*args, **kwargs)
if user:
...code here...
Related
I have a detail view with 2 forms and here I provide code for only one of them. The form is located in a modal on user detailed view and I need to redirect the client to that detail view in which the form is. In the post method the request.GET['user'] returns the user id so I have everything needed to achieve this. I have tried the reverse and redirect, nothing worked I guess because of wrong code.
Should I provide a get_success_url() to do that? I think it will cause some problems because I can get user id only from post method.
class UserDetailView(LoginRequiredMixin, DetailView):
model = TbUser
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['entrance_rights_form'] = TbPeopleEntranceRightForm(
user=self.object, initial={'user': self.object})
return context
class TbPeopleEntranceRightFormView(FormView):
form_class = TbPeopleEntranceRightForm
template_name = 'users/create_entrance_permission_modal.html'
def post(self, request, *args, **kwargs):
print(request.POST['user']) # returns user id
entrance_rights_form = self.form_class(
user=None, data=request.POST)
terminal_permissions_form = TbTerminalPermissionForm(user=None)
if entrance_rights_form.is_valid():
entrance_rights_form.save()
return redirect('user-detail', args=(request.POST['user'],))
else:
return redirect('users-list')
urlpatterns = [
path('users-list/', UsersListView.as_view(), name='users-list'),
path('user-detail/<str:pk>/',
UserDetailView.as_view(), name='user-detail'),
path('tb-entrance-right-form/submit',
TbPeopleEntranceRightFormView.as_view(), name='tb-entrance-right-form'),
]
You don't need to pass the user id in the args as a tuple with redirect.
This should work:
if entrance_rights_form.is_valid():
entrance_rights_form.save()
user_id = request.POST['user'] # i suppose this returns user id as you mentioned
return redirect('user-detail', user_id)
EDIT:
you are not rendering the template inside the post method.
def post(self, request, *args, **kwargs):
print(request.POST['user']) # returns user id
entrance_rights_form = self.form_class(
user=None, data=request.POST)
terminal_permissions_form = TbTerminalPermissionForm(user=None)
if entrance_rights_form.is_valid():
entrance_rights_form.save()
return redirect('user-detail', request.POST['user'])
else:
return redirect('users-list')
return render(request, self.template_name, {'form': entrance_rights_form})
I am trying to manually change the name of a form field after it initialized in a CBV. You could do this after initializing:
form = self.get_form() # it is initialized here
form.cleaned_data['name'] = form.instance.name + ' (new)'
But I need to change the value after validating, so form.cleaned_data is no longer involved. I need to directly change the value of the BoundField. How can I do that? Or how can I do what I am trying to do in any other way?
Here is some cody of my view and form class:
class MyView(CBV):
def form_valid(self, form):
if copy:
form.instance.pk = None
name = form.instance.name + ' (new)'
form.instance.name = name
# it does work this way
data = form.data.copy()
data['name'] = name
form.data = data
self.object = form.save()
else:
pass
return self.render_to_response(self.get_context_data(form=form))
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = []
def __init__(self, *args, **kwargs):
self.copy = kwargs.get('copy', False)
super(MyForm, self).__init__(*args, **kwargs)
def clean_name(self):
# (1) doing this has no effect in the displayed form included in the response of a POST request
# (2) I don't always want to do this, only when a form is valid
cleaned_data = self.cleaned_data['name']
if self.copy:
cleaned_data['name'] += ' (new)'
return cleaned_data
You can override clean method. After inherit form, you can modify your cleaned_data after validation.
def clean(self)
# It will validate data
self.cleaned_data = super().clean()
# do what you want
self.cleaned_data['name'] = 'blah...'
return self.cleaned_data
Or you can use specific field, in this situcation, name.
def clean_name(self):
data = self.cleaned_data.get('name', '')
if not data:
raise forms.ValidationError("You must enter a name")
if not data.endswith('( new)'):
return data += '( new)'
return data
ADD
You can see the validation actually already DONE after super().clean().
You can check print self.is_valid() then you can see it's True. Or just add debugger in that line to check validation is already done
def clean(self):
# It will validate data
self.cleaned_data = super().clean()
# Check validation is already done here
# Or debugging it
# from IPython import embed; embed()
print(self.is_valid())
# do what you want
return self.cleaned_data
You should save the form first, with commit=False, and then change it in the instance.
def form_valid(self, form):
if copy:
instance = form.save(commit=False)
instance.pk = None
name = instance.name + ' (new)'
instance.save()
return...
(Also, you should always redirect after a successful post, not render a template.)
I have a custom Form where I need to pass in the request.user.username so I can use that to fill out forms.ModelChoiceField based on what user is accessing it.
Here is what I have
views.py
if request.method == 'POST':
form = DepartmentForm(request.POST, username=request.user.username)
# Do stuff
else:
form = DepartmentForm(username=request.user.username)
forms.py
class DepartmentForm(forms.Form):
def __init__(self, *args, **kwargs):
self.username = kwargs.pop('username', None)
super(DepartmentForm, self).__init__(*args, **kwargs)
Company = forms.ModelChoiceField(queryset=Company.objects.raw('''
SELECT DISTINCT c.id, c.name, c.createdBy, c.allowedTeam_ID FROM projects_company AS c
LEFT JOIN auth_user_groups AS aug ON c.allowedTeam_id=aug.group_id
LEFT JOIN auth_user AS au ON aug.user_id=au.id
WHERE au.username = %s OR c.createdBy = %s''',
[self.username, self.username]),
empty_label='Select company', widget=forms.Select(attrs={'class': 'materialSelect', 'id': 'companyDropdown'}))
But when I try to select fill out self.username, I get:
NameError: name 'self' is not defined
I tried searching for my solution, but none of the ones I found seem to work.
I would appreciate any help you guys can give.
If you need more information, please don't hesitate to ask, and I will update the question accordingly.
This is the error I'm getting:
line 25, in DepartmentForm
[self.username, self.username]),
NameError: name 'self' is not defined
Which is the end of the query where I select based on self.username
The line Company = forms.ModelChoiceField() is evaluated when the module is loaded. You don't have access to self.username at that point.
Instead, you should set the queryset in the form's __init__ method:
class DepartmentForm(forms.Form):
company = forms.ModelChoiceField(
queryset=Company.objects.none(), # use empty queryset now, we'll set the real queryset when the form is initialised
widget=forms.Select(...),
)
def __init__(self, *args, **kwargs):
self.username = kwargs.pop('username', None)
super(DepartmentForm, self).__init__(*args, **kwargs)
self.fields['company'].queryset = Company.objects.raw(...)
You can pre-fill any field with a dictionary:
if request.method == 'POST':
# do stuff
else:
form = DepartmentForm({"username":request.user.username})
or
if request.method == 'POST':
# do stuff
else:
form = DepartmentForm()
form.initial = {"username":request.user.username}
I do not want the logged in user to show up on this ModelMultipleChoiceField in order to restrict themselves from creating a following relationship with themselves? So how do I exclude the logged in user from the queryset, probably an easy fix but I'm new to Django and it has eluded me for a few hours now.
forms.py
class Add_Profile(forms.ModelForm):
def __init__(self,*args, **kwargs): # initializing your form in other words loading it
super(Add_Profile, self).__init__(*args, **kwargs)
user_id = kwargs.pop('user_id') # taking user_id out of the querylist
self.fields['follows'] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(), queryset=UserProfile.objects.filter(~Q(id=user_id)))
class Meta:
model = UserProfile
fields = (
'bio',
'follows',
'theme',
'profile_picture',
)
Views.py
#login_required
def edit_profile(request, user_id):
userprofile = UserProfile.objects.get(pk=user_id)
if request.method == 'POST':
edit_profile = Add_Profile(request.POST, request.FILES, instance=userprofile, user_id=request.user.id)
if edit_profile.is_valid():
edit_profile.save()
return redirect('/home/user/{0}/'.format(request.user.username))
else:
print edit_profile.errors
else:
edit_profile = Add_Profile(instance=userprofile, user_id=request.user.id)
return render (request, 'edit.html', {'form': edit_profile,})
Error: init() got an unexpected keyword argument 'user_id'
You can definitely do it using forms.Form instead of forms.ModelForm with something along the lines of this example in the docs:
from django import forms
from django.contrib.auth import get_user_model
class Add_Profile(forms.Form):
follows = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, user=None, *args, **kwargs):
super(Add_Profile, self).__init__(*args, **kwargs)
if user is not None:
self.fields['follows'].queryset = get_user_model().objects.exclude(pk=user.pk)
else:
self.fields['follows'].queryset = get_user_model.objects.all()
Just pass in the user you wish to exclude when you instantiate the form:
form = Add_Profile() # all users will be present in the dropdown
some_guy = User.objects.get(pk=4)
form = Add_Profile(user=some_guy) # all users except some_guy will be present
Define an __init__ method for the form class. Pass the logged in userid to the form while initializing it, this will work with a model form.
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user_id')
super(Add_Profile, self).__init__(*args, **kwargs)
self.fields['follows'] = forms.ModelMultipleChoiceField(queryset=UserProfile.objects.filter(~Q(user_id=user_id)))
While initializing your form, you can pass user_id
address_form = Add_Profile(request.POST, user_id=request.user.id)
I have a Django ModelForm with some initial data passed to it. Which is working fine so far.
But, if the user doesn't fill in all data, or makes another mistake the initial value will not be looked up again on redisplaying the form.
Here's a piece of code:
class TrainingAddForm(forms.ModelForm):
class Meta:
model = TrainingTasks
fields = ('task','ac_reg','date','wo_no')
def __init__(self, *args, **kwargs):
super(TrainingAddForm, self).__init__(*args, **kwargs)
self.fields['task'].required = False
self.fields['task'].widget.attrs['disabled'] = 'disabled'
self.fields['date'].widget = widgets.AdminDateWidget()
def clean_task(self):
return
An in forms.py:
def add_trainingtask(request, task_id):
if request.POST:
form = TrainingAddForm(request.POST)
if form.is_valid():
tt = TrainingTasks(
trainee = request.user,
task = Tasks.objects.get(pk=task_id),
date = form.cleaned_data['date'],
ac_reg = form.cleaned_data['ac_reg'],
wo_no = form.cleaned_data['wo_no'],
)
tt.save()
return HttpResponseRedirect('/admin/tot/tasks/')
else:
form = TrainingAddForm(initial = {"task": task_id})
return render_to_response('admin/tot/trainingtasks/add.html', {
'form': form,
'task_id': task_id
},
context_instance = RequestContext(request)
)
If a user misses to fill in i.e. the date (which is mandatory) the form will be redisplayed showing an error (field required), but the underlying record of task_id is not shown anymore.
The ID is still there and it's also possible to save the record (after correcting the error), so that's almost an irritating error for the user.
I guess I missed some piece of code, but I can't figure it out.
I'm not sure I understand the logic of your form or your view.
You've included the task field, yet disabled the field. With the task field disabled, the value isn't going to be in the request.POST collection.
In your view, you're passing the form the task_id parameter as the initial data, and if the request.method is a POST, you're retrieving the Task object from the database.
It seems like the Task is something that you want to assign to TrainingTask, but not it's not necessarily something you want to include in the form. Given that, I would:
#forms.py
class TrainingAddForm(forms.ModelForm):
class Meta:
model = TrainingTasks
fields = ('ac_reg','date','wo_no',)
#not include the task
def __init__(self, *args, **kwargs):
super(TrainingAddForm, self).__init__(*args, **kwargs)
self.fields['date'].widget = widgets.AdminDateWidget()
#views.py
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, render
from your_app.forms import TrainingAddForm
from your_app.models import Task, TrainingTasks
def add_trainingtask(request, task_id):
#make sure we have a valid Task object, or redirect.
#you could also use a try/except Task.DoesNotExist and show an error
task = get_object_or_404(Task, pk=task_id)
form = TrainingAddForm(request.POST or None)
if request.POST:
if form.is_valid():
tt = TrainingTasks(
trainee = request.user,
task = task,
date = form.cleaned_data['date'],
ac_reg = form.cleaned_data['ac_reg'],
wo_no = form.cleaned_data['wo_no'],
)
tt.save()
#dont hard-code the url here
return HttpResponseRedirect(reverse('admin_tot_tasks'))
return render(request, 'admin/tot/trainingtasks/add.html', {'form': form,
'task' : task})
Hope that helps you out.