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)
Related
I apologize for my confusing title but I hope the code explains it better.
In my views.py file I have the follow view
def create_view(request):
context = {}
form = CreateForm(request.POST)
if request.method == "POST":
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
instance.author.profile.participating_in = Post.objects.get(
title=instance.title
)
instance.save()
print(instance.author.profile.participating_in)
context["form"] = form
return render(request, "post/post_form.html", context)
when I print out the value of instance.author.profile.participating_in it shows up in my terminal however when I check the admin page it doesnt update at all. I'm sure I messed up somewhere silly but I cant seem to find it. Thanks!
participating_in is the profile model field, but you are not calling the save() method for profile anywhere.
You have to do it like the following:
profile = instance.author.profile
profile.participating_in = Post.objects.get(title=instance.title)
profile.save()
If participating_in is ManyToManyField then we can do it like this:
post = Post.objects.get(title=instance.title)
instance.author.profile.participating_in.add(post)
Note that add(), create(), remove(), clear(), and set() all
apply database changes immediately for all types of related fields. In
other words, there is no need to call save() on either end of the
relationship.
Look at Related objects reference
According to the documenation on UpdateView this should be really simple and indeed the form is showing with the content from the database, but when clicking the submit button django displays a message saying:
'ProfileForm' object is not callable'.
Why would it need a callable form ? The form works well with CreateView for example so no problem there, dont get why it would complain now.
I have researched stackoverflow and search on google and indeed there are results, but none of them seem to apply to my situation as I dont see that im making any mistakes although clearly I apparently am according to django.
My code is as follows:
class PortfolioEditBase(UpdateView):
post_req = False
url_name = ''
def form_valid(self, form):
self.post_req = True
return super(PortfolioEditBase, self).get_form(form)
def get_context_data(self, **kwargs):
context = super(PortfolioEditBase, self).get_context_data(**kwargs)
context['post_req'] = self.post_req
context['profile_id'] = self.kwargs['profile_id']
return context
def get_success_url(self):
return reverse(self.url_name, args=self.kwargs['profile_id'])
class PortfolioEditGeneralInfo(PortfolioEditBase):
model = Profile
form_class = ProfileForm
url_name = 'plan:general-info-edit'
template_name = 'plan/portfolio/edit/general_info_edit.html'
Profile form has the following code:
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ['company', 'exchange', 'ticker',
'investment_stage', 'investment_type']
widgets = {
'earnings_growth': Textarea(attrs={'cols': 1, 'rows': 2}),
}
This is the relevant urls.py code:
url(r'^portfolio/general-info/edit/(?P<profile_id>[0-9]+)$', views.PortfolioEditGeneralInfo.as_view(), name='general-info-edit'),
I dont think the error message django gives me makes any sense. How about giving an error message that says a little more about what the actual problem is. Using function based views this is pretty simple and works with few lines of code, but class based views are suppose to be "best practice". It seems to try to fetch the form data, but i dont get why it would have any problems with that and why it would call a from instead of fetch the data with request.POST.
Do anyone know what is wrong here ? So annoying when its suppose to be so simple. The other class based views ived used has been working almost without any problems.
The error in form_valid method. It should be form_vaild instaed of get_form:
def form_valid(self, form):
self.post_req = True
return super(PortfolioEditBase, self).form_valid(form)
get_form method expecting form class as argument. But you are passing form instance.
I'm running Django 1.7.
I have the following model form:
class DeckCreateForm(forms.ModelForm):
csv_file = forms.FileField(required=False)
class Meta:
model = Deck
fields = ['title', 'description']
Notice that the file field is not part of the model (and I would like to keep it this way). This file field is meant to provide an alternative means of constructing the model Deck.
I would like to know how to access the uploaded file. I looked in my media directory but it is not there. I tried adding a "upload_to" to the csv_file constructor but get an error:
TypeError: __init__() got an unexpected keyword argument 'upload_to'
EDIT:
I would like to know how to get this to work with a generic class based create view which makes use of the above model form - in views.py I have:
class DeckCreateView(CreateView):
model = Deck
form_class = DeckCreateForm
template_name = 'deck_create.html'
Specifically, how do I modify something like http://docs.djangoproject.com/en/1.7/topics/http/file-uploads to work with the above class based view. My urls.py file:
urlpatterns = patterns(
...
url(r"^deck/create/$", views.DeckCreateView.as_view(), name="deck-create"),
...
)
Is there a method which I can override in DeckCreateView to handle the file upload?
I've found that the Django documentation concerning file uploads can be a little difficult to understand for newer Django users. However, I think that the following link provides a very concise and easy to follow step by step process of setting up a file upload form.
Need a minimal Django file upload example
I believe you'll find everything you need there.
Edit
In response to the OP's edit and comment concerning class based views, I believe they can be clearer and arguably "cleaner" looking code than function based views. Here is a great link discussing CBV and FBV that includes a simple, but effective example of CBV.
http://www.datalifebalance.com/2014/04/django-file-uploads-with-class-based-views.html
Addendum to Edit
For the sake of completeness, and to limit the dependence of the answer on the above external link (which may disappear one day), we add a few more details. In order to achieve their objective, the OP could override the post method of DeckCreateView and save, __init__ of DeckCreateForm like so:
views.py:
...
class DeckCreateView(CreateView):
...
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect(self.success_url)
else:
return render(request, self.template_name, {'form': form})
forms.py
...
class DeckCreateForm(forms.ModelForm):
...
def __init__(self, post_data, files_data):
self.csv_file = files_data.get('csv_file', None)
return super(DeckCreateForm, self).__init__(post_data, files_data)
def save(self, *args, **kwargs):
deck = super(DeckCreateForm, self).save(*args, **kwargs)
self.handle_csv_file(self.csv_file, deck)
return deck
def handle_csv_file(f, deck):
...
for chunk in f.chunks():
...
...
Upon form submission a request is sent to DeckCreateView::post. The file handling occurs when DeckCreateForm::save is called.
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).