I am trying to create a wizard to import several of a model in Django using JSON. I want to be able to:
go to a URL like entries/import/ which will display a textfield
where I can paste in some JSON entries and hit submit
which will then bring me to step 2 of the wizard where I will be presented with a list of model forms
where upon inspecting/changing some data I can hit submit and have all the entries saved
It looks like I want to use a Form Wizard in conjunction with a FormSet. I have steps 1 and 2 complete, but I can't figure out how to get all the models to be presented as forms on step 2 of the wizard.
I've come across this link that shows where I might be able to convert the JSON to a FormSet, but I haven't been able to get it to work as of yet. Below is what I believe to be the relevant code. Can you help me figure out how to get the formset to be passed to step2?
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text', 'tags']
class ImportForm(forms.Form):
json = forms.CharField(widget=forms.Textarea, label='JSON')
class ImportSelectionForm(forms.Form):
entryFormSet = formset_factory(EntryForm)
FORMS = (
("step1", ImportForm),
("step2", ImportSelectionForm),
)
TEMPLATES = {
"step1": "entries/json_form.html",
"step2": "entries/entry_import_form.html",
}
class ImportWizard(SessionWizardView):
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def get_form_initial(self, step):
current_step = self.storage.current_step
if current_step == 'step2':
# Not getting here for some reason after submitting step1
prev_data = self.storage.get_step_data('step1')
json = prev_data.get('step1-json', '')
models = serializers.deserialize("json", json)
EntryFormSet = formset_factory(EntryForm)
formset = EntryFormSet(self.request.POST, self.request.FILES)
return self.initial_dict.get(step, {'formset': formset})
return self.initial_dict.get(step, {})
def done(self, form_list, **kwargs):
return HttpResponseRedirect(revierse_lazy('entries:index'))
You should just use step parameter passed to get_form_initial method instead of using self.storage.current_step. After testing I've noticed that self.storage.current_step contains previous step.
Also notice that for some reason get_form_initial is processed twice, once for previous and once for current step.
Related
Alright, I am pulling my hair out (and I don't have much) I have created a FormView that uses 2 models. One model simply displays some information from a table (not editable) the other Model is a form that a user selects two items from a drop down box. I need to filter the first Dropdown box. Below is the code I am using that is not working:
views.py
def assign_load(request):
form = DispatchForm(request.POST or None)
loads = Load.objects.all().filter(active=True, dispatched=False,
picked_up=False, delivered=False,
billed=False,
paid=False).order_by('start_pickup_date')
context_dict = {'dispatch' : form, 'load' : loads}
if form.is_valid():
save_it = form.save()
save_it.save()
new_dispatch = Dispatch.objects.get(id=save_it.id)
fix_load = Load.objects.get(id=new_dispatch.load_number_id)
fix_load.dispatched = True
fix_load.save()
return HttpResponseRedirect('/dispatch/dispatch/')
return render(request, 'dispatch/dispatch_form.html', context_dict)
forms.py
class DispatchForm(ModelForm):
class Meta:
model = Dispatch
fields = ['load_number', 'truck', 'start_mileage', 'end_mileage',
'pickup_date',
'pickup_time', 'delivery_date', 'delivery_time', 'driver_pay',
'fuel_cost', 'miles',
'status']
def get_queryset(self):
return self.model.objects.filter(load_number__dispatched=False)
I am trying to filter the model in forms.py I have tried using def get(), def get_queryset() and def get_context_data, none of them are returning a filtered queryset...I know I am missing something simple but I am running out of ideas any help would be great...if you need more information let me know that as well.
Thanks for all your help!
I'm currently working on a Django app that allows users to set synonyms for keywords.
Its using the following model:
class UserSynonym(models.Model):
user = models.ForeignKey(Tenant)
key = models.CharField(max_length=255)
value = models.CharField(max_length=255)
A ListView provides users with an overview, and each entry is followed by an 'edit' button that links to a relevant
EditView that allows the user to change the 'value' parameter.
I now want to add a button that allows the user to quickly reset the object to its original meaning (Set the 'value' to the value of 'key')
I want this to be a single button, without any forms or html templates. Just a button on the list that, when pressed, 'resets' the value of the model.
I reckon I have to edit an EditView for this task, however, these views keep demaning that I supply them with either a form or a HTML template/page
Which is not what I want to do.
Is there a way to change the EditView so that it changes the value of the object, without redirecting the user to a form or a new webpage?
EDIT:
for completeness sake I've added the UpdateView as I'm currently using it
class SynonymUpdate(UserRootedMixin, UpdateView):
model = UserSynonym
form_class = SynonymCreateForm
def get_form_kwargs(self):
kwargs = super(SynonymUpdate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.linked_user = self.kwargs.get('user')
return super(SynonymUpdate, self).form_valid(form)
def get_success_url(self, **kwargs):
return reverse('synonym_list', args=[self.request.user.id])
I sort of solved my problem. I gave up on using a class based view and used the following function instead:
def SynonymReset(request, user_id, pk):
"""(re)sets the Synonym.value to the value of Synonym.key"""
#Get relevant variables
currentuser = User.objects.get(id=request.user.id)
currentsynonym = Synonym.objects.get(id = pk)
#(re)set object & save
currentsynonym.value = currentsynonym.key
currentsynonym.save()
#Return to the listview.
return redirect('synonym_list', user=current_user)
This way the value is reset, without going to a seperate webpage. I still hope to one day find out how to do this in a class based view. But for now this will suffice.
I am trying to get a custom UpdateView to work in Python/Django. I believe that the code that I've writtten is mostly correct, as it seems to be returning the proper Primary Key ID in the URL when I click on the associated dropdown. The problem is that I am not seeing any of the data associated with this record on the screen in update mode. The screen appears in edit mode, but there is no data. I suspect the problem is perhaps the django template in the html form? However, I have played with the form and used {{ form }} and it too returns a blank form. I've played with this all afternoon and I'm out of guesses. Here is my view:
def updating_document(request, pk):
doc = get_object_or_404(Doc, pk=pk)
form = Update_Doc_Form(request.user, request.POST)
if request.method == 'GET':
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('App:main_procedure_menu'))
else:
print("Form is invalid!")
return render(request,'Doc/update_doc.html',{'form':form })
I also have an associated form...
Form.py
class Update_Doc_Form(forms.ModelForm):
class Meta:
model = Doc
exclude = ['user']
doc_name = forms.CharField(widget=forms.TextInput)
description = forms.CharField(required=True,widget=forms.Textarea)
team = forms.CharField(widget=forms.Select)
document = forms.CharField(required=True,widget=forms.Textarea)
def __init__(self, *args, **kwargs):
super(Update_Doc_Form, self).__init__(*args, **kwargs)
self.fields['doc_name'].widget.attrs['class'] = 'name'
self.fields['description'].widget.attrs['class'] = 'description'
self.fields['team'].widget.attrs['class'] = 'choices'
self.fields['team'].empty_label = ''
I'm a newbie, but I do want to use a custom UpdateView so that I can alter some of the fields and pass user information. I feel like the code is close, just need to figure out why it's not actually populating the form with data. Thanks in advance for your help!
What a difference a day makes. Found an answer on SO this morning. Not sure how to credit the person or issue number....
The answer was to add the following line of code to my form:
user = kwargs.pop('object_user')
I also needed to add the following function to my View:
def get_form_kwargs(self):
kwargs = super(ViewName,self).get_form_kwargs()
kwargs.update({'object_user':self.request.user})
return kwargs
This question was answered originally in 2013 by Ivan ViraByan. Thanks Ivan!
I ultimately went with a standard class based UpdateView and scrapped my plans for the custom UpdateView once I was able to figure out how to use the Class Based View(UpdateView) and "pop" off the user information when passing it to the form based on Ivan ViraByan's answer in 2013.
The code above allows you to get the user but not pass it to the ModelForm so that you don't get the unexpected user error.
As part of a Form Wizard in my Django view I am using a Formset. The wizard's forms for each step are declared like this:
UserFormSet = modelformset_factory(account_models.MyUser,
form=account_forms.MyUserForm,
extra=5,
max_num=10,
can_delete=True)
FORMS = [('userchoice', UserChoiceForm),
('user', UserFormSet),]
TEMPLATES = {'userchoice': "account/userchoice.html",
'user': "account/user.html",}
What I am trying to achieve is this: In UserChoiceForm (first step) the number of required users can be set. I want to use this value to dynamically set the extra attribute on UserFormSet so that only the required number of Forms gets displayed in the second step.
I am trying to do this by overriding the wizard's get_form() method:
class MyUserWizard(SessionWizardView):
def get_form(self, step=None, data=None, files=None):
form = super(MyUserWizard, self).get_form(step, data, files)
# Determine the step if not given
if step is None:
step = self.steps.current
if step == 'user':
# Return number of forms for formset requested
# in previous step.
userchoice = self.get_cleaned_data_for_step('userchoice')
num_users = userchoice['num_users']
CoFunderFormSet.extra = num_users
return CoFunderFormSet
return form
With this approach I am able to get the right amount of forms displayed for the second step, but when trying to post the Formset I am ending up with this error:
[u'ManagementForm data is missing or has been tampered with']
The POST data has the expected management form fields set, e.g.
form-TOTAL_FORMS u'1'
but I assume the FormWizard is using the Formset that was set in the initial FORMS list and therefore the management forms do not match.
I was wondering if there is a solution to this and if there is a way to tell the FormWizard to use the dynamically generated Formset on POST instead.
You can override get_form_initial, assume you have already set your form_list like this:
form_list = [
(FIRST_STEP, forms.FirstForm),
(SECOND_STEP, modelformset_factory(Second_model, form=forms.SecondForm)),
]
def get_form_initial(self, step):
"""
Set extra parameter for step2, which is from clean data of step1.
"""
if step == self.SECOND_STEP:
form_class = self.form_list[step]
data = self.get_cleaned_data_for_step(self.FIRST_STEP)
if data is not None:
extra = get_extra_count(data) # use cleaned data calculate extra
form_class.extra = extra
return super(PackageWizard, self).get_form_initial(step)
If you get this error message,
[u'ManagementForm data is missing or has been tampered with']
In your template make sure that you have given {{ wizard.form.management_form }}.
I am unable to upload the file. I am getting
Type error builtin_function_or_method' object is not iterable
models.py
class seeker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
class Upload(models.Model):
user = models.ForeignKey(Seekers)
resume = models.FileField(upload_to ='resume', blank = True, null = True)
forms.py
class SeekersForm(forms.Form):
resume = forms.FileField(label = 'Select a file',help_text = 'max.3 MB')
views.py
def List(request):
# Handle file upload
if request.method == 'POST':
form = SeekersForm(request.POST, request.FILES)
if form.is_valid():
#id = User.object.get(id)
newdoc = Seekers.objects.get(user_id)
newdoc.resume =Upload(resume = request.FILES['resume'])
newdoc.save()
#seekers_edit = Seekers.objects.get(id)
#seekers_edit.resume = Seekers(resume = request.FILES['resume'])
#seekers_edit.save()
#Redirect to the document list after POST
return HttpResponseRedirect('/profile/')
else:
form = SeekersForm() # A empty, unbound form
#Load documents for the list page
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html',{'seekers':seekers,'form':form},context_instance=RequestContext(request))
It's hard to say where your problem is, but I think the following line of code is the main problem:
newdoc.resume =Upload(resume = request.FILES['resume'])
You have to save a file in a FileField explicitly before you save the entire model instance. Also, if you have a ForeignKey field in one of your models and you want to assign it an instance of another model, please save that instance first before you do the assignment. Without knowing your Seekers model, all I can do is guessing what might help you. Something like the following might get you started:
your_file = request.FILES['resume']
upload_instance = Upload()
upload_instance.resume.save(name=your_file.name, content=your_file, save=False)
upload_instance.user = ... # Here goes an instance of your Seekers model
upload_instance.save() # Here you save the whole instance of your Upload model
Also, please note the following:
Your model Seekers should rather be named Seeker using the singular, not the plural. This should generally be like that with all your models.
Python functions should always start with a lowercase letter, i.e. list instead of List. However, this name is a bad choice here anyway, because a function called list is already present in Python's standard library.
Please take a closer look at Django's documentation. It's all in there what you need to know. I recommend you to read especially these sections:
https://docs.djangoproject.com/en/1.4/ref/models/fields/#filefield
https://docs.djangoproject.com/en/1.4/ref/files/file/
Problems in your code:
Your form definition duplicates information from your model — just use forms.ModelForm (with exclude so as not to display the user field)
As currently pasted, newdoc = Seekers.objects.get(user_id) will raise a TypeError ('foo' object is not iterable); .get() accepts keyword parameter filters, not anything else.
Accessing request.FILES['resume'] manually isn't necessary or recommended
So, in short, you're almost there; just let Django forms do more of the work for you:
# forms.py
class SeekerForm(forms.ModelForm)
class Meta:
model = Seeker
# views.py
def seeker_list(request):
# Opinions are divided as to whether it's ever appropriate to
# modify the database like this on a GET request, but it seems
# to make sense here
seeker = Seekers.objects.get_or_create(user=request.user)
if request.method == 'POST':
form = SeekerForm(request.POST, request.FILES, instance=seeker)
if form.is_valid():
form.save()
return HttpResponseRedirect('/profile/')
else:
form = SeekerForm(instance=seeker)
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html', {
'seekers':seekers,
'form':form
}, context_instance=RequestContext(request))
It's not clear what the significance (if any) of the commented-out sections of your code is — I've assumed you always want to modify the current user's Seeker, but if not then adapt as appropriate.