I now get the error below because of this line setattr(CsvModel, field.value(), CharField())
Error: 'dict' object is not callable
views.py
if request.method == 'POST':
form = ConfiguratorForm(data=request.POST)
# Send import to task.
# Clean all data and add to var data.
if form.is_valid():
data = form.cleaned_data
process_upload.delay(upload_id=upload.id, form=data)
tasks.py
#task
def process_upload(upload_id, form):
upload = Upload.objects.get(id=upload_id)
upload.process(form=form)
process
import_this(data=self.filepath, extra_fields=[
{'value': self.group_id, 'position': 5},
{'value': self.uploaded_by.id, 'position': 6}], form=form)
model.py
def import_this(form, *args, **kw):
# make custom ContactCSVModel
class ContactCSVModel(CsvModel):
for k, v in form:
setattr(CsvModel, v, CharField())
group = DjangoModelField(Group)
contact_owner = DjangoModelField(User)
class Meta:
delimiter = ","
dbModel = Contact
update = {'keys': ["mobile", "group"]}
return ContactCSVModel.import_data(*args, **kw)
In the second call you are passing in form.cleaned_data, which is a mapping (a dict), so you are looping over the keys, which are strings.
In the first call on the other hand, you are passing in the form itself. The second call is thus not the same; the following call would be:
form = ConfiguratorForm(data=request.POST)
if form.is_valid():
process_upload(upload_id=upload.id, form=form)
The question here is if that is what you actually meant to do.
In the first case your form argument is a form instance.
In the second your form argument is a dict instance.
Additionnally:
I think your import_this method (if it is, indeed, a method of your model) lacks a self first argument or should be declared as a #staticmethod. It may save you the trouble of calling it with import_this(self.xxx, self.yyy, ...) But I'm not quite sure how you go from upload.process() to a call to import_method() (my Django is a bit rusty).
And for what it's worth I think there are much simpler ways to achieve what you want to achieve (which if I guess well, is load CSV file):
Without form validation: have a look at csv.DictReader() will return a dict for each line in your file. Then do:
with csv.DictReader(...) as r:
for line in r:
instance = MyModel(**line)
instance.save()
With a form to validate data read from the file: do the same but instanciate a ModelForm, which you'll need to define:
with csv.DictReader(...) as r:
for line in r:
form = MyModelForm(**line)
form.save()
What you are doing looks like some kind of strange, brain-damaging, meta-programming... Are you trying to create a model on the fly, from what you find in a CSV file ?
Hope this helps.
Related
I am trying to make a form and formset work together which are not related to each other.Any suggestions if there are easy solutions to this?
def resume(request):
form=ResumeForm(request.POST)
ExperienceFormSet=formset_factory(Experience)
formset=ExperienceFormSet(request.POST,request.FILES)
print(form.is_valid)
if request.method=='POST':
if form.is_valid() and formset.is_valid():
name=request.POST.get('name')
email=request.POST.get('email')
phone=form.cleaned_data['phone']
objective=request.POST.get('objective')
branch=request.POST.get('branch')
course=request.POST.get('course')
course_total=course+' in '+branch
department=request.POST.get('department')
other_link=request.POST.get('other_link')
for f in formset:
cd=f.cleaned_data
companyName=cd.get('companyName')
print(companyName)
else:
form=ResumeForm()
ExperienceFormSet=formset_factory(Experience)
formset=ExperienceFormSet()
return render(request,'resume.html',{'name':name,'email':email,'phone':phone,'objective':objective,'course_total':course_total,'department':department,'other_link':other_link})
if request.method isn't 'POST', the variable name will never be created. Thus, when your function tries to return this part: {'name':name it won't find the variable name and it will fail.
These two lines might cause the problem:
if request.method=='POST':
if form.is_valid() and formset.is_valid():
First if request.method is not a POST then the name variable will not be created thus name referenced before assignment error will occur. Same for second line of code too. You can solve this problem with like this:
if request_method=='POST':
form_valid=form.is_valid()
formset_valid=formset.is_valid()
if form_valid and formset_valid:
# ...
# ...
if request_method=="POST" and form_valid and formset_valid:
return render(request,'resume.html',{'name':name,'email':email,'phone':phone,'objective':objective,'course_total':course_total,'department':department,'other_link':other_link})
else:
# Something is not valid you need to handle this.
There are quite a few things wrong with your view: you create a bound form right from the start (uselessly when it's a GET request), you use the raw request.POST data instead of the sanitized ones from the form's cleaned_data, and - the issue you mention - you try to inconditionnally use variables that are only conditionnally defined.
The proper canonical function-based-view template for form submissions is:
def myview(request):
# if you have variables that you want to always
# be in the context, the safest way is to define
# them right from the start (with a dummy value):
name = None
# now FIRST test the request method:
if request.method == "POST":
# and ONLY then build a bound form:
form = MyForm(request.POST)
if form.is_valid():
# use sanitized data, not raw POST data:
name = form.cleaned_data["name"]
else:
# build an unbound form
form = MyForm()
# here you know that both variables have been defined whatever
return render(request, "mytemplate.html", {"form": form, "name": name}
im geting this error "save() got an unexpected keyword argument 'commit'"
what im trying to do is request user when user upload his files.
update i added my model.py and forms.py and also screen shot of error sorry my fisrt time learning python/django.
screen shot
model.py
class Document(models.Model):
fs = FileSystemStorage(location=settings.MEDIA_ROOT)
input_file = models.FileField(max_length=255, upload_to='uploads', storage=fs)
user = models.ForeignKey(User)
def __unicode__(self):
return self.input_file.name
#models.permalink
def get_absolute_url(self):
return ('upload-delete', )
forms.py
class BaseForm(FileFormMixin, django_bootstrap3_form.BootstrapForm):
title = django_bootstrap3_form.CharField()
class MultipleFileExampleForm(BaseForm):
input_file = MultipleUploadedFileField()
def save(self):
for f in self.cleaned_data['input_file']:
Document.objects.create(
input_file=f
)
here is my views.py
#login_required
def list(request):
# Handle file upload
if request.method == 'POST':
form = MultipleFileExampleForm(request.POST, request.FILES)
if form.is_valid():
newdoc = form.save(commit=False)
newdoc.user = request.user
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('myfiles.views.list'))
else:
form = MultipleFileExampleForm() # A empty, unbound form
documents = Document.objects.all
return render_to_response(
'example_form.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
You are not sub classing django.forms.ModelForm, yet, you are writing your code like you are.
You need to subclass ModelForm (which has the save method with the commit argument).
Calling super will not work either, as the super class has no save method with that argument.
Remove the commit=False it will never work unless you rewrite your code to subclass django.forms.ModelForm
In any case the save method should always return an instance. I suggest you rename your method to save_all_files or something similar. You will not be able to use commit=False to save multiple object in your save method. It is not the intended use.
For further reading, you can read the source to know how the commit=False works in the ModelForm class at the following address :
https://github.com/django/django/blob/master/django/forms/models.py
I believe you are completely overriding the save method, which gets rid of the existing functionality (i.e. the commit arg). Try running a super() at the end so that it has the existing save functionality as well.
def save(self):
for f in self.cleaned_data['input_file']:
Document.objects.create(
input_file=f
)
super(MultipleFileExampleForm, self).save(*args, **kwargs)
I'm trying to write an app for Django. I want my users to be able to collect certain types of data, for instance samples, videos, etc... The app is called collector and for each type of item there is a class and a form that goes along with it.
Example Class:
class CreateTextView(CreateItemView):
form_class = TextForm
model = Text
def get_context_data(self, **kwargs):
context = super(CreateTextView, self).get_context_data(**kwargs)
context['item_type'] = 'text'
return context
Example Form:
class TextForm(forms.ModelForm):
class Meta:
model = Text
fields = COMMON_FIELDS + ('text',)
As you can see, the actual view is inheriting from CreateItemView. I want as much of the functionality to be defined for CreateItemView so that I don't have to do it individually for all item classes. That has been working for the most part, but it gets a bit tricky when I try to process forms with data.
def post(self, request, *args, **kwargs):
form = TextForm(request.POST) # line 2
form = getattr(TextForm, '__init__')(data=request.POST) # line 3
if form.is_valid():
# Add owner information.
item = form.save(commit=False)
item.owner = request.user
item.save()
return HttpResponseRedirect(reverse('collector:index'))
return render(request, self.template_name, {'form': form})
In Line 2 you can see how I would handle the form if there was only one type of form. Line 3 is what I'm trying to do. I want to be able to use the context['item_type'] to dynamically choose the right form and instantiate it with the given data.
Now the problem lies with the __init__-method which I have never defined anywhere. When I pass only POST.request to __init__, it complains about not having a self. When I pass the additional self, it complains about how CreateTextView has no _meta-attribute and so on. I just can't find the right combination of argumentes to satisfy the __init__-method. I can't look up it's definition either, because I didn't define it. I then followed the definition of the parent classes in the django framework which led me to a couple of complex functions that looked like factories. That didn't really help me...
Now I know how to use the TextForm()-initiation. Is there a way to fetch this method dynamically with getattr()? That would save me the hassle with __init__. If not, how do I provide __init__ with the correct self-instance?
As mentioned below, I have changed my classes a little bit. I no longer use context to store the item_type, instead I use a class variable to have easy acces to the item_type within a view. My post method is defined in the mother class CreateItemView and looks like this now:
def post(self, request, *args, **kwargs):
try:
form_cls = ITEM_TYPE_MAP[self.item_type]
except KeyError:
# invalid item_type. raise a meaningful error here
raise Exception('Invalid item type.')
form = form_cls(request.POST)
if form.is_valid():
# Add owner information.
item = form.save(commit=False)
item.owner = request.user
item.save()
return HttpResponseRedirect(reverse('collector:index'))
return render(request, self.template_name, {'form': form})
A clean and quite simple solution to look for is using a dictionary to map the item_type values to actual form classes:
ITEM_TYPE_MAP = {
"foo": TextForm,
"bar": SomeOtherForm,
}
You’d put that dictionary at some global place and use it from within the controller like this:
item_type = context['item_type']
try:
form_cls = ITEM_TYPE_MAP[item_type]
except KeyError:
# invalid item_type. raise a meaningful error here
raise
form = form_cls(request.POST)
You cannot directly call __init__ usually, because there’s more than that to instanciate an object. Python will also call __new__ on the class of the object, so the only way to be sure is to go through the actual constructor, which is calling the type.
This is what happens above, by first fetching the type into form_cls and then calling the type (i.e. the constructor).
So I have this custom ModelForm that I created that takes in a variable creator_list for the queryset like this:
class UserModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.get_full_name()
class OrderCreateForm(ModelForm):
class Meta:
model=Order
fields=('work_type', 'comment',)
def __init__(self, creator_list=None, *args, **kwargs):
super(OrderCreateForm, self).__init__(*args, **kwargs)
if creator_list:
self.fields['creator'] = UserModelChoiceField(
queryset=creator_list,
empty_label="Select a user",
widget=Select(attrs={
'onchange': "Dajaxice.doors.orders_create_creator_changed(fill_other_fields, {'creator_pk': this.options[this.selectedIndex].value})"
})
)
self.fields['place'] = UserModelChoiceField(
queryset=User.objects.none(),
empty_label="Select a creator first"
)
When I am simply displaying the fields, everything works perfectly. However during a POST submission. I get errors that I don't know how to debug.
My views.py looks like this:
user = request.user
dictionary = get_order_create_dictionary(user)
if request.method == 'POST':
#import ipdb; ipdb.set_trace()
form = OrderCreateForm(request.POST)
if form.is_valid():
creator = form.cleaned_data['creator']
place = form.cleaned_data['place']
work_type = form.cleaned_data['work_type']
comment = form.cleaned_data['comment']
new_order = Order.objects.create(
creator =creator,
place =place,
work_type=work_type,
comment =comment
)
messages.success(request, "Your order #{} had been created!".format(new_order.pk))
logger.info("{user} created order #{pk}".format(user=user, pk=new_order.pk))
return HttpResponseRedirect(reverse('orders_detail', kwargs={'pk': new_order.pk}))
else:
return render(request, 'doors/orders/create.html', {'form': form, 'can_assign_creator': dictionary['can_assign_creator']})
else:
if dictionary:
return render(request, 'doors/orders/create.html', {
'form': OrderCreateForm(creator_list=dictionary['creator_list']),
'can_assign_creator': dictionary['can_assign_creator']
})
else:
return HttpResponseRedirect(reverse('orders_list'))
get_order_create_dictionary() simply returns a dictionary that looks like this:
dictionary = {
'creator_list': Order.objects.all(), # Or some kind of filtered Order.
'can_assign_order: 1, # Or 0. This variable is used in the template to control access to what gets displayed.
}
Currently with the above code I get an error like this when I try to POST something:
AttributeError: 'QueryDict' object has no attribute 'all'
on the line "return render(request, 'doors/orders/create.html', {'form': form, 'can_assign_creator': dictionary['can_assign_creator']})"
I thought it has something to do with the line form = OrderCreateForm(request.POST) so I changed that to form = OrderCreateForm(request.POST, creator_list=dictionary['creator_list']). But then I get this error:
TypeError: __init__() got multiple values for keyword argument 'creator_list'
on the line "form = OrderCreateForm(request.POST, creator_list=dictionary['creator_list'])"
I have no clue how to resolve this. I appreciate any help or tips! Thanks!
EDIT:
I changed the line to form = OrderCreateForm(dictionary['creator_list'], request.POST) and now the validation works, but it won't let me submit a valid POST. It keeps saying Select a valid choice. That choice is not one of the available choices. for the place. This probably has something to do with how I populate the <option> with place using Ajax depending on what the creator is.
You'd better instantiate Form instances with only named arguments, i.e.
form = OrderCreateForm(creator_list=dictionary['creator_list'], data=request.POST)
One exception is when form only has one argument - the data. This will help you to avoid messing up with arguments order (which is the reason of your errors here).
I have been using this site as an example of how to make a dynamic form in Django. In his view he uses
if request.method == 'POST':
form = UserCreationForm(request.POST)
to pass the data into the form and in the form constructor he uses
extra = kwargs.pop('extra')
to access the POST data. I tried to do something similar with my view:
def custom_report(request):
if request.method=='POST':
form=CustomQueryConstraintForm(request.POST)
else:
form=CustomQueryConstraintForm()
return render(request, 'frontend/custom_report.html', {'form':form})
In my form constructor I printed args and kwargs and found that kwargs is empty and args is a tuple containing the QueryDict that in turn contains the POST data. If I try instead to use form=CustomQueryConstraintForm(**request.POST), each element in kwargs is a list containing the value of the field as its only element. Am I doing something wrong here? If not, is there a more elegant way of accessing the data than args[0][element_name][0]?
That is expected behavior for forms: the POST data you pass into the form is the first argument, args[0] and not a keyword argument. What are you looking for?
data = args[0]
print data['my_field']
and in the form constructor he uses
extra = kwargs.pop('extra') to access
the POST data.
kwargs.pop('extra') is not getting POST data. It is a list of questions associated with that given user -- some scenario given by the author that the "marketing department" handed you.
In any case, if you need to access the post data at any point in a form, I find self.data the cleanest which is set in forms.__init__.
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.data['my_field']
If I understand correctltly, after a POST request you are trying to redisplay the same form page in which the form fields are filled, right? If yes, this is what you need:
form = CustomQueryConstraintForm(initial=request.POST)
You can also access the form data through the cleaned_data dictionary after using the is_valid() method. Like this:
jobForm = JobForm(request.POST)
jobForm.is_valid()
jobForm.cleaned_data
it's a dictionary of the values that have been entered into the form.