Probably a simple question but having trouble implementing a form preview page using django-formtools. I've configured everything per the docs. I'm stuck on what to add to the done() method to save the data to db.
forms.py
class JobForm(ModelForm):
class Meta:
model = Job
fields = ('title', 'category', 'company', 'website', 'description',)
class JobFormPreview(FormPreview):
def done(self, request, cleaned_data):
# add what here to save form data as object?
return HttpResponseRedirect('/success')
urls.py
...
url(r'^jobs/new/$',
JobFormPreview(JobForm),
name='job_form'),
...
Using the default templates. The form and preview both render fine, but obviously data doesn't save on submit. Tried self.form.save() per this answer but get an error save() missing 1 required positional argument: 'self'.
I appreciate any guidance.
Looking at the formtools code, it looks as if self.form is the form class, not the validated form instance. Therefore self.form.save() will not work. I've removed the suggestion to call self.form.save() from the linked answer.
I can't see a straight forward way to access the validated form in the done method. I suggest that you create the instance using the cleaned_data instead:
class JobFormPreview(FormPreview):
def done(self, request, cleaned_data):
job = Job.objects.create(**cleaned_data)
return HttpResponseRedirect('/success')
Related
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.
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 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.
Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()
I'm using Django's FormWizard. It works fine but I'm having trouble getting any empty model formset to display correctly.
I have a model called Domain. I'm creating a ModelFormset like this:
DomainFormset = modelformset_factory(Domain)
I pass this to the FormWizard like this:
BuyNowWizardView.as_view([DomainFormset])
I don't get any errors but when the wizard renders the page, I get a list of all Domain objects. I'd like to get an empty form. How I can do this? I've read that I can give a queryset parameter to the ModelFormset like Domain.objects.none() but it doesn't seem to work as I get errors.
Any ideas on where I'm going wrong?
Thanks
The Django docs give two ways to change the queryset for a formset.
The first way is to pass the queryset as an argument when instantiating the formset. With the formwizard, you can do this by passing instance_dict
# set the queryset for step '0' of the formset
instance_dict = {'0': Domain.objects.none()}
# in your url patterns
url(r'^$', BuyNowWizardView.as_view([UserFormSet], instance_dict=instance_dict)),
The second approach is to subclass BaseModelFormSet and override the __init__ method to use the empty queryset.
from django.forms.models import BaseModelFormSet
class BaseDomainFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseDomainFormSet, self).__init__(*args, **kwargs)
self.queryset = Domain.objects.none()
DomainFormSet = modelformset_factory(Domain, formset=BaseDomainFormSet)
You then pass DomainFormSet to the form wizard as before.